Fork me on GitHub

2009/09/02

はてな
こんな拡張が欲しい人なんて半ば病気ですよ。

夜になるとエアコン無しに過ごせる涼しい季節になって来ました。皆さん如何お過ごしでしょうか。

最近Google Chromeを使っているのですが、AutoPagerizeのGoogle Chrome拡張を入れてみて感動し、被はてなブックマーク数を画面下に表示する拡張が欲しくなったので、つい勢いで作ってしまいました。
はいはい。病気病気

スクリーンショットはこんな感じ
chrome-hbcount

os0xさんの記事をふんだんに参考にしながらなんとか作り上げました(os0x++)。
以下その作成手順。

まず、manifest.jsonを用意しました。
manifest.json
{
  "name": "hatena bookmark counter",
  "description": "hatena bookmark counter for google chrome. hatena bookmark is social bookmark web service. see 'http://b.hatena.ne.jp/'. this extension show image of numbers which how many users bookmarked the URL.",
  "version": "0.0.1",
  "icons": { "normal": "hbcount.gif" },
  "permissions": [ "tabs" ],
  "toolstrips": [ "hbcount.html" ],
  "content_scripts": [
    {
      "js": [ "hbcount.js" ],
      "matches": [ "http://*/*" ]
    }
  ]
}
今回の拡張は、ページがロードされた瞬間に発動するjavascriptと、Google Chrome起動中に画面下で常駐するためのtoolstripsと呼ばれるHTMLコンテンツを使いました。
まずユーザスクリプト側。
hbcount.js
if (window == top{
  var port = chrome.extension.connect();
  port.onMessage.addListener(function(data) {
    location.href = data.url;
  });
  port.postMessage({url: location.href});
}
ユーザスクリプト側からはtoolstripのコントロールに対してアクセス出来ませんのでpostMessageでメッセージを送信し被はてなブックマーク数アイコンを更新します。なおtoolstripにあるアイコンをクリックすると、はてなブックマークエントリページを開く様になっているのですが、toolstrip側から現在開いているタブのコンテンツにはアクセスする事も出来たのですが、少し危険を感じたのでpostMessageを使いtoolstrip側からユーザスクリプト側にURLを返信してlocation.hrefでの画面遷移をさせています。
真ん中のイベント待ちはその為の物です。なお、topかどうかを確認しているのは現在のURL以外でもこの拡張が走ってしまうのを防止している小細工です。例えばiframeなんかでアフィが表示されているとそれに対してもこの拡張が実行されてしまいます。

次に本体であるtoolstripのソースは以下の通り。
hbcount.html
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<script type="text/javascript">
window.onload = function() {
  var hbcount = document.getElementById("hbcount");
  chrome.extension.onConnect.addListener(function(port) {
    function update(url) {
      hbcount.src = 'hbcount.gif';
      hbcount.onclick = null;
      if (url.match(/^chrome:\/\//)) return;
      url = url.replace(/#/g, '%23');
      hbcount.src = 'http://b.hatena.ne.jp/entry/image/' + url;
      hbcount.onclick = function() { port.postMessage({'url': 'http://b.hatena.ne.jp/entry/' + url}); }
    }
    port.onMessage.addListener(function(data) { update(data.url); });
    chrome.tabs.onSelectionChanged.addListener(function(id, props) {
      chrome.tabs.get(id, function(tab) { update(tab.url); });
    });
  });
};
</script>
<img src="hbcount.gif" id="hbcount" style="cursor: pointer;" title="はてなブックマーク"/>
ユーザスクリプトからメッセージを受け取りupdate関数にて更新します。アイコンクリック時のメッセージ返信を行う処理もここにあります。
その下にある処理ではタブが切り替わったタイミングで現在の被ブックマーク数を更新する処理になります。この場合、いくらmanifest.jsonでhttp://のみと宣言していてもタブに対するイベント登録を行うのでchrome://といったURLも対象になってしまいます。update関数でURLを避けているのはその為です。

さて、ここから話がそれ始めます。
Google Chromeの拡張はchromeブラウザ自身でパッキングする事が出来ます。ただchromeへのパスをいちいち書いてられないのでMakefileを書きました。
Makefile
ifndef CHROME
ifneq ($(windir),)

# Windows
DEST = "$(shell pwd)\chrome-hbcount"
CHROME = "$(USERPROFILE)/Local Settings/Application Data/Google/Chrome/Application/chrome.exe"

else

# Other Platform: Linux? Mac?
DEST = $(shell pwd)/chrome-hbcount

CHROME = $(shell which crxmake)
ifeq ($(CHROME),)
CHROME = $(shell which google-chrome)
endif
ifeq ($(CHROME),)
CHROME = $(shell which chromium-browser)
endif
ifeq ($(CHROME),)
CHROME = chrome
endif
endif
endif

SRCS = hbcount.gif hbcount.html hbcount.js manifest.json

all : chrome-hbcount.crx

first : $(SRCS)
    @-rm -r $(DEST)
    @mkdir $(DEST)
    @cp $(SRCS) $(DEST)/.
    $(CHROME) --pack-extension=$(DEST)

chrome-hbcount.crx : $(SRCS)
    @-rm -r $(DEST)
    @mkdir $(DEST)
    @cp $(SRCS) $(DEST)/.
    $(CHROME) --pack-extension=$(DEST) --pack-extension-key=chrome-hbcount.pem

clean:
    @-rm *.crx
    @-rm -r $(DEST)
初回だけmake first、以降はmakeでビルドです。
WindowsでもLinuxでもMacでも使える様に、かつgoogle-chromeが入っていない場合にはchromium-browserを使う様になっています。ちなみにcrxmakeというのはConstellationさんが書いたchrome拡張のパッケージングツールです(Constellation++)。chromeを起動する事なくパッケージング出来ます。なおchrome-hbcountという部分を書き換えれば、他の拡張でも使えるかもしれません。

さらに拡張を書いていると拡張のバージョンがちょくちょくあがります。
Google Chrome(chromium-browser)の拡張にはupdate_urlという設定項目があり、決まった形のXMLファイルを用意しておけば自動アップデート出来る様になる予定があるそうなのですが、拡張を書き換える度にいちいちupdate.xmlを書き換えてアップロードするも面倒くさそうだったので、手順を自動化出来る様にしてみました。簡単には
  • manifest.jsonからバージョンを抜き出す
  • 抜き出したバージョンでupdate.xmlを更新する
  • githubのダウンロードページにあるファイルを一旦削除する
  • 拡張モジュール(crx)とupdate.xmlをアップロードする
という一連の流れをPerlスクリプトにしてみました。
dist-upload.pl
#!/usr/bin/perl

use strict;
use warnings;

use JSON;
use Perl6::Slurp;
use WWW::Mechanize;
use Net::GitHub::Upload;
use Config::Pit;

my $config = pit_get('github-upload', require => {
    'login' => 'your login id on github.com',
    'password' => 'your password on github.com',
});

my $manifest = from_json(slurp 'manifest.json');
my $id = $manifest->{id};
my $version = $manifest->{version};

open my $fh, '>update.xml';
print $fh <<EOF;
<gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
    <app appid="$id">
        <updatecheck codebase="http://cloud.github.com/downloads/mattn/chrome-hbcount/chrome-hbcount.crx" version="$version" />
    </app>
</gupdate>
EOF
close $fh;

my $mech = WWW::Mechanize->new;
$mech->get('https://github.com/login');
$mech->submit_form(
    form_number => 2,
    fields      => {
        login => $config->{login},
        password => $config->{password},
});

$mech->get('http://github.com/mattn/chrome-hbcount/downloads');
for my $form (@{$mech->forms}) {
    if ($form->action =~ /^http:\/\/github.com\/mattn\/chrome-hbcount\/downloads\//) {
        print "deleting ".$form->action."\n";
        $mech->request($form->click);
    }
}

chomp(my $user  = `git config github.user`);
chomp(my $token = `git config github.token`);
my $gh = Net::GitHub::Upload->new(
    login => $user,
    token => $token,
);
print "uploading chrome-hbcount.crx\n";
$gh->upload( repos => 'mattn/chrome-hbcount', file  => 'chrome-hbcount.crx' );
print "uploading update.xml\n";
$gh->upload( repos => 'mattn/chrome-hbcount', file  => 'update.xml' );
こちらもmattnとかchrome-hbcountという部分を書き換えれば、他の拡張で使えるかもしれません。ちなみにNet::GitHub::Uploadはtypesterさんのモジュールです(typester++)。
これで、あとは
  • 拡張を書き換える
  • manifest.jsonのバージョンを上げる
  • make
  • dist-upload.pl
という単純な手順で皆様に最新の拡張を自動ダウンロードして頂ける...予定です。まだupdate.xmlを使った自動アップデート機能は動いてないらしいです。


さて、はてなブックマークカウンタ拡張に話を戻して...
コードおよび拡張ファイルはgithubに置いてあります。Downloadのページからでもダウンロード出来ますが、以下のリンクでインストール出来る様になっています。
hatena bookmark counter for google chrome
リポジトリは以下のリンク先にあります。
mattn's chrome-hbcount at master - GitHu

hatena bookmark counter for google chrome

http://github.com/mattn/chrome-hbcount/tree/master
ライセンスはBSDにしましたので、どうぞお好きに使って下さい。流用してdelicious拡張なんてのもいいですね。

2009/08/05

はてな
ReverseHttp面白いですね。
ReverseHttp

Tunnel HTTP over HTTP, in a structured, controllable, securable way. Let programs claim part of URL space, and serve HTTP, all by using an ordinary HTTP client library.

http://www.reversehttp.net/
ただ勘違いされやすいのが「何がReverseなの」という部分。通常ブラウザからリクエストが送信され、それに対する応答がサーバから返されます。ReverseHttpはサーバで何かアクションが起きた場合に、ブラウザ側がその通知を受信する...なんて事が出来るプロトコルです。仕組みはcometというlong pollに似た仕組みで、サイトのdemoを観るとなんなく理解出来るかと思います。
例えば何が出来るのか...

ローカルPC内で動作するファイアウォール内のwebアプリを外部に公開する

rubyにhookoutというライブラリがあり、これを使用するとrackアプリがさも外部に公開されているかの様に振舞う事が出来ます。
paulj's hookout at master - GitHub

Expose Ruby applications to the web via ReverseHTTP

http://github.com/paulj/hookout/tree/master
グローバルIPが無くても、webアプリを公開出来るなんて素晴らしい!
なお、ReverseHttpはプロトコルですのでhookout以外にも同様のソフトウェアはあります。例えばmiyagawaさんが書いたAnyEvent::ReverseHttpに含まれるeg/proxy.plを使うとローカルPC内のwebアプリを外部に公開する事が出来ます。

外部で起きたアクションをローカルPCに通知させる

例えば、はてなブックマークで自分のサイトがブックマークされた瞬間にデスクトップPCが反応したらどうしますか?
スターを付けに行きませんか(笑)?ReverseHttpを使えば出来るのです。


今日はこの「はてなブックマーク通知」をやってみたいと思います。
使う材料は以下の通り。
  • hookout : 上記で紹介したrackアプリを公開するライブラリ
  • sinatra : ruby製webアプリケーションフレームワーク
  • ruby_gntp : snakaさん作のruby用Growl For Windowsインタフェース
こんだけ。
上記のサイトからhookoutを取得してインストールし、以下のsinatraアプリケーションを作成します。
my-hatebu-growler.rb
require 'rubygems'
require 'sinatra'
require 'ruby_gntp'
  
growl = GNTP.new
growl.register({
  :app_name => "はてブ",
  :notifies => [{
   :name     => "hatenabookmark",
    :enabled  => true,
  }]
})

post '/' do
  return "ng" if params[:status] !~ /add|update/
  user = params[:username]
  text = "#{params[:comment]}\r\r#{params[:title]}\r#{params[:url]}"
  icon = "http://www.hatena.ne.jp/users/#{user[0,2]}/#{user}/profile.gif"
  p params[:status]
  growl.notify({
    :name  => "hatenabookmark",
    :title => user,
    :text  => text,
    :icon  => icon,
  })
  'ok'
end
config.ru
require 'my-hatebu-growler'
set :run, false
run Sinatra::Application
これを以下の様に起動します。
hookout -a http:/www.reversehttp.net/reversehttp -n my-hatebu-growler-application -R config.ru start

my-hatebu-growler-applicationの部分は適当な物に変えて下さい。

起動すると以下の様に出力されます。
Bound to location http://my-hatebu-growler-application.www.reversehttp.net/
このURLを、はてなブックマークにwebhook登録します。
hatebu-webhook
あとは、じっとブクマされるのを待ちます。










hatebu-growler
デタ━━━゚(∀)゚━━━!!

秋の夜長に、こんなツールお一つどうでしょうか。


追記1
HTTP::Engine::Interface::ReverseHTTPもあるよとmiyagwawaさんに教えてもらいました。
hookout for HTTP::Engineらしいです。

追記2
例では分かり易くする為にwebhook APIのキー認証を省いていますが、本当はちゃんと判定する必要があります。

追記3
PerlでHTTP::Engine::Interface::ReverseHTTPを使ってみた。ネットワークGrowlにはアイコンが使える仕組みがないのが残念。

2009/08/01

はてな
普段はあまりブラウザを立ち上げてなくて、vimでコーディングしてる時なんかに調べたい事があればw3mを使っています。
ただ良いコンテンツを見つけて、「うむ。はてブしたい!」と思った時にイチイチFirefox起動するのも面倒臭かったので、w3mから直接ブックマーク出来る様にした。
まずは、perlからはてなブックマークする仕組み。これはAtom APIを使った単純な物。
#!/usr/bin/perl
use warnings;
use strict;

use Config::Pit;
use XML::Atom::Client;
use XML::Atom::Entry;
use XML::Atom::Link;

my $url = shift;
print "\n$url\n";
print "はてなブックマーク: ";
my $summary = <STDIN>;
my $config  = pit_get( "b.hatena.ne.jp", require => {
  "username" => "your username in b.hatena.ne.jp",
  "password" => "your password in b.hatena.ne.jp",
});

my $entry = XML::Atom::Entry->new;
my $link  = XML::Atom::Link->new;
$link->rel('related');
$link->type('text/html');
$link->href($url);
$entry->add_link($link);
$entry->summary($summary);

my $client = XML::Atom::Client->new;
$client->username( $config->{username} );
$client->password( $config->{password} );
my $edit_uri = $client->createEntry( "http://b.hatena.ne.jp/atom/post", $entry )
  or warn $client->errstr;
Config::Pitを使ってる所と、はてブコメントを入力している部分がちょっと違うだけ。
このスクリプト "hb.pl" を $HOME/.w3m/hb.pl として置き、$HOME/.w3m/keymap に以下の行を追加する。
keymap m EXTERN "/usr/bin/env perl ~/.w3m/hb.pl '%s'"
これでw3mから"m"をタイプすれば、コメント入力が表示され
http://mattn.kaoriya.net/
はてなブックマーク: [これはすごい]よーわからんけどね!
確定させればブックマーク出来る。中止は CTRL-C ね。

快適快適。

2009/04/02

はてな
追記
vimperatorrcに追加するスクリプトを少し修正する必要があります。詳しくはココを参照。

こうするといいよ。

add to your .vimperatorrc
javascript if (typeof hBookmark) liberator.loadScript('chrome://hatenabookmark/content/vimperator/plugin/hatenabookmark.js', {__proto__: this});
javascript if (typeof hBookmark != 'undefined') liberator.loadScript('chrome://hatenabookmark/content/vimperator/plugin/hatenabookmark.js', {__proto__: this});

and try
:hbsearch はてな
:hbtabsearch はてな
id:hazime2914さんに間違いを指摘して頂きました。正しくはhbtagsearchではなくhbtabsearchです。
はてなブックマーク Firefox 拡張のベータテストを開始します - はてなブックマーク日記 - 機能変更、お知らせなど