ページ内に含まれるRSSのURLを取得するChromeの拡張機能を公開してみた

# 概要

## Chrome拡張機能とは

Chromeの機能を拡張するアドオンプログラムです。
JavaScriptで作成可能で、開いているページ上で、画面の操作や補助など作業の効率化が図れます。

画面表示時に広告をブロックしてくれる**AdBlock**や、画面上の英文を変換してくれる**Google翻訳**などが有名かと思います。


## RSSとは

RSSとは、ニュースやブログなど各種のウェブサイトの更新情報を配信するための文書フォーマットの総称です。XML形式で配信され、大きく分けると`RSS1.0` `RSS2.0` `ATOM`のフォーマットがあります。

## 今回のやりたいこと

**RSS解析のために様々なサイトのRSSを取得**したいというのと、Chromeの拡張機能を作成してみて、**Chromeの拡張機能の開発手法**を理解したいというのが目的です。

そのため、画面表示時にRSSのURLがページ上に存在していた場合に、RSSのURLを表示してくれるChromeの拡張機能を作成します。

# 完成したもの

### Chrome拡張機能へのリンク

https://chrome.google.com/webstore/detail/feeduck/alfjohbjhjgkcmgecbhjcjhhkiphccne

### 実際の動作

RSSのURLがあった場合に、拡張機能のアヒルのアイコンにRSS数のバッチをつけて、クリックをするとリンク一覧が表示されます。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473097/32c2576d-8485-63c3-f616-0fa90ec906fe.gif)

(※開いたXMLの可読性がよくなる`JSONVue`というChromeの拡張機能を使用しています。使いやすいので愛用しています。皆さんもぜひ！)

# ディレクトリ構成

ディレクトリは以下のような構成にしました。
`manifest.json`や`icon`なども必要なのですが、今回は、HTMLとJavaScriptの説明のみのさせてください。

```
├ popup/
│ ├ popup.html
│ ├ popup.js
│ └ popup.css
├ content/
│ └ content.js
└ background/
  └ background.js
```


クロームの拡張機能の構造は、以下の3つに分かれており、私はこのような解釈で実装しております。
- popup : 拡張機能のアイコンをクリックすると表示されるウインドウを実装
- content : 表示されているページについての処理を実装
- background : タブや拡張機能のバッチなどブラウザ自体の機能やイベントを実装


# プログラム説明

簡単に作成したプログラムについて説明します。

### popupについて

拡張機能のアイコンをクリックすると表示されるウインドウを`jquery`や`bootstrap`を用いて実装しています。URLを別タブで開く処理ですが、Chromeの拡張機能ではHTMLのhrefではエラーになってしまうため、js側で開くような処理になっています。

<details><summary>popup.htmlの中身</summary>

```html:popup.html
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <script src="../lib/jquery/jquery.min.js"></script>
  <link rel="stylesheet" type="text/css" href="../lib/bootstrap/css/bootstrap.min.css">
  <link rel="stylesheet" type="text/css" href="popup.css">
</head>
<body>
  <div class="header">
    Feeduck
  </div>
  <div id="rss_url_list" class="container">
    <p class="not_find">RSSが見つかりませんでした</p>
  </div>
  <script src="popup.js"></script>
</body>
</html>
```
</details>

<details><summary>popup.cssの中身</summary>

```css:popup.css
.hide {
  display: none;
}
body {
  min-width:500px;
  padding: 5px;
}
.header {
  border-bottom: solid 1px gray;
  margin-bottom: 10px;
  font-size: 18px;
  text-align: center;
  padding: 6px;
  font-weight: bold;
}
.not_find {
  text-align: center;
  padding: 10px;
}
#rss_url_list {
  display: block;
  padding: 0 5px;
}
.rss_url_list {
  padding: 0 10px;
  margin-bottom: 5px;
}
```

</details>


<details><summary>popup.jsの中身</summary>

