X のタイムラインでリポストだけを非表示にしたり表示に戻したりと切り替えたくなることはないでしょうか。

この記事では、Google Chrome 用の拡張機能を自作する手順をまとめます。ツールバーのアイコンからポップアップを開き、スイッチを切り替えると、表示中の X のタブのタイムラインから、リポスト行を非表示にしたり、再び表示したりできます。

できること

  • タイムライン(おすすめ・リスト等)に表示されるリポストを非表示にできる
  • 表示・非表示をいつでも切り替えられる
  • 表示・非表示状態をブラウザ内に保存し、閉じても状態を維持する

用意するファイル

同じフォルダに次を置く(icon.png は任意)。

  • manifest.json
  • content.js
  • popup.html / popup.js
  • icon.png(256×256 推奨。無ければ manifest の icons ごと削る)

manifest.json

下の JSON は manifest_version3 の Manifest V3 向け。ファイル名 manifest.json で保存する。

  • content_scriptsx.com / twitter.comcontent.js を注入
  • action.default_popup … ツールバーアイコン用の HTML
  • permissionsstorage … ポップアップと content で rtFilterEnabled を共有
{
  "name": "X リポスト非表示",
  "manifest_version": 3,
  "version": "1.0",
  "permissions": ["storage"],
  "content_scripts": [
    {
      "matches": ["https://twitter.com/*", "https://x.com/*"],
      "js": ["content.js"]
    }
  ],
  "action": {
    "default_popup": "popup.html"
  },
  "icons": {
    "256": "icon.png"
  }
}

content.js

detectRetweet の文字列は環境で変わる。日本語 UI 向けに「さんがリポスト」を足した例になっている。

(function () {
  'use strict';

  // ポップアップで保存した値。未設定なら false(リポストは隠さない)
  let isRtFilterEnabled = false;

  chrome.storage.local.get(['rtFilterEnabled'], (result) => {
    isRtFilterEnabled = result.rtFilterEnabled === true;
    applyVisibilityAll();
  });

  // ポップアップでスイッチを変えたあと、開いているタブにすぐ反映させる
  chrome.storage.onChanged.addListener((changes) => {
    if (changes.rtFilterEnabled) {
      isRtFilterEnabled = changes.rtFilterEnabled.newValue;
      applyVisibilityAll();
    }
  });

  // article 全体の text でリポスト用ラベルを探す。日本語 UI なら「さんがリポスト」など環境に合わせて増やす(誤爆に注意)
  function detectRetweet(article) {
    const t = article.textContent || '';
    return (
      t.includes('Reposted by') ||
      t.includes('Retweeted by') ||
      t.includes('reposted') ||
      t.includes('さんがリポスト')
    );
  }

  function processTweets() {
    const articles = document.querySelectorAll('article[data-testid="tweet"]');
    articles.forEach((article) => {
      // 一度判定したノードは dataset を使い回して表示だけ直す
      if (article.dataset.rtChecked === 'true') {
        updateArticleVisibility(article);
        return;
      }
      article.dataset.isRetweet = detectRetweet(article);
      article.dataset.rtChecked = 'true';
      updateArticleVisibility(article);
    });
  }

  function updateArticleVisibility(article) {
    const isRt = article.dataset.isRetweet === 'true';
    if (isRtFilterEnabled && isRt) {
      if (article.style.display !== 'none') article.style.display = 'none';
    } else if (article.style.display === 'none') {
      article.style.display = '';
    }
  }

  // OFF にしたとき隠していた行をまとめて戻す
  function applyVisibilityAll() {
    document.querySelectorAll('article[data-testid="tweet"]').forEach((article) => {
      updateArticleVisibility(article);
    });
  }

  // 無限スクロールで article が増えたら再スキャン
  const observer = new MutationObserver(() => processTweets());
  observer.observe(document.body, { childList: true, subtree: true });
})();

popup.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <style>
    /* ポップアップは横幅が狭いのでコンパクトに */
    body { width: 220px; padding: 12px; font-family: sans-serif; margin: 0; }
    .row { display: flex; align-items: center; justify-content: space-between; }
    .label { font-size: 14px; font-weight: bold; }
    /* チェックボックスを隠して span.slider をスイッチの見た目にする */
    .switch { position: relative; display: inline-block; width: 40px; height: 24px; }
    .switch input { opacity: 0; width: 0; height: 0; }
    .slider {
      position: absolute; cursor: pointer; inset: 0;
      background: #ccc; transition: 0.2s; border-radius: 24px;
    }
    .slider::before {
      position: absolute; content: ''; height: 16px; width: 16px;
      left: 4px; bottom: 4px; background: #fff; transition: 0.2s; border-radius: 50%;
    }
    input:checked + .slider { background: #1d9bf0; }
    input:checked + .slider::before { transform: translateX(16px); }
  </style>
</head>
<body>
  <!-- id は popup.js から getElementById するので変えない -->
  <div class="row">
    <span class="label">リポスト非表示</span>
    <label class="switch">
      <input type="checkbox" id="toggleRtFilter" />
      <span class="slider"></span>
    </label>
  </div>
  <script src="popup.js"></script>
</body>
</html>

popup.js

document.addEventListener('DOMContentLoaded', () => {
  const toggleRtFilter = document.getElementById('toggleRtFilter');
  // 前回の状態を復元(未保存なら OFF)
  chrome.storage.local.get(['rtFilterEnabled'], (result) => {
    toggleRtFilter.checked = result.rtFilterEnabled === true;
  });
  // 変更は即 storage に書く → content 側の onChanged で拾う
  toggleRtFilter.addEventListener('change', () => {
    chrome.storage.local.set({ rtFilterEnabled: toggleRtFilter.checked });
  });
});

インストール手順

  1. Chrome で chrome://extensions/ を開く
  2. 「デベロッパーモード」を ON
  3. 「パッケージ化されていない拡張機能を読み込む」で上記のファイルを配置したフォルダを指定

X を開き直す。拡張アイコンでポップアップを開き、スイッチの ON/OFF で表示を確かめる。

補足・限界