mikutterの最新の情報は、mikutter blogに引っ越しました。

2014年12月25日木曜日

#mikutter 3.2をハックする

mikutterアドベントカレンダー20145 25日目の記事です。

mikutter 3.2で新しく追加されたプラグインの機能拡張について、一通り見てみましょう。

画像サービスに対応する
twitpicやinstagramみたいな画像をアップロードすることを主とするサービスや、d250g2.comのような画像の提供が主となるサービス(便宜上まとめて画像サービスと呼びます)を、画像プレビューのウィンドウで表示できるようになりました。

ルールを追加してみる
今まで、画像を開くのはopenimgというプラグインが担当していました。これからは、画像をロードしてUI上に画像を表示するのはopenimgで、あるURLから画像自体を取得するのはphoto_support プラグインが担うことになります。

実際に photo_support プラグインを見ると、どうやって画像を開くのかわかると思います。
これはTwitpicのルールです。一番オーソドックスなルールを持っています。
今までのopenimgは、openimgプラグインに付属しているJSONファイルを編集してルールを追加していたので、サードパーティプラグインから追加する方法はありませんでした。
また、openimg自体も正規表現を駆使して画像を探すなど素晴らしい実装になっており、今回openimgを作りなおすに当たっては本当に難儀しました。数年前に自分が書いた、正規表現をゴリゴリ使ってスクレイピングするコードをメンテナンスするってなかなかつらいものがあるんだなあ。

今回このように一つひとつの画像サービスに対するルールをコードで現すようにした経緯は前のエントリで説明しましたが、もともと3年くらい前は全部のルールをコードで書いていました。それをやめた時の反省をせず戻すわけにはいきません。

で、JSONのようなデータ構造を採用したのは以下のような問題を解決するためであって
  1. 画像サービスってちょいちょいルールが変わるので、バージョンアップしなくてもルール配信できるようにしたい! 
  2. まとめないとコードが恐ろしいサイズになる
それぞれ以下のように解決しました
  1. 画像サービスはTwitterに死刑宣告されたからか、もう頻繁にDOM構造が変わることがなくなった。そもそも利用頻度が低い(今回この改修をしてるときにyfrogが見れなくなっていることに気づいたが、誰からも報告が来てなかった)。
  2. Nokogiri,HTTPClient,OpenURIあたりを積極的に使ったらJSONと同じくらいの行数のコードに収まっちゃった
まとめ
  • openimg と photo_support に分けたので、取得ルールと表示部分が分かれて良かった
  • ルールは全部コードに書くことにしたので一箇所にまとまって良かった
  • ルールはNokogiri使って書くことにしたら分量が減ってわかりやすくなって良かった
  • ルールは他のプラグインからも追加できるし最高
自分のプラグインから画像を開こう
この変更でユーザが新しくできるようになったことで一番重要なのは、自分のプラグインからopenimgを利用できるようになったことです。まず、mikutter 3.2 で Alt + x を押して mikutter console を開き、以下のように入力して CTRL + Enter を押してみましょう。

Plugin.call(:openimg_open, 'http://twitpic.com/d250g2/')


画像のURLをクリックした時と同じように、画像が表示されたと思います。ただのイベントですから、あなたのプラグインからも同じ方法で画像を表示できるのです。
注目すべきは、URLから画像を探しだし、それを表示したということです。実は、photo_support や他のプラグインで定義されたルールがこの時に使われます。これは強力ですね。mikutterを起動する度に同じ画像を勝手に開くことも可能です。当然、3.1までは他のプラグインからこのようなことは出来ませんでした。

画像をダウンロードしよう
次は画像自体をダウンロードしてみます。
次のコードを実行してみましょう。

画像をダウンロードするのでちょっと時間がかかるかもしれません。
終わったら、 ~/.mikutter/tmp/d250g2.jpg というファイルができていると思うので見てみましょう。燃えているのではないでしょうか。

簡単に説明すると、フィルタ openimg_raw_image_from_display_url を使うと、URLをダウンロードした結果が流れてくるIOオブジェクトが第二引数に渡されます。あとはそこから IO#read で受け取れば、画像のダウンロード完了です。これもちゃんと画像サービスのURLから実際の画像を探してダウンロードしていますね。それがなければ、このフィルタの存在する意味がないんですが。

ルールを追加してみるの節で、 defimageopener のブロックが、open() で終わっているのが気になった人がいるかもしれませんね。あの戻り値が、このフィルタのIOです。つまり何が言いたいかというと、ダウンロードが終わったら、そのIOは、フィルタを呼び出した側がcloseしてください、ということです。尤も、GC時に自動的にファイルディスクリプタは開放されると理解してるんですが、IO閉じないのって他のオブジェクトより罪悪感があって…