```javascript:popup.js
//タブの情報を取得
chrome.tabs.query({active: true, lastFocusedWindow: true}, function(tabs) {
    //現在のタブIDを取得
    var tabId = tabs[0]['id'];

    //現在のタブIDの情報のみ表示
    chrome.storage.local.get(String(tabId),function(urls){
        if ( urls[tabId].length !== 0 ) {
            //RSSが見つかりませんでしたの表示を削除する
            $('#rss_url_list').empty();

            //URLを表示する
            $.each( urls[tabId], function(i, url) {
                $('#rss_url_list').append('<div class="row rss_url_list"><input class="col form-control" type="text" value="'+ url +'" readonly><button data-link="'+ url +'" class="col-2 btn btn-primary LinkOpenBtn">開く</button></div>');
            });
        }
    });
});

// 読み込み完了後の処理
window.addEventListener('load', load, false);
function load(e) {
    var timer = setInterval(jsLoaded, 1000);
    function jsLoaded() {
        if (document.querySelector('.rss_url_list') != null) {
            // setInterval()解除
            clearInterval(timer);

            // フォーカスが当たったときに全選択にする
            $('.rss_url_list input').focus(function(){
                $(this).select();
            });

            // 開くボタンクリック時のイベント
            document.querySelectorAll('.LinkOpenBtn').forEach(function (button) {
                button.addEventListener('click', LinkOpen);
            });
        }
    }
}

// URLを開く
function LinkOpen() {
    window.open(this.getAttribute('data-link'));
}
```

</details>

### contentについて

今表示しているページで動作する処理を記載しています。
そのため、RSSのURLをスクレイピングしてくる処理がメインになります。

<details><summary>content.jsの中身</summary>

```javascript:content.js
$(function() {
    // background.jsにメッセージを送信
    chrome.runtime.sendMessage({rssUrls: rssUrlGet()});
});

//RSSをスクレイピングして取得
function rssUrlGet() {
    var rssUrls = [];

    $("link[type='application/rss+xml']").each(function(k,e){
        var href = $(e).attr('href');
        rssUrls.push( absolutePath(href) );
    });
    
    $("link[type='application/atom+xml']").each(function(k,e){
        var href = $(e).attr('href');
        rssUrls.push( absolutePath(href) );
    });

    //重複の削除
    rssUrls = rssUrls.filter( function(element, index) {
        return rssUrls.indexOf(element) === index;
    });
    return rssUrls;
}


// 相対パスを絶対パスに変換
function absolutePath(path) {
    var e = document.createElement('a');
    e.href = path;
    return e.href;
}
```
</details>

### backgroundについて

タブを切り替えたときや閉じたときの処理のイベント処理や、拡張機能アイコンに表示される数字バッチなど管理しています。

<details><summary>background.jsの中身</summary>

```javascript:background.js
// messageが送された時のイベント
chrome.runtime.onMessage.addListener(
    function(message, sender, callback) {
        // タブの情報を取得してバッチの表示処理を行う
        chrome.tabs.query({active: true, lastFocusedWindow: true}, function(tabs) {

            if (tabs[0]) {
                var tabId = tabs[0]['id'];
            } else {
                return;
            }

            //タブのバッチを初期化する
            chrome.action.setBadgeText({
                text: '',
                tabId: tabId
            });

            // バッチの表示
            if ( message.rssUrls.length == 0 || message.rssUrls.length == null ) {
                chrome.action.setBadgeText({
                    text: '',
                    tabId: tabId
                });
            } else {
                chrome.action.setBadgeText({
                    text: message.rssUrls.length + '',
                    tabId: tabId
                });
            }

            chrome.storage.local.set({[tabId]: message.rssUrls});
        });
    }
);

// タブ切り替え時のイベント
chrome.tabs.onActivated.addListener(function (activeInfo) {
    //保持しているカウントを取り出して表示する
    chrome.storage.local.get(String(activeInfo.tabId),function(urls){
        if ( urls[activeInfo.tabId] ) {
            if ( urls[activeInfo.tabId].length !== 0 ) {
                chrome.action.setBadgeText({
                    text: urls[activeInfo.tabId].length + '',
                    tabId: activeInfo.tabId
                });
            } else {
                chrome.action.setBadgeText({
                    text: '',
                    tabId: activeInfo.tabId
                });
            }
        } else {
            chrome.action.setBadgeText({
                text: '',
                tabId: activeInfo.tabId
            });
        }
    });
});

// タブを閉じたときのイベント
chrome.tabs.onRemoved.addListener(function(tabId, info) {
    chrome.storage.local.remove(String(tabId));
});
```
</details>

# まとめ

作成したChromeの拡張機能を紹介しました。
この拡張機能を用いることで、RSSを収集が可能となり、RSS解析が捗りそうです。
皆さんもぜひ使ってみてください。

あと、リリースする際に、ZIPファイルにまとめる必要がありますが、`jquery`や`Bootstrap`などのjsのライブラリの管理方法やリリース方法もある程度自動化することを確立できたので、いつか説明できたらと思います

今後も、Chromeの拡張機能を作ってみたいなと思います。
ここまで読んでいただきありがとうございました。