Categoryperl

EmacsでPerlのモジュール名を動的に補完する

追記: id:rubikitchさんに、auto-revert-modeってのを教えていただきました!素晴しい!!!1

/path/to/pmlistをauto-revert-modeした状態にしておけばcronの更新が反映されるはず。

そこで(global-)?auto-revert-modeですよ – (rubikitch loves (Emacs Ruby CUI Books))

というわけで、そっちを使うように書き直した。

EmacsでPerlのモジュール名を補完する – subpop – subtech」で書いたネタだったりするのだけど、ともあれ、Perlモジュール名を補完(dabbrev)したいよー的な要求について。上記リンク先で書いたのは、cperl-mode初回起動時にコマンドを流していて、すごく重くていらいらしてたので、あらかじめモジュールの一覧だけのファイルを作るようにした。依然として、アホっぽいけど。

下記のようなてけとースクリプトを用意し、cronで定期的に回す。

# make_pmlist.sh
# 0 * * * * /path/to/make_pmlist.sh /path/to/pmlist
find `perl -e 'print join(q{ }, grep {-e $_} @INC);'` -name '*.pm' -type f |
xargs egrep -h -o 'package [a-zA-Z0-9:]+;' |
perl -nle 's/package\s+(.+);/$1/; print' |
sort |
uniq > $1

んでもって、Emacsの設定ファイルにこんな感じで追記。

(defvar perl-pmlist-file-name "/path/to/pmlist")
(defvar perl-pmlist-buffer-name "*PerlModules*")
(defvar perl-pmlist-make-command (concat "/path/to/make_pmlist.sh"
" "
perl-pmlist-file-name))
(defun perl-make-pmlist-buffer ()
(interactive)
(save-excursion
(when (interactive-p)
(shell-command perl-pmlist-make-command))
(unless (get-buffer perl-pmlist-buffer-name)
(find-file perl-pmlist-file-name)
(rename-buffer perl-pmlist-buffer-name)
(auto-revert-mode t))))
(add-hook 'cperl-mode-hook 'perl-make-pmlist-buffer)

これで、cperl-mode初回起動時に、あらかじめ作っておいたPerlモジュール一覧を読み込んだバッファを作成しておくので、dabbrevの候補に上がってくる感じ。んでもって、一覧がcronで更新されたら、auto-revert-modeによりEmacsのバッファ上も自動的に反映される。モジュールの新規インストール後に、cronによる更新をを待たずに手動で反映させるには、M-x perl-make-pmlist-bufferを実行すればよい(これはちょっと時間がかかる)。

一覧をあらかじめ作っておくってのがすごくアレな感じだが、まぁないよりはマシかなぁとは思われる。もっとかっけー方法があったら教えてください><

WWW::HatenaDiary 0.01リリース

はてなダイアリー/はてなグループCRUDを行うモジュール、WWW::HatenaDiaryをCPANへリリースしました。

id:tokuhiromさんがCodeReposで始めたものに、僕があれこれごしゃごしゃくっつけてったってな感じのものです。今後、ちょっとあれこれしてみたいと思ったりしてます。

はてなダイアラーならご存知の通り、はてなダイアリーは元々、エントリ単位というよりもむしろ、日付け単位でエントリを書き綴っていくというスタイルです。WWW::HatenaDiaryでは、そのどちらの単位でもCRUDを行えるような感じにしてみました。

使い方は、以下のような感じで。詳細については、PODをご覧ください。また、バグや改善した方がいい点等ありましたら、是非ともCodeReposでお願いします。

オブジェクト生成 & ログイン

use WWW::HatenaDiary;
my $diary = WWW::HatenaDiary->new({
username => $username,
password => $password,
group    => $group,
mech_opt => {
timeout    => $timeout,
cookie_jar => HTTP::Cookies->new(...),
},
});
if (!$diary->is_loggedin) {
$diary->login({
username => $username,
password => $password,
});
}

validなcookieがあれば、newメソッドのmech_optにわたすだけで、newメソッドのその他のオプション、および、loginは省略可能です。

Create

