📰

Chrome拡張のRSSの検出方法とロジック

2022/05/21に公開

今回紹介する内容

自作しているRSSリーダに搭載している機能で閲覧したページでのRSSをどのように検出しているかをご紹介します。

https://zenn.dev/harurow/articles/812dabf395797f

Chrome拡張について

自作しているRSSリーダはChome拡張で作っています。Chrome拡張はHTML, CSS, JavaScriptを使ってChromeに機能を追加する仕組み[1]です。
作成するにはGoogleさんが公開している仕様に従って作る必要があるのですが、他の方々がが分かりやすく記事を書いてくださっているので、この記事では省略いたします。
自分で試したいだけであれば「拡張機能」タブの「パッケージ化されていない拡張機能を読み込む」で自分のChromeを拡張できます。
ご興味がある方は是非お試ししてみてはいかがでしょうか。

あらすじ

処理のあらすじです。

  1. コンテンツスクリプトでRSSを検索
  2. サービスワーカーへRSS候補を送る
  3. サービスワーカーでRSS候補を受け取る
  4. RSSを検出したタブにバッチをつける

これでローカルストレージへRSSの候補が保存されます。

1.コンテンツスクリプトでRSSを検索

コンテンツスクリプトとは

検出にはコンテンツスクリプトという機能を利用しています。この機能は指定したパターンに一致するURLのページを開いたときにDOMにアクセスできるプロセスでJavaScriptを実行することができます。この機能を利用するにはマニフェストで設定する必要があります。

コンテンツスクリプトを利用するためのマニフェストの設定方法

今回の処理では全てのURLに対して動作[2]
JavaScriptではjQueryを事前に読み込ませて自分の処理で利用しています。

manifest.json
  ... 略 ...

  "content_scripts": [
    {
      "matches": ["<all_urls>"],      // 全てのURLを指定
      "js": [
        "third-party/jquery.min.js",  // 同梱しているjQueryを利用
        "content-script.js"           // コンテンツスクリプト本体
      ]
    }
  ],

... 略 ...

コンテンツスクリプト本体

コンテンツスクリプトでは実行直後にjQueryを利用して

  • XMLビュワーか判断 👉 XMLビュワーならそのまま中まで判断
  • そうでない場合は、aタグを検索しRSSとなりそうなhrefを検索

XMLビュワーか判断

XMLビュワーかの判断は以下

content-scrpit.js
$(function () {
  /// スタイルがxml-viewer-styleか
  const headCheck = $('html head style[id="xml-viewer-style"]')

  if (headCheck.length > 0) {
    // xmlと判断する。
    // id='webkit-xml-viewer-source-xml' がxml本体
    const xml = $('html body div:first[id="webkit-xml-viewer-source-xml"]')
    const rss = analyze(xml) // xmlがRSSフォーマットか確認

XMLがRSSかの判断

RSSかどうかの判断はXMLの最初のタグ名で判断

content-script.js
function analyze (xml) {
    const root = xml.find(':first')[0]
    const tagName = root.tagName

    switch (tagName) {
      case 'rdf:RDF':
        return {
          rss: 'RSS1.0',
        }
      case 'rss':
        return {
          rss: 'RSS2.0',
        }
      case 'feed':
        return {
          rss: 'ATOM',
        }
    }
    return { rss: undefined }
  }

XMLビュワーでなければ、aタグを探す

Webページでは開いた後にJavaScriptを使ってDOMの操作をする恐れがあります。
それを考慮してページを開いてから3秒待ち、その後aタグを検索します。

aタグを見つけたら

href属性を確認し指定されているURLが以下の場合にRSSの候補としています。

  • クエリ文字を排除したURLの終わりが次のいずれかであればRSSの候補とする。
  • .rss, .rdf, .atom, .feed, .xml, /rss, /rdf, /atom, /feed, /xml
content-script.js
  setTimeout(() => { // 3秒待つ
    // aタグのリンク先が .rss .rdf .atom .feed .xml /rss /rdf /atom /feed /xml だった場合に候補とする
    // 上記の候補で終わるURLであるか直後にクエリ文字列ある場合に対応
    const regex = /(\.|\/)(rss|rdf|atom|feed|xml)([?].+)?$/i
    const urls = Array.from(new Set($(`
      a[href*=".rss"],
      a[href*=".rdf"],
      a[href*=".atom"],
      a[href*=".feed"],
      a[href*=".xml"],
      a[href*="/rss"],
      a[href*="/rdf"],
      a[href*="/atom"],
      a[href*="/feed"],
      a[href*="/xml"]`)
      .map((_, i) => $(i).prop('href'))
      .filter((_, i) => i.match(regex))
    ))

......
  }, 3000)  // 3000=3秒

2.サービスワーカーへRSS候補を送る

RSSの候補を見つけたらバックグラウンドのサービスワーカーへメッセージとしてRSSのURLリストを送ります。

content-script.js
chrome.runtime.sendMessage(
  chrome.runtime.id,
  {
    type: 'detected-rss',
    data: {
      urls // 検出したURLリスト
    }
  }
)

3.サービスワーカーでRSS候補を受け取る

そもそもサービスワーカーって?

バックグラウンドで動作するJavaScriptです。DOMがない環境で動作します。
コンテンツスクリプト等はタブごとに複数立ち上がり動作しますが、これは唯一のプロセスとして動作します。コンテンツスクリプトとの通信はメッセージを送りあってやりとりします。こちらもマニフェストで設定することで利用できます。

サービスワーカーを利用するためのマニフェストの設定

サービスワーカーは以下のマニフェストで動作定義します。
なおサービスワーカーのJavaScriptファイルはマニフェストファイルと同じフォルダに含める必要があるそうです。

manifest.json
  ... 略 ...

  "background": {
    "service_worker": "background.js"
  }

... 略 ...

バックグラウンドのロジック

メッセージのリスナを登録します。メッセージは汎用性がありRSSの検出以外の用途にも使います。
ですのでこのアプリでは必ずtypeにそのメッセージがなんなのか、dataにそのデータを入れるようにしています。

background.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'detected-rss') { // RSSの候補リスト
    const rssUrls = message.data.urls
    // ...略...
    // urls を 検出時間を追加してローカルストレージへ保存
    // ...略...
    sendResponse?.() // コールバックがあれば呼び出しておく
    return true
  }
  return false
})

4.RSSを検出したタブにバッチをつける

コンテンツスクリプト本体では自身にバッチをつけることができません。サービスワーカーにつけてもらいます。
さっきのbackground.jsのソースの続きでバッチをつけるロジックを追加します。

background.js
    // ...略...

    // 999件を超える場合は '999+' と表示、それ以下は数値そのまま
    chrome.action.setBadgeText({
      text: `${message.data.urls.length > 999 ? '999+' : message.data.urls.length}`,
      tabId: sender.tab.id
    })

// ...略...

最後に

RSSの検出方法とロジックのご紹介でした。他にも時間があればChrome拡張の記事を書いていこうと思います。

脚注
  1. 今はChromeだけでなくEdgeでも利用できます。 ↩︎

  2. URLの指定するパターンはこちらで確認することができます ↩︎

Discussion