Tsubatoの発信記録

主に機械学習やデータサイエンス関連で学んだことを書いています。

既読のリンクを非表示にするchrome拡張機能を作る

0. この投稿の概要

  • マッチングアプリをやっている時に、一度見たプロフィールを一覧から非表示に出来れば何度も同じプロフィールを見ないで済むのではと考えました。
  • 既存のものが見つからなかったので、chrome拡張機能として実装しました。この投稿ではペアーズを対象にして実装例を紹介しています。(顔写真を隠すのが面倒なので、実行例は載せていません…)

1. 機能の概要

自分が実装した拡張機能は以下のような構成になっています。今回初めて拡張機能を作ったので、私の構成は最適ではないと思いますが参考まで。

  • google chrome拡張機能アイコンをクリックすることで機能が動きます。
  • まずbackground.jsがクリックイベントに反応して実行され、chromeの閲覧履歴から対象のURLを取得してlocal storageに保存、その後content.jsを実行します。*1
  • content.jsでアクティブなタブのhtmlから特定のclassを持つリンクを抽出し、それが閲覧履歴に含まれる場合はdisplayプロパティをnoneにすることで非表示にします。

2. 実装例

フォルダ構成

  • 以下の通りになっています。必要なコードは全部載せているので、コピーをして同様の構成を作り、chromeで読み込めば機能を使えます。読み込み方についてはchrome拡張機能のチュートリアルを参考にしてください。
  • イコン画像は何でもいいですが、私は同ページからダウンロードした画像をそのまま使っています。

.
├── background.js
├── content.js
├── images
│ ├── get_started128.png
│ ├── get_started16.png
│ ├── get_started32.png
│ └── get_started48.png
└── manifest.json

manifest.json

  • 拡張機能の設定ファイルみたいなもので、必須のファイルです。拡張機能の名前や呼び出すスクリプト、アイコン画像などを設定します。
  • 特に重要なのはpermissionです。ここで拡張機能がアクセスできる資源を指定しています。今回は閲覧履歴を取得するためのhistory、閲覧履歴を保存するためのstorage、webページを操作するためのactiveTab、スクリプトをページに追加するためのscriptingを許可しています。
{
  "name": "Hide read link",
  "description": "Hide links from web page if you have already read them.",
  "version": "1.0",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["storage", "activeTab", "scripting", "history"],
  "action": {
    "default_title": "Click",
    "default_icon": {
      "16": "/images/get_started16.png",
      "32": "/images/get_started32.png",
      "48": "/images/get_started48.png",
      "128": "/images/get_started128.png"
    }
  },
  "icons": {
    "16": "/images/get_started16.png",
    "32": "/images/get_started32.png",
    "48": "/images/get_started48.png",
    "128": "/images/get_started128.png"
  }
}

background.js

let microseconds3Days = 1000 * 60 * 60 * 24 * 3;
var threeDaysAgo = (new Date).getTime() - microseconds3Days;
// Execute when the icon is clicked.
chrome.action.onClicked.addListener((tab) => {
  storeHistory(tab);
});

function storeHistory(tab) {
  chrome.history.search({
    "text": "https://pairs.lv/user/profile/",
    "startTime": threeDaysAgo,
    "maxResults": 10000,
  },
  function(historyItems) {
    let historyList = [];
    for (let i = 0; i < historyItems.length; ++i) {
      let url = historyItems[i].url;
      let url_split = url.split("/");
      // Store only profile ID
      historyList.push(url_split[url_split.length-1]);
    };
    chrome.storage.local.set({ historyList });
    chrome.scripting.executeScript({
      target: {tabId: tab.id},
      files: ['content.js'],
    });
  });
};
  • 拡張機能のアイコンをクリックするとchrome.action.onClicked.addListenerで指定した関数が実行されます。
  • 今回は3日分のペアーズのプロフィール閲覧履歴をchrome.history.searchで取得しています。
  • URLは表示される場所によって変わるようなので、プロフィール固有と思われるIDをURLから抽出しています。そのリストをchrome.storage.local.setで保管しています。
  • 最後にchrome.scripting.executeScriptでcontent.jsをwebページに埋め込みます。

content.js

  • chrome.storage.local.getで保存しておいた履歴データを読み込む意外は普通のjavascriptコードです。対象リンクのクラスはchromeデベロッパーツールで確認しました。
function deleteReadLinks() {
  chrome.storage.local.get("historyList", ({ historyList }) => {
    let historySet = new Set(historyList)
    let profiles = document.getElementsByClassName("css-opde7s");
    for (let i = 0; i < profiles.length; ++i) {
      if ("href" in profiles[i]) {
        let url_split = profiles[i].href.split("/");
        if (historySet.has(url_split[url_split.length-1])) {
          profiles[i].style.display = "none";
        }
      }
    };
  });
}
deleteReadLinks();

3. 参考

*1:わざわざ別スクリプトを呼んでいるのは、何故かbackground.jsから直接webページを操作できなかったため。