my $edit_uri = $diary->create({
title => $title,
body  => $body,
});
$diary->create_day({
date  => $date,
title => $title,
body  => $body,
});

エントリ単位でのCRUDについては、createメソッドで返される$edit_uriによって、エントリの取得・編集・削除を行います。

Retrieve

my $post = $diary->retrieve({
uri  => $edit_uri,
})
my $day  = $diary->retrieve_day({
date => $date,
});

いずれのメソッドについても、ポストの内容がハッシュリファレンスで返されます。

Update

$edit_uri = $diary->update({
uri   => $edit_uri,
title => $new_title,
body  => $new_body,
});
$diary->update_day({
date  => $date,
title => $new_title,
body  => $new_body,
});

エントリ、または、指定された日付の日記を更新。

Delete

$diary->delete({
uri => $edit_uri,
});
$diary->delete_day({,
date => $date,
});

削除しちゃいます。さようならー。

どうぞご利用ください。

スクリプトでメールを送信して、はてなダイアリを更新する

はてなダイアリー日記 – パソコンのメールからも更新が可能になりました」というわけで、メールではてなダイアリを更新できるようになった。そこで、以下のようなスクリプトを書いて、実験してみた。
結果、普通に更新できた!当然、はてな記法もちゃんと適用されている。また、以下のスクリプトでは、試しに画像を2枚添付して送信してみたが、最初の一枚だけが掲載されるようであった。

はてなダイアリー日記 – メール投稿で複数画像のアップロードに対応しました」というわけで、複数の添付画像に対応したとのこと。

ともあれこれで、Plaggerのはてなダイアリへのpublish用プラグインとか、作れちゃいそうである。

#!/usr/bin/perl
use strict;
use warnings;
use MIME::Lite;
my $msg = MIME::Lite->new(
From => 'test@example.com',
To   => '秘密のアドレス',
Subject => 'てすとだぉ',
Data => <<'__BODY__',
本文始まりだぉ。
-りすと1だぉ
-りすと2だぉ
>>
引用だぉ
<<
本文終了だぉ。
__BODY__
);
# スクリプトと同じ階層に、事前にtest1.jpg, test2.jpgを置く
for (qw(test1.jpg test2.jpg)) {
$msg->attach(
Type     => 'image/jpeg',
Path     => $_,
Filename => $_,
);
}
$msg->send;

HTML::WidgetValidatorのプラグインを簡単に作成する

HTML::WidgetValidatorのプラグインを作成した」にて述べた通り、はてなダイアリーブログパーツ対応を可能にするモジュールであるところのHTML::WidgetValidatorのプラグインを作ったりした。今後、これを増やして行くに際して、プラグインを一個ずつちまちま作成するのは、非常に面倒である。ブログパーツは多数あれど、やるべきことは大して変わらないので、パーツのコード断片の入力さえあれば、作業をある程度までは自動化できるはずだ。
というわけで、plugin-start.plというスクリプトを書いた。スクリプトの、あまりにもてきとーな書きぶりについては、特に触れないことにする。

ここでは、Wassrを例として取り上げ、その対応プラグインを作成する。また、HTML::WidgetValidatorはすでにインストールされているものとする。
まず、以下のような書式でもって記述されたYAMLファイルを作成する。

---
author_name: プラグイン作者の名前
author_mail: プラグイン作者のメールアドレス
widgets:
- module_name: プラグインのモジュール名(モジュール名.pmの「モジュール名」の部分)
widget_code: ブログパーツのコード断片
widget_name: ブログパーツの名前
widget_url: ブログパーツ提供元のURL
- module_name: プラグインのモジュール名(モジュール名.pmの「モジュール名」の部分)
widget_code: ブログパーツのコード断片
widget_name: ブログパーツの名前
widget_url: ブログパーツ提供元のURL
...

ここでは、Wassrブログパーツ対応プラグインを作成するので、以下のようにファイルに書き込む。

