html Javascript プログラミング

Twitterを開いた時に「フォロー中」タブを自動クリックする拡張機能を作った

Twitterの仕様変更で「おすすめ」がデフォルトになった

2023年になって、Twitterの仕様がちょっと変わり、「おすすめ」表示がデフォルトになりました。

フォローしている人のツイートだけを時系列順で見るためには「フォロー中」をクリックする必要があります。

毎回クリックするのは面倒なので、できれば自動化したいところです。

すでにこういう機能を持った拡張機能はあるのですが、自作してみることにしました。

属性 role="tab" で探した要素をクリック

コード本体は以下のような感じです。

Chromeの検証でHTMLソースを見ると「おすすめ」「フォロー中」のタブにはrole="tab"という属性が付いていたのでそれを検索して、見つかった配列の2番目をクリックするだけです。

const tabs= document.querySelectorAll('[role="tab"]');
tabs[1].click();

このコードをこのままChromeのコンソールにペーストすれば切り替わります。

Chromeの機能拡張にする

Chrome機能拡張にするために、適当なフォルダを作ってその中にcontent_script.jsmanifest.jsonの2つのファイルを作ります。

manifest.jsonのmatchesにtwitterのホーム画面のURL("https://twitter.com/home")を書いておくと、ホーム画面にアクセスする度に自動的にスクリプトが動いてくれます。

manifest.json

{
  "name": "Twitter Autoselect Following Tab",
  "description": "when open https://twitter.com/home ,select following tab automatically ",
  "version": "0.2",
  "manifest_version": 3,
  "content_scripts": [
    {
      "matches": [
        "https://twitter.com/home"
      ],
      "js": [
        "content_script.js"
      ],
      "run_at": "document_end",
      "all_frames": true
    }
  ]
}

content_script.js

window.addEventListener("load", main, false);

function main() {
  const jsInitCheckTimer = setInterval(jsLoaded, 1000);
  function jsLoaded() {
    if (document.querySelector('[role="tab"]') != null) {
      clearInterval(jsInitCheckTimer);
      autoSelectFollowing();
    };
  }
}

function autoSelectFollowing() {
  const tabs = document.querySelectorAll('[role="tab"]');
  tabs[1].click();
};

Twitterのコンテンツは動的に生成されているので、単純に処理だけを書くと要素がロードされる前に実行されてしまい、エラーになってしまいます。

Chromeの機能拡張をページがロードされてから動かすための処理部分は、以下の記事から拝借しました。感謝。

Qiita:動的なページの読み込みが完了してからChrome拡張機能を実行する方法

拡張機能をChromeに登録

コードが用意できたら、Chromeの拡張機能画面 chrome://extensions/ でデベロッパーモードをONにして、フォルダをドラッグアンドドロップするか「パッケージ化されていない拡張機能を読み込む」で読み込みます。

あとはTwitterのホーム画面にアクセスすると「フォロー中」のタブが自動選択されます。

とりあえずちょっと快適になりました。

動かない時がある

ところがこのコード、なんか動かない時があります。

どうもTwitter内でtwitter/homeに遷移した時に動かない感じです。そのときでもリロードするとフォロー中タブに切り替わります。なんでしょうか?

とりあえずブラウザのブックマークをtwitter.comではなくtwitter/homeにすると最初のアクセスで失敗することはなくなりました。

どうやら、まだ改良が必要な感じです。

追記:Ajax対応にするには、MutationObserver版でDOMの変化をキャッチする

その後、いろいろ調べてみました。

上記のスクリプトがうまく動かないときがあるのは、どうやらTwitterがloadイベント発生せずにURLを変更しているらしいためでした。

ページ内のアイコンをクリックした場合などは、新しいコンテンツをAjaxで読み込んでURLはpushStateメソッドとかで変更しているのではないかと思います。

pushStateとかreplaceStateとかアドレスだけ変更するメソッドがあるなんて、初めて知りました。

