取りあえず何か創る

ソフトウェアエンジニアだけど仕事以外でも何か作りたくなったので、主にその記録のためにブログを開設しました。

既読のリンクを非表示にする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ページを操作できなかったため。