---
author_name: Kentaro Kuribayashi
author_mail: kentaro at cpan.org
widgets:
- module_name: Wassr
widget_code: |
<script type="text/javascript" src="http://wassr.jp/js/WassrBlogParts.js"></script><script type="text/javascript">wassr_host = "wassr.jp";wassr_userid = "kentaro";wassr_defaultview = "";wassr_bgcolor="";wassr_titlecolor="";wassr_textcolor="";wassr_boxcolor="";WassrFlashBlogParts();</script>
widget_name: Wassr
widget_url: http://wassr.jp/

これをwidget_info.ymlと名前を付けて保存し、以下の通り、plugin-start.plを実行する。

$ ./plugin-start.pl widget_info.yml
Creating directory lib/HTML/WidgetValidator/Widget
mkdir lib
mkdir lib/HTML
mkdir lib/HTML/WidgetValidator
mkdir lib/HTML/WidgetValidator/Widget
Creating lib/HTML/WidgetValidator/Widget/Wassr.pm
Creating directory t
mkdir t
Creating t/wassr.t

スクリプトを実行したディレクトリ直下のlibディレクトリ以下にプラグインが、tディレクトリ以下にテストが作成される。
次に、以下のようにして、テストを実行する。

$ prove -lv t/wassr.t
t/wassr....ok 1 - Wassr script - test1
ok 2 - Wassr script - test2
1..2
ok
All tests successful.
Files=1, Tests=2,  0 wallclock secs ( 0.04 cusr +  0.17 csys =  0.21 CPU)

シンプルなブログパーツなら、まずはだいたいテストが通るものと思われる(通らなくても、あとで修正するので問題ない)。生成されるテストは以下の通り。
wassr.t

use strict;
use Test::Base;
use HTML::WidgetValidator;
sub validate {
my $validator = HTML::WidgetValidator->new(widgets => [ 'Wassr' ]);
my $result = $validator->validate(shift);
return $result ? $result->name : ' ';
}
filters {
input    => [qw/chomp validate/],
expected => [qw/chomp/],
};
__END__
=== Wassr script - test1
--- input
<script type="text/javascript" src="http://wassr.jp/js/WassrBlogParts.js"></script>
--- expected
Wassr
=== Wassr script - test2
--- input
<script type="text/javascript">wassr_host = "wassr.jp";wassr_userid = "kentaro";wassr_defaultview = "";wassr_bgcolor="";wassr_titlecolor="";wassr_textcolor="";wassr_boxcolor="";WassrFlashBlogParts();</script>
--- expected
Wassr

次に、生成されたひな形を元に、実際にプラグインを作成する。とはいえ、コード断片からデータ構造が大体抽出されているはずなので、必要な箇所を適宜手直しするだけで済むはずだ。たとえば、Wassrブログパーツの場合、ブログパーツを設置するユーザがテキスト色や背景色等を変更できるので、変更され得る部分については、正規表現でもって記述することになる。今回、変更が必要になるのはその部分のみである。
修正を終え、完成したプラグインは以下の通り。
Wassr.pm

package HTML::WidgetValidator::Widget::Wassr;
use strict;
use warnings;
use base qw(HTML::WidgetValidator::Widget);
our $VERSION = '0.01';
__PACKAGE__->name('Wassr');
__PACKAGE__->url('http://wassr.jp/');
__PACKAGE__->models([
[
{
"name" => "script",
"type" => "start",
"attr" => {
"src" => "http://wassr.jp/js/WassrBlogParts.js",
"type" => "text/javascript"
}
},
{
"name" => "script",
"type" => "end"
}
],
[
{
"name" => "script",
"type" => "start",
"attr" => {
"type" => "text/javascript"
}
},
{
"text" => qr{\s*(?:wassr_host\s*=\s*"wassr\.jp";\s*|\s*wassr_userid\s*=\s*"\w+";\s*|\s*wassr_defaultview\s*=\s*"(?:user|friends|public|)";\s*|\s*wassr_bgcolor\s*=\s*"(?:[0-9A-Fa-f]{6}|)";\s*|\s*wassr_titlecolor\s*=\s*"(?:[0-9A-Fa-f]{6}|)";\s*|\s*wassr_textcolor\s*=\s*"(?:[0-9A-Fa-f]{6}|)";\s*|\s*wassr_boxcolor\s*=\s*"(?:[0-9A-Fa-f]{6}|)";\s*|WassrFlashBlogParts\(\);\s*)+\s*},
"type" => "text"
},
{
"name" => "script",
"type" => "end"
}
]
]);
1;

