2019年10月27日日曜日

Chrome のプラグインを作る! その4 本ブログのフィードを取得する

今日は近所のクリーンデーに参加した。
普段会話しなくても、諍いなく存在を肯定できるご近所さん。
平穏のありがたみを感じる。

***

「ブログを一週間更新していないと叱ってくれるプラグイン」を作るには、ブログのフィードを取得する必要がある。
前回は、XMLHttpRequestクラスとJSONクラスを用いて、ローカルのJSONファイルを読み込んで解析したが、今回は応用として、実際に本ブログのフィードを取得する。

ポイント

  • アイコンのクリックでHTMLのポップアップを表示するには、マニフェストのbrowser_action.default_popupに呼び出すHTMLを指定する
  • 拡張機能のJavaScriptから外部URLのソースを取得する際には、マニフェストのpermissionsにURLを追加するだけでよい(クロスドメインリクエストの問題に頭を抱える必要はない)
  • 「ブロガー支援プラグイン」として、クリックで一覧表示+記事へのリンクを表示する機能などを加えたら面白いかもしれない(感想)

開発・実行環境

ブラウザ Google Chrome
バージョン: 78.0.3904.70(Official Build) (64 ビット)
起動時オプション:
--flag-switches-begin --flag-switches-end

PC
プロセッサ: Intel(R) Core(TM) i5-5200U CPU @2.20GHz
メモリ: 8.00 GB
OS: Windows 8.1 (64bit)
compiler: Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x86
Visual Studio: 14.0

フォルダ構成

.
├── icon
│   └── icon.png
├── load.js
├── manifest.json
└── popup.html

popup.html は拡張機能アイコンを左クリックした際に呼ばれる。
load.js には popup.html の読み込み時に起動されるイベントハンドラを実装した。

コード

manifest.json

{
  "name": "Blogger feed JSON parser",
  "description" : "Base Level Extension",
  "manifest_version": 2,
  "version": "1.0",
  "permissions": [
    "http://*/*", "https://*/*"
  ],
  "browser_action": {
    "default_popup": "./popup.html"
  },
  "icons": {
    "16": "./icon/icon.png",
    "32": "./icon/icon.png",
    "48": "./icon/icon.png",
    "128": "./icon/icon.png"
  }
}
browser_action.default_popup にHTMLを指定する。
これにより、拡張機能アイコンを左クリックした際にpopup.html に記述されるポップアップを表示できる[1]。
permissionsに "http://*/*", "https://*/*" を指定することでCORSのエラーを回避できる[2][3]。

調べていて、拡張機能としてではなく、純粋にWEBページに組み込むJavaScriptからURLにアクセスする場合には、いろいろ大変そうだと思った[3]。
マニフェストを弄るだけでクロスドメインリクエストの問題に頭を抱えずに済むのだから、とてもありがたい。

popup.html

<!DOCTYPE html>
<html>
  <head>
    <meta charser="utf-8" />
    <title>Odonata Bug Hunt Blog</title>
    <!-- <link rel="manifest" href="/manifest.json"> -->
    <script type="text/javascript" src="load.js"></script>
  </head>
  <body>
    <div>About</div>
    title: <span id="id_title"></span><br />
    subtitle: <span id="id_subtitle"></span><br />
    authors: <span id="id_authors"></span><br />
    <br />
    <div>Posts</div>
    total posts: <span id="id_total_post"></span><br />
    <ul id="id_post_list"></ul>
  </body>
</html>
ブログのタイトル、サブタイトル、著者、投稿本数は数が決まっている項目なので、spanタグを指定する。
一方で、投稿本数は変化するので、各投稿の情報の表示にはulタグを利用する。

load.js