どうしたものかとイロイロ調べるうちに、Ajaxによる遷移イベントを捉えるには、DOMの変更を監視するのが良いらしいということが分かりました。

使うのは MutationObserver.observe()というメソッドです。

https://developer.mozilla.org/ja/docs/Web/API/MutationObserver

https://developer.mozilla.org/ja/docs/Web/API/MutationObserver/observe

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe

// 監視対象の要素を取得。特定の要素に絞らずに"document"とかでもいい。
const elementToObserve = document.querySelector("#targetElementId");

// 新規MutationObserverオブジェクトを作成して、コールバック関数を設定する
const observer = new MutationObserver(function() {
    console.log('callback that runs when observer is triggered');
});

// 監視対象の要素とオプションで監視対象にするものを設定する
observer.observe(elementToObserve, { attributes: true,subtree: true, childList: true });
//アトリビュートフィルターの使い方
// https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe

function callback(mutationList) {
  mutationList.forEach((mutation) => {
    switch(mutation.type) {
      case "attributes":
        switch(mutation.attributeName) {
          case "status":
            userStatusChanged(mutation.target.username, mutation.target.status);
            break;
          case "username":
            usernameChanged(mutation.oldValue, mutation.target.username);
            break;
        }
        break;
    }
  });
}

const userListElement = document.querySelector("#userlist");

const observer = new MutationObserver(callback);
observer.observe(userListElement, {
  attributeFilter: [ "status", "username" ],
  attributeOldValue: true,
  subtree: true
});

Twitter内のページ遷移にも対応した改良版

そんなわけで改良版です。

observe()メソッドでDOMの変化をキャッチして、URLが/homeに変化したら切り替え用のタブ要素を探して、見つかったらクリックします。

本当はobserve()のオプションでattributeFilterを設定して、URLの変化だけを捉えようとしたのですがうまくイベント発火してくれませんでした。

なので、とりあえずあらゆるDOMの変化を捉えて、自前でURLの変化をチェックしています。

最初のバージョンと同様にsetIntervalでタブの検索とクリックを行っています。

Twitterでは、DOMの変更イベントは頻繁に発生します。

放っておくと大量のタイマーが同時に走ってしまうので、URLの変化があった最初の回だけsetIntervalを走らせるようにしています。

コードは以下のような感じです。

console.log("Twitter auto select following tab script launched");

let oldLocationPath = "";

function main() {
  if (oldLocationPath === "/home") return;
  oldLocationPath = "/home";
  const jsInitCheckTimer = setInterval(jsLoaded, 1000);
  function jsLoaded() {
    if (document.querySelector('[role="tab"]') != null) {
      clearInterval(jsInitCheckTimer);
      autoSelectFollowing();
    };
  }
}

function autoSelectFollowing() {
  const tabs = document.querySelectorAll('[role="tab"]');
  if (tabs[1].getAttribute("aria-selected")) {
    tabs[1].click();
    console.log("following selected.");
  }

};


const config = {
  attributes: true,
  childList: true,
  subtree: true,
  // attributeFilter:[location]   did not work
};

const callback = function (mutationsList, observer) {
  console.log("DOM Changed. check if url is twitter.com/home: " + location.pathname);
  if (location.pathname === "/home") {
    main();
  } else {
    oldLocationPath = location.pathname;
  };
};

const observer = new MutationObserver(callback);
observer.observe(document, config);

{
  "name": "Twitter Autoselect Following tab",
  "description": "when open https://twitter.com/home ,select following tab automatically ",
  "version": "0.4",
  "manifest_version": 3,
  "content_scripts": [
    {
      "matches": [
        "https://twitter.com/*"
      ],
      "js": [
        "content_script.js"
      ],
      "run_at": "document_end",
      "all_frames": true
    }
  ]
}

とりあえず、動いてるみたいです。

こういう問題を自力で解決すると楽しいですね。

あと、拡張機能の中身が100%分かっているというのは精神安定上良いです。

-html, Javascript, プログラミング