ユーザ入力による色等の設定や、コード自体の整形が行われた場合に備え、テストにたとえば以下のようなコード断片を追記する。

=== Wassr script - test3
--- input
<script type="text/javascript">
wassr_host = "wassr.jp";
wassr_userid = "kentaro";
wassr_defaultview = "user";
wassr_bgcolor="FDFAFA";
wassr_titlecolor="C1B3B3";
wassr_textcolor="5D5555";
wassr_boxcolor="F8F5F5";
WassrFlashBlogParts();
</script>
--- expected
Wassr

プラグインの動作を確認するために、再度、テストを実行する。

$ prove -lv t/wassr.t
t/wassr....ok 1 - Wassr script - test1
ok 2 - Wassr script - test2
ok 3 - Wassr script - test3
1..3
ok
All tests successful.
Files=1, Tests=3,  0 wallclock secs ( 0.06 cusr +  0.12 csys =  0.18 CPU)

以上により、Wassrブログパーツに対応したHTML::WidgetValidatorプラグインが作成できた。他のブログパーツに関しても、あまり複雑なものでないならば、同じようにして簡単にプラグインを作成することができるだろう。その際は、先に作成したwidget_info.ymlに、新たなブログパーツに関する情報を追記し、plugin-start.plを実行することになる。

HTML::WidgetValidatorのプラグインを作成した

はてなダイアリのウィジェットブログパーツ)対応については、予告通り着々と進行しているところだが、それらウィジェットの安全な使用を可能にする仕組みについても、「はてなダイアリー日記 – はてなダイアリーで設置可能なブログパーツを判定するPerlモジュールをCPANにて公開しました」にて発表された通り、HTML::WidgetValidatorなるモジュールがCPANにて公開された。

はてなダイアリーに貼り付け可能なブログパーツの判別に利用しているPerlモジュール「HTML::WidgetValidator」をCPANにアップロードして公開しました。

はてなダイアリーで設置可能なブログパーツを判定するPerlモジュールをCPANにて公開しました – はてなダイアリー日記

このモジュールは、各ウィジェットへの対応を、プラグインを追加することで増やしてしていくことができ、また、以下の通り、開発への参加がよびかけられている。

このモジュールの対応ブログパーツの追加に興味をもたれた開発者で、新たなブログパーツ判別のためのコードを書いていただけるという方は、ぜひ作成したコードをsupport☆hatena.ne.jp(引用者註:@を☆に変更)までお送りください。お送りいただいたコードは、可能な限りHTML::WidgetValidatorに取り込ませていただきたいと考えております。

はてなダイアリーで設置可能なブログパーツを判定するPerlモジュールをCPANにて公開しました – はてなダイアリー日記

激烈に素晴しいことこの上ないこの試みに対し、僭越ながらこの僕も是非とも協力させていただきたいと思い、プラグインを作成してみた。対応したブログパーツは、先日、ブロゴスフィアに激甚な衝撃をもたらした、あのISTブログパーツである。ご存知ない方は、実際にそれが適用されたサンプルブログをご覧いただきたい。必ずや「これはすごい!自分のはてなダイアリーにも是非適用したい!!!」と思うことだろう。
コードは、以下のリンク先から得ることができる。

是非ともこのプラグインを採用していただき、ユーザはもとより閲覧者についても、誰もがより豊かなはてなダイアリーライフを満喫できるよう、寛大な取り計らいを望む。

Acme::Youpy – Create your own youpy!

