<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns="http://purl.org/rss/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel rdf:about="https://let.hatelabo.jp/Annenpolka/rss">
    <link>https://let.hatelabo.jp/Annenpolka/rss</link>
    <description></description>
    <title>Bookmarklets from Annenpolka</title>
    <items>
      <rdf:Seq>
        <rdf:li rdf:resource="https://let.hatelabo.jp/Annenpolka/let/k77UsOCsgYAA"/>
      </rdf:Seq>
    </items>
  </channel>
  <item rdf:about="https://let.hatelabo.jp/Annenpolka/let/k77UsOCsgYAA">
    <link>https://let.hatelabo.jp/Annenpolka/let/k77UsOCsgYAA</link>
    <dc:date>2025-08-11T07:38:13Z</dc:date>
    <description>my bookmarklet</description>
    <dc:creator>Annenpolka</dc:creator>
    <title>[Let] XのタイムラインをTSVに変換してコピーする</title>
    <content:encoded>&lt;a href="javascript:%22https%3A%2F%2Flet.st-hatelabo.com%2FAnnenpolka%2Flet%2Fk77UsOCsgYAA.bookmarklet.js%20%28arg%29%22.replace%28%2F%28%5CS%2B%29%5Cs%2B%28%5CS%2A%29%2F%2Cfunction%28s%2Curl%2Carg%29%7Bs%3Ddocument.createElement%28%22script%22%29%3Bs.charset%3D%22utf-8%22%3Bs.src%3Durl%2B%22%3Fs%3D%22%2BencodeURIComponent%28arg%29%3Bdocument.body.appendChild%28s%29%7D%29%3Bvoid%280%29%3B"&gt;XのタイムラインをTSVに変換してコピーする&lt;/a&gt;&lt;pre&gt;/*
 * @title XのタイムラインをTSVに変換してコピーする
 * @description my bookmarklet
 * @include https://x.com/*
 * @license MIT License
 * @require 
 */

(function() {
  function pick(el, selector) {
    return el.querySelector(selector)?.textContent?.trim() || &amp;quot;&amp;quot;;
  }

  const rows = [...document.querySelectorAll('article[data-testid=&amp;quot;tweet&amp;quot;]')].map(article =&amp;gt; {
    // ツイートURL
    const linkEl = article.querySelector('a[href*=&amp;quot;/status/&amp;quot;]');
    const url = linkEl ? linkEl.href : &amp;quot;&amp;quot;;

    // ユーザーID (@...)
    const handle = pick(article, 'div[data-testid=&amp;quot;User-Name&amp;quot;] a[href^=&amp;quot;/&amp;quot;] span') ||
                   (article.querySelector('a[href^=&amp;quot;/&amp;quot;]')?.getAttribute(&amp;quot;href&amp;quot;) || &amp;quot;&amp;quot;).split(&amp;quot;/&amp;quot;)[1];

    // 表示名
    const displayName = pick(article, 'div[data-testid=&amp;quot;User-Name&amp;quot;] span');

    // 本文（複数tweetTextを結合）
    const text = [...article.querySelectorAll('[data-testid=&amp;quot;tweetText&amp;quot;]')]
      .map(t =&amp;gt; t.innerText.trim())
      .join(&amp;quot; &amp;quot;);

    return [url, handle, displayName, text].join(&amp;quot;\t&amp;quot;);
  });

  const tsv = rows.join(&amp;quot;\n&amp;quot;);

  // クリップボードへコピー（execCommandはCSPの影響を受けにくい）
  const ta = document.createElement('textarea');
  ta.value = tsv;
  document.body.appendChild(ta);
  ta.select();
  try {
    if (document.execCommand('copy')) {
      alert('TSVをクリップボードにコピーしました');
    } else {
      alert('コピーに失敗。テキストは選択状態になっています');
    }
  } finally {
    ta.remove();
  }
})();
&lt;/pre&gt;</content:encoded>
  </item>
</rdf:RDF>