抽出タブの抽出対象
うまく伝わるタイトルは思いつかないのですが、要するにこれを増やす機能です。
標準では、ユーザ名・本文・Twitterクライアント名を使用できますが、もっとほかのことを基準に抽出したい!ということがままあったので追加できるようにしちゃいました。はい、こんな感じのコードで追加できます。



これは、Ruby 2.0から使えるキーワード引数を使っているという意味でも面白い例です。キーワード引数が何なのか知らない人は、12/30に東京ビッなんとかで行われるコミックなんとかの、西2 く-30a mikutterの薄い本製作委員会 ブースに来てください。私も行くので、本物のmikutterをお見せしましょう。

defextractcondition(:bio, name: "bio", operator: true, args: 1)

などと書いていますが、これは見て分かる通りbioで抽出するやつを追加します。name: は上のスクショのコンボボックスに表示する名前ですね。operatorとargsはおまじないです。…と言っても、敬虔なmikutterユーザ諸君は「おまじない」では納得できないでしょう。そういう方は、12/30に東京ビッなんとかで行われるコミックなんとかの、西2 く-30a mikutterの薄い本製作委員会 ブースに来てください。私も行くので、本物のmikutterをお見せしましょう。

あとはブロックの方がキーワード引数を要求してますね。 argument はスクショで言うところの右のテキストボックスに書かれた内容です。その左隣の「=」とか「含む」とかの条件ですが、 &compare がその条件でうまい具合に比較してくれる奴です。message: は抽出対象の message が一つ渡されます。つまり1ツイート毎に1回このブロックは実行されるので、ここではあまり重い処理をやるとロクなことがなさそうです。実際には、重い処理はどんな場所でも大抵ろくなことになりません。現実は厳しい!社会は厳しい!

それで、コメントになっている部分ですが、これはそのすぐ下のシステムメッセージを抽出するための条件と同じものです。システムメッセージは二つ見どころがあって、ひとつは引数や比較方法がない条件だということ、もうひとつは、defextractconditionにはS式も渡せるということです。コメントアウトしてる部分は、ブロックがなくなり、こんな引数が追加されていますね。

MIKU.parse("'(system? message)")

これをmikutter上で実行すると、以下のような値が返って来ます。

(:quote (:system? :message))

そのまんまですね。mikutterの抽出タブの条件がS式になっていることは有名ですが、まさか抽出対象がS式でも渡せるとは…たまげたなあ。
流石にこれは闇が深いのでこれ以上掘り下げることはしません。S式にすることの意味は速度・メモリの効率です。抽出対象をS式で指定すれば、マクロみたいな雰囲気で全体の条件の中に展開されるので、単一のRubyコードに変換されます。一方で、ブロックだとブロックを呼び出す準備で余計な処理が入ります。ブロック呼び出し自体もありますし。

で、確かにこちらのほうが速度的には有利になりますが、実際計測してみると思った程でもありませんでした。ラピュタに突っ込む気でいるなら気になるかもしれませんが、毎秒2,3ツイート程度ならどうでもいいんじゃないでしょうか。完全な悪ノリでした。mikutterも悪ノリみたいなものなので、抽出タブの条件周りはmikutterの本質のひとつなのだと思います。こういう側面を知らなかった、もっと知りたいという人は、12/30に東京ビッなんとかで行われるコミックなんとかの、西2 く-30a mikutterの薄い本製作委員会 ブースに来てください。私も行くので、本物のmikutterをお見せしましょう。デコにとんでもないものをくっつけてお待ちしております。

あとがき
サードパーティプラグインがどのような恩恵を受けられるのか、という観点からmikutter3.2で行われた変更を見てみました。どちらもコア機能の変更はとくになく、表層的な変更ですが、そんなに需要はないと思います。しかし需要はほとんど気にしていないので、こういうこともあるよなあといった感じです。

内容に関してはもっと具体的に掘り下げても良かったんですが、なかなかこういう記事では難しいものですね。別の話ですが、writing mikutter pluginとは別に、やりたいことからリファレンス的に引けるドキュメントを考えてるしそっちで補えたらなーとか思いつつ、そもそも書けるのかとか、うーん。


mikutterアドベントカレンダーの最終日・mikutter5周年ということで、今年一年のmikutterについて振り返ろうかなとも思ったのですが、ここ数年は、正月に一切のタスクから開放された状態でゆっくり書く感じだったので、今年もそんな感じで行きたいと思います。
そんなわけで簡単に、mikutter、誕生日おめでとう。来年もよろしくお願いします。