ネット中のそこかしこで目撃されているにもかかわらず、誰もそれが実際にはなんなのかを知らない……。Bus error吐いたり寝返りで地震を起こしたり窓の外で光ったりyoupyが人間に見えた人は、抽象化能力が高くてプログラマに向いていたり怪しげなビデオを枕元においていったりする、謎の存在、id:youpy……。あまりにもわからなさ過ぎるので、もうこうなったらみんなで、俺の/私の考えるyoupyを作ってしまえばいいんじゃないかと思い、Google Codeを利用して、youpy作成プロジェクトを立ち上げた。

また、このyoupyの活動の様子をTwitterにて観察することができる。以下をご覧いただきたい。

youpyの使い方

以下、youpyの使い方を解説する。
まず、以下のようにして、svnリポジトリからコードをチェックアウトする。

$ svn checkout http://acme-youpy.googlecode.com/svn/trunk/ acme-youpy

youpyは設定ファイルに基づいてプラグインを読み込み、動作する。youpyの機能を実装した例として、現在のところ、ふたつのプラグイン(function)が用意されている。

設定については、exampleディレクトリ下に配された設定例を参考にしてほしい。

debug: 1
functions:
- name: TimeTone
config:
username: username
password: password
- name: Shigo
config:
username: username
password: password

functionsに、プラグインをname, configからなるハッシュの配列として記述すればよい。設定の詳細については、それぞれの機能を実装したモジュールのPODにあたっていただきたい。

$ ./youpy -c [設定ファイル]

として起動する。設定ファイルが指定されなかった場合、youpyと同一ディレクトリにある.youpyというファイルを読み込む。

youpyの作り方

以下、youpyの作り方を解説する。
前述の通り、youpyの具体的な機能は、プラグインによって実装される。全ての機能は、Acme::Youpy::Functionのサブクラスとして、Acme::Youpy::Function::というプレフィックスを付された形で実装されなければならない。
以下は、youpy実行時現在の時間を、Twitterに投稿する機能を実装したモジュールである。

package Acme::Youpy::Function::TimeTone;
use strict;
use warnings;
use Net::Twitter;
use POSIX qw(strftime);
use base qw(Acme::Youpy::Function);
sub execute {
my ($self, $ctx, $config) = @_;
my $twitter = Net::Twitter->new(
username => $config->{username},
password => $config->{password},
);
$twitter->update(strftime("%Y/%m/%d %H:%M:%S", localtime));
}
1;

要するに、Acme::Youpy::Functionを継承し、executeメソッドにより、実際の機能を実装すればよい。そして、設定ファイルにしかるべく設定を書き込み、youpyを実行する。
なお、現在のところ、某Plaggerにて用意されているような数々のユーティリティのようなものは存在しないし、また、「それplaggerをyoupyにリネームすればできるよ」といった揶揄を受け入れる用意はない。

Join our project!

上記を参考に、それぞれで思い思いのyoupyを作るのも良いだろうし、より優れたyoupyを作るべくこのプロジェクトに協力してくださる方は、このエントリのコメント欄にてGoogleのアカウント名を知らせていただきたい。随時、プロジェクトメンバに追加する。

HTML::StripScriptsでXSS対策をする