window.onload = function() {
  var xhr = createXMLHttpRequest();
  var url = 'https://mamorunoblog.blogspot.com/feeds/posts/default?alt=json&orderby=published';
  xhr.onreadystatechange = function() {
    switch (xhr.readyState) {
      case 4:  // DONE
        if (xhr.status != 200) {
          return;
        }
        console.log('xhr.readyState: ' + xhr.readyState);
        console.log('xhr.status: ' + xhr.status);
        console.log('xhr.responseText:\n' + xhr.responseText);

        // Parsing JSON.
        var data = JSON.parse(xhr.responseText);

        // title.
        var elem = document.getElementById('id_title');
        elem.innerText = data.feed.title.$t;

        // subtitle.
        var elem = document.getElementById('id_subtitle');
        elem.innerText = data.feed.subtitle.$t;

        // authors.
        var authors = '';
        var i = 0;
        for (i = 0; i < data.feed.author.length; i++) {
          authors = authors + data.feed.author[i].name.$t + '(' +
            data.feed.author[i].email.$t + '), ';
        }
        var elem = document.getElementById('id_authors');
        elem.innerText = authors;

        // total posts.
        var elem = document.getElementById('id_total_post');
        elem.innerText = data.feed.entry.length;

        // entry.
        var list = document.getElementById('id_post_list');
        while (list.firstChild) {
          // Clear last result.
          list.removeChild(list.firstChild);
        }
        var i = 0;
        var show_max = 10;
        for (i = 0; i < data.feed.entry.length; i++) {
          if (i > show_max) {
            var li = document.createElement('li');
            list.appendChild(li);
            var node = document.createTextNode('omitted more ' +
              (data.feed.entry.length - show_max) + 'posts.');
            li.appendChild(node);
            break;
          }

          // title.
          var li = document.createElement('li');
          list.appendChild(li);
          var node = document.createTextNode(data.feed.entry[i].title.$t);
          li.appendChild(node);

          // sublist.
          var subList = document.createElement('ul');
          li.appendChild(subList);

          // published
          var subLi = document.createElement('li');
          subList.appendChild(subLi);
          var node = document.createTextNode(
            'published: ' + data.feed.entry[i].published.$t);
          subLi.appendChild(node);

          // updated
          var subLi = document.createElement('li');
          subList.appendChild(subLi);
          var node = document.createTextNode(
            'updated: ' + data.feed.entry[i].updated.$t);
          subLi.appendChild(node);

          // authors.
          var authors = '';
          var j = 0;
          for (j = 0; j < data.feed.entry[i].author.length; j++) {
            authors = authors + data.feed.entry[i].author[j].name.$t + '(' +
              data.feed.entry[i].author[j].email.$t + '), ';
          }
          var node = document.createTextNode('authors: ' + authors);
          var subLi = document.createElement('li');
          subList.appendChild(subLi);
          subLi.appendChild(node);

          // categories
          var categories = '';
          var j = 0;
          for (j = 0; j < data.feed.entry[i].category.length; j++) {
            categories =
              categories + data.feed.entry[i].category[j].term + ', ';
          }
          var node = document.createTextNode('categories: ' + categories);
          var subLi = document.createElement('li');
          subList.appendChild(subLi);
          subLi.appendChild(node);
        }
        break;
      default:
        // none.
        break;
    }
  }
  xhr.open('GET', url);
  xhr.send();
}

function createXMLHttpRequest() {
  if (window.XMLHttpRequest) {
    return new XMLHttpRequest();
  }
  if (window.ActiveXObject) {
    try {
      return new ActiveXObject('Msxml2.XMLHTTP.6.0');
    } catch (e) {
      // none.
    }
    try {
      return new ActiveXObject('Msxml2.XMLHTTP.3.0');
    } catch (e) {
      // none.
    }
    try {
      return new ActiveXObject('Microsoft.XMLHTTP');
    } catch (e) {
      // none.
    }
  }
  return false;
}
フィードを取得しHTMLを更新するスクリプトである。
createXMLHttpRequest関数でXMLHttpRequestクラスを作成するところは前回と一緒だが、今度は引数に指定するURLがローカルのJSONから本ブログのフィードになった。
著者とカテゴリ、投稿はJSONに配列として記述されているため、ループで処理する。

実行結果


アイコン(星印)をクリックすると、ポップアップで本ブログの投稿情報が表示される。
これでフィードが取得できることが確かめられた!

改善点

投稿のタイトルに記事へのリンクを張れば実用的になるだろう。
ほんのテストで実装したにしては、便利ツールに化けそうだ。
私はブロガーとして過去の自分の投稿を参照することが結構あるので、ブックマークとは別に素早いジャンプ機構を持つことはよいことだ。
リンクを張るだけなら、簡単な改造で済むだろう。
問題は表示する情報の選択と、表示方法の最適化だな。

投稿期間の設定とか、表示件数の上限とか、設定を変えられるようにできたらいいな。
まあ、そういう欲を出すのはアルファ版ができてからでも遅くはない。

まとめ

XMLHttpRequestクラスとJSONクラスにより本ブログのフィードをJSON形式で取得し解析した。そして、記事の公開日といった情報を取得することに成功した。

感想

左クリックの機能は考えていなかったが、投稿の一覧表示+リンクはブロガーの大きな助けになるだろう。
漠然と「ブログを一週間更新していないと叱ってくれるプラグイン」を作りたいと考えてきたが、「ブロガー支援プラグイン」として、より便利なツールにしたいと考え始めている。

付録

特になし。

参考文献

  1. Google Chrome 拡張機能を開発する 〜 ポップアップを表示するまで 〜
  2. Chrome拡張でCORS対応
  3. クロス ドメイン リクエスト (Cross-Domain Requests, XDR) の問題を理解する
  4. What does “http://*/*”, “https://*/*” and “<all_urls>” mean in the context of Chrome extension's permissions

0 件のコメント:

コメントを投稿

コメント表示は承認制に設定しています