XのタイムラインをTSVに変換してコピーする

    @@ -6,9 +6,9 @@ * @require */ -(async function() { - function pickText(el, sel) { - return el.querySelector(sel)?.textContent?.trim() || ""; +(function() { + function pick(el, selector) { + return el.querySelector(selector)?.textContent?.trim() || ""; } const rows = [...document.querySelectorAll('article[data-testid="tweet"]')].map(article => { @@ -17,13 +17,13 @@ const url = linkEl ? linkEl.href : ""; // ユーザーID (@...) - const handle = pickText(article, 'div[data-testid="User-Name"] a[href^="/"] span') || + const handle = pick(article, 'div[data-testid="User-Name"] a[href^="/"] span') || (article.querySelector('a[href^="/"]')?.getAttribute("href") || "").split("/")[1]; // 表示名 - const displayName = pickText(article, 'div[data-testid="User-Name"] span'); + const displayName = pick(article, 'div[data-testid="User-Name"] span'); - // 本文 + // 本文(複数tweetTextを結合) const text = [...article.querySelectorAll('[data-testid="tweetText"]')] .map(t => t.innerText.trim()) .join(" "); @@ -33,7 +33,18 @@ const tsv = rows.join("\n"); - // クリップボードにコピー - await navigator.clipboard.writeText(tsv); - alert("TSVをクリップボードにコピーしました!\n\n貼り付けて使えます。"); + // クリップボードへコピー(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(); + } })();
  • /*
     * @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() || "";
      }
    
      const rows = [...document.querySelectorAll('article[data-testid="tweet"]')].map(article => {
        // ツイートURL
        const linkEl = article.querySelector('a[href*="/status/"]');
        const url = linkEl ? linkEl.href : "";
    
        // ユーザーID (@...)
        const handle = pick(article, 'div[data-testid="User-Name"] a[href^="/"] span') ||
                       (article.querySelector('a[href^="/"]')?.getAttribute("href") || "").split("/")[1];
    
        // 表示名
        const displayName = pick(article, 'div[data-testid="User-Name"] span');
    
        // 本文(複数tweetTextを結合)
        const text = [...article.querySelectorAll('[data-testid="tweetText"]')]
          .map(t => t.innerText.trim())
          .join(" ");
    
        return [url, handle, displayName, text].join("\t");
      });
    
      const tsv = rows.join("\n");
    
      // クリップボードへコピー(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();
      }
    })();
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2025/08/11 15:13:48 - 08/11
  2. 2025/08/11 15:10:51 - 08/11