先日公開した「はて☆すたアンケート」にて、アンケートの説明文をはてな記法で書けるよう、機能追加を行った。その際、Template::Plugin::Hatenaを用いた。これは、はてな記法パーサであるText::Hatena(正確には、そのヴァージョン0.16以下)を、Template::Toolkitのプラグインとして使えるようにしたものである。
はてな記法は、それ自体で全ての文書構造を表現できる、あるいは、はてなダイアリのシステム自体は、はてな記法のみしか許容しないというものではなく、たとえば画像を貼る際には、普通にimg要素を書く必要があるし、また、その他の要素についても、記法が用意されていないものについては、「はてなダイアリーのヘルプ – はてなダイアリー利用可能タグ」に掲載されているものに限り、自分でタグを書くことができる。これは自由度を高める反面で、XSSを誘発し得る潜在的なリスクがある。であればこそ、「はてなダイアリーのヘルプ – はてなダイアリーXSS対策」に見られるような対策が重ねられてきたわけだ。
はて☆すたアンケート」におけるはてな記法の利用、および、それに伴う一部のタグ利用の開放に際して、当然、記法パーサの役割を担うのみであるText::Hatenaを利用するだけでは、上述のようなセキュリティ上のリスクをまったく考慮しないことになってしまう。つまり、Text::Hatenaによりはてな記法を変換した上で、ユーザ入力によるHTML断片の中に含まれ得る危険な要素を排除する必要がある。この取り扱いは非常に困難を極めるため、よくテストされた配慮を必要とする。
その問題に処するに際して、今回はHTML::StripScriptsを用いた。これは、ホワイトリストに基づいた要素や属性のフィルタリングを行うとともに、セキュリティ上の問題を誘発し得るスクリプト断片を除去することを目的としたモジュールである。スクリプト断片は、script要素やイヴェントハンドラ等、様々な場所で現れ得るが、それらを適切に除去してくれることになっている。また、その際に、以下に挙げる細かいオプションを指定することができる。

  • 入力されたHTML断片のコンテキストに応じて、許容する要素をあらかじめ用意(HTML全体/body要素中の、通常現れても問題ないと考えられる要素/インライン要素のみ/タグを一切許容しない等)
  • 指定したタグを許容しない/指定したタグ以外を許容しない
  • href、src属性を許容するかどうかを指定可能
  • 相対URLやmailtoスキームによるリンクを許容するかどうかを指定可能
  • 柔軟なカスタムルールの追加

コンテキストに応じて許容され得る要素の具体的な内容については、モジュールのソースを参照されたい。
以上に見たとおり、使い勝手が非常に良いモジュールであり、また、肝心のスクリプト除去については、通常のテストとともに、XSS (Cross Site Scripting) Cheat Sheetに挙げられたリストについても、その全てについてテストを行い、危険箇所が除去されることが確認されており、そのことによって全ての状況において安全とはもちろんいえないのだろうが、よくテストされたものであるように思われる。
このモジュールを「はて☆すたアンケート」のようなウェブアプリケーションで用いるに際しては、Template::Toolkitのプラグインとして利用するのがベストな方法だろう。それはまだ用意されていないようであったので、HTML::StripScriptsの、HTML::Parserを用いたヴァージョンであるHTML::StripScripts::Parserを利用して、Template::Plugin::StripScriptsとして、プラグイン化を行った。以下のようにして、テンプレート内で使用する。各オプションの詳細については、依存モジュールのドキュメントにあたられたい。

[% USE StripScripts %]
[% FILTER stripscripts Context             => 'Document',
BanList             => ['br' 'img'],
BanAllBut           => ['p' 'div' 'span'],
AllowSrc            => 1,
AllowHref           => 1,
AllowRelURL         => 0,
AllowMailto         => 0,
EscapeFiltered      => 0,
Rules               => { See the POD of HTML::StripScripts },
ParserOptions       => {
strict_names    => 1,
strict_comments => 1,
},
%]
... HTML which can cause XSS ...
[% END %]
or
[% myhtml | stripscripts options_like_above %]

上述した、「はて☆すたアンケート」におけるはてな記法、および、一部のユーザー入力によるタグの利用については、以下のような形でフィルタをつなげることでそれを可能とした。

[% myhtml | hatena | stripscripts options_like_above %]

ここでは、HTML::StripScriptsのHTMLの利用によるXSS対策について述べたが、それが完全にその脆弱性を排除し得ることを意味するわけでは、もちろんない。実際の利用に際しては、よくよく検討されたいと思う。

Plagger で、はてなミュージックを Windows 以外でも使えるようにする

はてラボで、はてなミュージックなんてな素敵サービスがリリースされ、音楽好きとしてはこれ以上ない楽しみなわけで、さっそく遊んでいます。しかし、いまんとこはてなミュージックの曲情報更新クライアントは Windows + iTunes にしか対応していないわけで、

はやくMacではてなミュージックしたいです。

なんてな記述も見受けられます。まぁ、ちょっと追加のコードを書きさえすれば、まさに「それPla」な話。そこで、はてなミュージックの更新クライアントの挙動を調べて、まずは WebService::Hatelabo::Music というモジュールを作り、それを Plagger から使うために Plagger::Plugin::Publish::HatelaboMusic というプラグインを作りました。

まぁ、以前「antipop – mixi ミュージック、あるいは Web2.0 のレッスン」なんてエントリを書いたりもしてるので、mixi をいじっておいて、はてなをいじらないわけにもいかないし!

WebService::Hatelabo::Music

WebService::Hatelabo::Music はこんな風に使います。まぁ、new して add_tracks するだけ。

use WebService::Hatelabo::Music;
my $music = WebService::Hatelabo::Music->new(
username => $username,
password => $password,
);
$music->add_tracks(
{
artist => 'Cornelius',
album  => 'Sensuous',
track  => 'Music',
date   => '2006-11-11 01:46:08',
length => 292,
count  => 11,
},
{
artist => 'Kahimi Karie',
album  => 'Nunki',
track  => 'He shoots the sun',
date   => '2006-11-11 01:51:00',
length => 165,
count  => 4,
},
);

Plagger::Plugin::Publish::HatelaboMusic

んでもって Plagger::Plugin::Publish::HatelaboMusic について。iTunes の曲情報を取得するプラグインはすでに存在するので(CustomFeed::iTunesRecentPlay)、それを使います。その際、title_format フィールドを %title と明示的に指定しておくほうがよいです。設定他、詳しくはそれぞれのプラグインの POD やソースをご覧ください。

  - module: CustomFeed::iTunesRecentPlay
config:
library_path: /path/to/iTunes Music Library.xml
duration:     120
title_format: %track # you'll prefer it in most case
- module: Publish::HatelaboMusic
config:
username: hatena_id
password: hatena_password

こんな感じで、Windows 以外の環境でも、はてなミュージックを楽しめるようになります。楽しみましょう。

はてなミュージック曲情報更新プロトコルについて

いろいろ調べてコードを書いたのですが、音ログ 開発日記 | はてなミュージックは AudioScrobbler 互換? 及びコメントによると、Audioscrobbler – The Music Technology Playground from Last.fm と互換のプロトコルとのこと!うおお。そうなるといろいろとアレしないとならなくなってきたなあああ。
というわけで仕様の詳細がわかったので、以下の記述はあんまり読む必要ないです(微妙に違うところがあったり、他の環境での実装の参考になる点はいくつかあるとは思いますが)。

まぁせっかくなので、上記のコードを書くにあたって調べたことを、他の環境でクライアントを作成したい方のために、まとめておきます。当然、API の非公式な使用ですので、今後の保証はまったくありません。

クライアント起動時

はてなミュージック更新クライアントは、起動時に http://music.hatelabo.jp/tools/HatenaMusic.rdf をリクエストします。その中身はこんな感じ。クライアント自体の更新があったときに、自動更新かなんかするためでしょう。

<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<RDF:Description about="urn:hatenamusic">
<hatena:version>0.1</hatena:version>
<hatena:updateLink>http://music.hatelabo.jp/tools/HatenaMusicSetup.exe</hatena:updateLink>
</RDF:Description>
</RDF:RDF>
認証に用いるための MD5 ハッシュ文字列の取得

認証方法は、Digest 認証ぽい感じの方法を用いているようです。最初に、パスワードをダイジェストするのに使うための MD5 ハッシュ文字列を取得します。
まず http://music.hatelabo.jp/trackauth に対して、

hs
hand shake
p
protocol?
c
client?
u
user

こんな感じのよくわからないパラメタ付きでリクエストします。以下のような感じ。

http://music.hatelabo.jp/trackauth?hs=true&p=1.1&c=hatena&v=1.0&u=hatena_id

リクエスト成功時には以下のようなレスポンスが返されます。

UPTODATE
(MD5 ハッシュ)
http://music.hatelabo.jp/trackadd
INTERVAL 1

リクエスト失敗時には以下のようなレスポンスが返されます。
hs が true 以外

FAILED handshake is not requested. INTERVAL 1

u が指定されていない

BADUSER INTERVAL 1

p や c が指定されていない

FAILED prams are short. INTERVAL 1
セッションの確立、曲情報の送信

次に、上述の過程で取得した MD5 ハッシュ文字列を元に、セッションを確立します。セッションの確立に用いられる文字列の作成方法は、以下の通り(例の md5_hex は、Perl の Digest::MD5::md5_hex)。

s = md5_hex(md5_hex(password) + サーバから送られてきた MD5 ハッシュ文字列)

このようにして作成したセッション文字列を用いて、http://music.hatelabo.jp/trackadd に対して、以下のようなリクエストを送ります。

u=hatena_id&s=上記で作成したセッション文字列&a[0]=&b[0]=&t[0]=&i[0]=1970-01-01 00:00:00&l[0]=0&p[0]=1&r[0]=0&c[0]=0&f[0]=20

以下のようなレスポンスが返されたら、認証に成功したことがわかります。

OK
INTERVAL 1

認証が成功したら、上記と同様、http://music.hatelabo.jp/trackadd に対して、曲情報を送信します。パラメータは以下の通り

a (required)
artist の名前
b (required)
album の名前
t (required)
track の名前
i (required)
再生した時刻
l (required)
length (秒単位)
c
count(アルバムの何曲目か)
p
不明
r
不明
f
不明

また、それぞれのパラメタは配列のような形式をとっており、複数の曲情報を送信することができます。例としては、以下のようなパラメタになります。

u=hatena_id&s=上記で作成したセッション文字列&a[0]=℃-ute&b[0]=キューティークイーンVol.1&t[0]=YES! しあわせ&i[0]=2006-11-10 09:05:14&l[0]=275&p[0]=0&r[0]=0&c[0]=8&f[0]=0

リクエスト成功時には以下のようなレスポンスが返されます。

OK
INTERVAL 1

リクエスト失敗時には以下のようなレスポンスが返されます。

BADAUTH
INTERVAL 5
注意点

http://music.hatelabo.jp/trackadd にアクセスし、MD5 ハッシュ文字列を取得するたびに、セッション文字列を再構築しなければならないことに注意が必要です。例えば、自作プログラムではてなミュージックにアクセスしている間に、通常の iTunes プラグインや、ブラウザ等によって http://music.hatelabo.jp/trackadd にアクセスがあると、セッションが初期化されるので、セッションを再確立しなければなりません。詳細については、WebService::Hatelabo::Music の add_tracks() と retry() メソッドのあたりの実装をごらんください。

WebService::Hatena::Graph 更新

WebService::Hatena::Graph を更新(0.04)。いろいろあって、インタフェイスを変更。引数をハッシュで渡すようにしました。最初からそうしてりゃよかったぉ。。。あと、POD がおかしい、、
実際使ってるのって「ふしはらかんの四方山話 – Publish::HatenaGraph 作った」ぐらいかしら。

use WebService::Hatena::Graph;
my $graph = WebService::Hatena::Graph->new(
username => $username,
password => $password,
);
$graph->post(
graphname => $graphname,
date      => $date,      # optional
value     => $value,
);

UNIVERSAL::to_json リリース

UNIVERSAL::to_json なんてのをこさえたのですが、DIS られる悪寒をひしひしと感じながら、CPAN にうpってみたりしました。UNIVERSAL::to_yaml インスパイヤ。つか、autobox かっけ!

use UNIVERSAL::to_json;
my $obj = Foo->new;
print $obj->to_json;
{
use autobox; # activate autobox
print 'scalar value'->to_json;   #=> "scalar value"
print [qw(list items)]->to_json; #=> ["list","items"]
print {key => 'value'}->to_json; #=> {"key":"value"}
no autobox;  # inactivate autobox
print {key => 'value'}->to_json;
#=> 'Can't call method "to_json" on unblessed reference'
}

UNIVERSAL::to_json provides to_json() method to all objects.
Besides, this module supports you to extend unblessed values like scalar, reference to array and reference to hash using brilliant autobox module. This feature enables you to call to_json() method directly from such values. It is optional.

© 2020 栗林健太郎

Theme by Anders NorénUp ↑