Category Archives: tool

tool

Ubigraph+ubi.jsでリブログの流れを可視化する

仕事でちょっとネットワークの可視化をしたい場面に遭遇したので、先日果たしてtumblrにアルファリブロガーは存在するのかのあとでtwitterでkybintさんに可視化ツールをいろいろ教えてもらったのでちょっと試してみようとやってみたらUbigraphがとてもお手軽でよかったです。インストールからはじめて、自分が持っているネットワークデータを可視化する方法を理解するまで5分、はじめてでも10分もあれば自分のデータを可視化できます!

ダウンロードのページからバイナリをダウンロードします(でも悲しいことに今はOSX版とLinux版しかありません)。ダウンロードするページにインストール方法、起動方法、サンプルコードの実行方法が載っているので、とにかくそれをコピーしてターミナルに貼れば本当に何も考えないでもサンプルが画面で表示されるところまでいけます。

で、おおー、っと思ったところではじめて画面に出てきたものとサンプルのコードを見比べていくと、ほしい数だけ頂点を作って繋がっているぶんだけ頂点2つを指定して辺を作っていくだけでいいのがすぐ分かります。Ubigraphの実体は、視覚化したものを表示するXMLRPCサーバで、それにXMLRPCでリクエストを送ると動的にネットワークが変化していく仕組みになっていて、いろんな言語から使うことができます。perlの場合は

#!/usr/bin/perl
use Frontier::Client;
my $client = Frontier::Client->new( url => 'http://127.0.0.1:20738/RPC2';);
$client->call('ubigraph.clear', 0);
my $a = $client->call('ubigraph.new_vertex');
my $b = $client->call('ubigraph.new_vertex');
$client->call('ubigraph.new_edge', $a, $b)

こういうコードで、画面に頂点がふたつ生成されて、そのふたつが辺で結ばれます。
perlを使うなら実際はFrontier::ClientをオブジェクトっぽくラップしているUbigraphのほうがなじみやすいと思います。RubyはmootohさんのRubigraphがあります。

Ubigraphのコードが分かりやすい、といっても、ほかのツールも同じようなもので、どの頂点とどの頂点が結ばれているかのデータファイルを作ってツールに流し込むだけで単純さはほとんど変わりません。ただ、手持ちのデータからそのツール用のデータファイルを作るためのプログラムを書いてそれをツールに読ませてビュワーで再読み込みさせるのより、手持ちのデータに従ってUbigraphのAPIを呼ぶプログラムを書けばそれで画面に反映されるところは地味に面倒な手順が減っていいなと思いました。

ひととおり使い方が分かったので、前回の果たしてtumblrにアルファリブロガーは存在するのかのときに果たせなかったリブログされる様子のグラフも作ってみました。

Ubigraphは動的に変化するグラフを視覚的に見られるところが本当に素晴らしいのであって、こういうふうに動かない画像にすると全然魅力的に見えないんですが、ほんとはリブログされた時系列順で頂点が増えていきます。Ubigraphは次数(頂点から出ている辺の数)が多いものを中心にしてグラフがレイアウトされるので、なんか全ての始まりであるはずのところが、真ん中のほうにちょこっとついてるだけに見えちゃってますが、赤い球のところが一番はじめのpostです。
はじめにpostしたgipsさんからkielaさんがreblogして、そこから6人のひとに枝分かれして何人もの人を介して広がっていっているのが分かります(っていうのが初めてでも10分くらいで作れるので、ちょっとこれ見てみたいな、という時があったらぜひ試してみるのをお勧めします!)。

一部を拡大することもできます。

Ubigraphの神髄は動的に変化する様子が見られるところで、こういう変化しないものを見るときはレイアウトを細かく設定できるほかのツールのほうが優れてそうです。それでも、小さなネットワークであればUbigraphでも十分形を把握することはできますし、何よりとっときやすいところがよかったです。

Firebug+ubi.js

上のグラフはFirefox+FirebugでXMLHttpRequestを使ってUbigraphにXMLRPCリクエストを送信して作りました。

ほんとはFirefox3.1のクロスサイトXMLHttpRequestを使えたら、ウェブページに置かれたUbigraph用のjavascriptコードを読み込むだけでローカルのUbigraphを動かせるんですが、仕様が厳しくなってtext/xmlはリクエストを送られる側のサーバの許可なしで送れなくなったため*1、Ubigraph自身のページ(127.0.0.1:20738)でFirebugを開いてコードを実行しないといけません。

FirefoxでUbigraphを起動したあとFirefoxでhttp://127.0.0.1:20738/RPC2を開いてFirebugで下記のコードを実行すれば手元のUbigraphでリブログされていく様子を見ることができます。Firefox上でウェブにあるHTMLからデータを取り出してFirebugからUbigraphにデータを送れたらすごくスマートでいいなというわけでUnigraph APIのほんの一部をラップしたubi.jsというのgistに置いたので、ちょっとしたデータをちょっと見てみたいな、という時にはぜひUbigraphともども使ってみてください。

function s (u) {
var s = document.createElement("script");

s.src = u;
s.type = "text/javascript";
document.body.appendChild(s);
}

s("http://gist.github.com/raw/66201/b6784b8431305d74ec0c822f73e7b8de9aee95af/ubi.js");
s("http://re.ido.nu/action/73684294?callback=p")

var vt = {};

window.p = function(ret) {
  var u = new Ubigraph();
 u.clear();

  ret.map ( function (d) {
if ( d.action == "liked" )
            return;

// ignore multiple reblogs.
if ( vt[d.who] )
   return;
    
    var v = vt[ d.who ] = u.new_vertex();
    u.set_vertex_attribute(v, "label", d.who);
    u.set_vertex_attribute(v, "shape", "sphere");

if ( d.action != "reblogged" ) {
    u.set_vertex_attribute(v, "size", 1.5);
    u.set_vertex_attribute(v, "color", "#800000");
    return;
}

if ( vt[d.from]  && vt[d.who] ){
  var e =    u.new_edge(
        vt[ d.from ],
        vt[ d.who ]
     );
   u.set_edge_attribute(e, "arrow", "true");
   u.set_edge_attribute(e, "arrow_position", "1.0");
}
} );
}

参考

  1. HTTP access control – MDC

JSCocoaのiPhone用インタラクティブコンソール

English version is available.

JSCocoaのページからリンクされたので日本語版をこっちにうつしました。

iviewで使っているUIViewtransformがなかなか思い通りに操れなくて困っていて、だったらアプリにSpiderMonkeyを入れてHTTP経由で通信して試行錯誤できるよにすればいいやとUIMonkeyというのを作り始めたところで、既にそういうのがいくつかあるのをJohn Resig – JavaScript iPhone Appsで知ってショックーというかアホだったのでJSCocoaベースにちょこっと書き直しました。viewの階層構造やサイズを調べるのに便利です。

ファイルのコピー

svn checkout http://jscocoa.googlecode.com/svn/trunk/ jscocoa-read-only

してjscocoa-read-only/JSCocoa/JSCocoaの中身を全部自分のプロジェクトにコピーします。class.jsだけプロジェクトに追加した時に、ただのリソースファイルとして認識してほしいんだけどソースファイルとして認識されてしまうので、いったんclass.gifという名前でプロジェクトに追加して後から名前を変えたらうまくいきました(どうやるのが正しいんでしょう…)。

JSCocoa本体のほかにHTTPサーバのためのHttpd.m, Httpd.h, console.htmlを追加してください。console.htmlCopy Bundle Reesourcesに入れてアプリケーションに組み込まれるようにしてください。

*_Prefix.pchの修正

JSCocoa用のヘッダファイルを追加します。
今回はインラインでUSE_JSCOCOAdefineしていますが、デバッグビルドの時だけdefineするようにしたりすればいちいち書き換えたりする手間がなくなります。

#define USE_JSCOCOA 1

#if USE_JSCOCOA
  #define JSCocoa_iPhone
  #include "JavascriptCore.h"
#endif

main.mの修正

JSCocoaのサンプルコードiPhoneTest2とおんなじようにmain.mで初期化します。クラスHttpdは自分で作ったなんちゃってHTTPサーバです(最後参照)。

#if USE_JSCOCOA
	// Fetch JS symbols
	[JSCocoaSymbolFetcher populateJavascriptCoreSymbols];
	
	// Load iPhone bridgeSupport
	[[BridgeSupportController sharedController] loadBridgeSupport:[NSString stringWithFormat:@"%@/iPhone.bridgeSupport", [[NSBundle mainBundle] bundlePath]]];
	// Load js class kit
	id c = [JSCocoaController sharedController];
	[c evalJSFile:[NSString stringWithFormat:@"%@/class.js", [[NSBundle mainBundle] bundlePath]]];

	[[[Httpd alloc] initWithPort:38880] autorelease];
#endif	

リンクするライブラリの追加

JSCocoaは関数呼び出しにlibffiを使っているので、プロジェクトの設定を開いてOther linker flags-lffiを追加します。

CGAffineTransformの追加

JSCocoaの中にiPhoneでだけ使われる構造体を定義しているiPhone.bridgesupportというXMLファイルがあります。これにCGAffineTransformを追加します(CGAffineTransformを使わないのであれば必要ありません)。

せっかくXMLファイルなのに構造体メンバを"で区切ってしかもそれを実体参照で書くという酷い仕様。下の行をてきとうにCGSizeを定義している下くらいに追加します。

<struct name='CGAffineTransform' type='{CGAffineTransform=&quot;a&quot;f&quot;b&quot;f&quot;c&quot;f&quot;d&quot;f&quot;tx&quot;f&quot;ty&quot;f}'
type64='{CGAffineTransform=&quot;a&quot;d&quot;b&quot;d&quot;c&quot;d&quot;d&quot;d&quot;tx&quot;d&quot;ty&quot;d}' />

ヘッダファイルからマシンフレンドリーな形式で生成して実行時コストを下げるか、ヒューマンフレンドリーにして手で書きやすくするかどっちかにするべきだと思います。

ブラウザで接続

そうしたらあとはアプリをふつうに起動すれば http://localhost:38880/ でアプリの中のJSCocoaに接続できるので、好きなタイミングでブラウザからJSCocoaにjsのコードを送り込んでアプリを操作できます。

以下iviewLifeの写真を表示してのテスト。

original

JSCocoaではObjective-Cのクラス名、メソッド名でjsからもアクセスできるので、UIApplication.sharedApplicationからアプリ内のオブジェクトにアクセスできます。

メインウインドウのアルファを0.2に変えると

alpha=0.2alpha=0.2

背景が透けて、暗くなります。

CGAffineTransformで横に3倍引き延ばしてちょっと右に100ピクセルずらすと

transformtransform

こうなります。

こういう試行錯誤や、使ったことのないクラスを触ってみるときに、Objective-Cのコードの上でやろうと思ったら毎回コンパイルし直す必要があるので時間がかかりますがjavascriptからできると細かい調整がすごく楽です。

View Tree Walk

takuma104さんが前に

iPhoneにWindowsでいうSpy++みたいなのがほしい。viewの階層構造がわかるやつ。
Twitter / takuma mori: iPhoneにWindowsでいうSpy++みたいな …

と言われていたのを思い出して、UIApplication.sharedApplication.subviewsからviewを列挙してjavascriptオブジェクトとしてみられるようにしてみました。HTMLで視覚的に再構築できればベストなんですけど、とりあえずFirebugのコンソールで値を確認できるようにしてみました。inspect view treeのリンクをクリックすると下の画面みたいにしてviewの階層構造を見ることができます。

JSCocoa感想

JSCocoaがNSArrayを自動的にjavascriptの配列として扱ってくれるのですが、これがあんまりうまくいってないみたいでlengthの値が入ってなかったりするとか、そういう微妙に変なところを配慮してあげればわりと使えそうなかんじでした。
自分が普段SpiderMonkeyにどっぷり浸かっているのでunevalがないとかE4Xがないとか分割代入できないとかもありましたがそうでなければふつうにjavascriptです。

ImageMagickでiPhoneシミュレータのキャプチャから画面部分だけを取り出す

iPhoneシミュレータではiPhoneの実機のように画面部分だけのスクリーンショットを撮ることができない。Command+Shift+4でiPhoneシミュレータ自体のスクリーンショットを撮ると不要なワクの部分もついてくる。これをImageMagickで捨てる。

ImageMagickのインストール。

sudo port install ImageMagick    

縦方向だけ微妙に中心からずれているので -6 で補正。

convert Picture\ 1.png -gravity Center -crop 320x480+0-6 p1.png

さらにそれを半分にしたければ

convert Picture\ 1.png -gravity Center -crop 320x480+0-6 -size 160x p2.png

これが
iphone simulator
こうなって

半分になる。

参考

ImageMagick、正方形のサムネイルを作成する – FAX

iPhotoからmixiにエクスポートするプラグイン(きちんと動くObjective-Cバージョン) v2

BugFix 2008.6.7(v2)

ファイルサイズが3M以上のファイルがアップロードされていなかったのを修正しました。ちゃんと縮小してからアップロードされるようにしたので、アップロードにかかる時間がすごく短くなりました。

いつのまにかiPhotoUploaderがmixiに対応していて、作ったけどかぶっちゃった。WSSE部分はiPhotoUploaderのコードを使わせていただきました。

iPhoto8+OSX10.5で動作確認済み。

ダウンロード

mixi.iPhotoExporter.pkg
source code

インストール

mixi.iPhotoExporter.pkgをダウンロードして、中身のmixi.iPhotoExporter.pkgを実行したら /Applications/iPhoto.app/Contents/PlugIns にプラグインがインストールされます。

スクリーンショット

こんなかんじで動きます。
mixiではひとつのアルバムに100枚しか写真が入らないので、100枚以上は入らないようになっています。また、3M以上の写真は640×480に縮小してからアップロードされます(アップロードしたらどっちにしても幅が640になるので、使う上ではなにも関係ないです)。

iPhotoのmixi用エクスポートプラグン改良版

追記 2008.6.2

別スレッドでexportされるiPhotoプラグインの仕様でRubyCocoaで動かすのが難しくて、けっきょくiPhotoからmixiにエクスポートするプラグイン(きちんと動くObjective-Cバージョン)を作ったのでそちらをご利用ください。

iPhotoのmixi用エクスポートプラグンを改良して比較的安定してまともな動作をするようになりました。ただRubyCocoaとiPhotoのエクスポートプラグインの仕様上の制約で一回エクスポートしたら一度再起動しないと二回目のエクスポートができないです(しようとすると何もしないで終了するようになっています)。

ダウンロード

バイナリ
mixiExporter.iPhotoExporter.zip
ソース
iphoto.exporter.mixi.20080509.zip

つかいかた

/Applications/iPhoto.app/Contents/PlugIns/
もしくは
~/Library/Application Support/iPhoto/Plugins/
mixiExporter.iPhotoExporter.zipの中にあるmixiExporter.iPhotoExporterをコピーしてください。もし前のバージョンのsimpleExporter.iPhotoExporterをインストールしていたらフォルダごと捨ててください(そのままにしていても動作に支障はありません)。

前のバージョンはエクスポートしている間フリーズしてた&ファイル数が多いと途中で止まってたのですが今回はフリーズもせず途中で止まったりもしなくなっています。また、ユーザ名・パスワード・アルバム名を記憶するようになりました。

RubyCocoaの何が問題になっていたか

iPhotoのエクスポートプラグインは、エクスポートを実行するたびに新しく作られたスレッド上で実行されるようになっているようで、実行するたびにコードを実行しているスレッドオブジェクトがかわります。

RubyCocoaのコードを見たかんじ、ひとつのプロセスの中で一度だけ初期化される前提で作られていて(それはたぶんRubyCocoaが内部で呼んでいるRuby本体がひとつのプロセスの中で一回しか初期化できない&同一スレッドで実行される前提になっているからだと思われます)、結果はじめにRubyCocoaを初期化したスレッドからしかRubyのコードを呼び出せないので(でもそのスレッドはもう一度エクスポートしようとしたときには存在しなくなっている)、二回以上エクスポートプラグインを呼び出すと問題が出るんだと思います。

やろうと思えばメインのスレッドでRubyCocoaを初期化して、ほかのスレッドからのイベント待ち状態にして、エクスポートプラグインからファイルをアップロードするときにメインのスレッドにリクエストを投げる実装にすればRUbyCocoaで問題なくいけるでしょうけど、そこまでやるならはじめからObjective-Cで書いたら?という気もするのでやりませんでした。

ゴールデンウィークあけて、前回iPhotoUploaderを参考にさせていただいたいとーけーさんのページを見たら

なんか、やってみたら意外にすんなりと出来てしまったので公開します。
いとーけーのページ – iPhotoUploader v0.0.2 公開しました(はてなフォトライフ対応)

と書かれてたので、たぶんObjective-Cに慣れている方ならSourceForge.JP: Project Info – iPhotoUploaderをベースにさくっとmixiにアップロードするバージョンも作れるんだと思います。

でも非同期サーバとか作るの楽しそうだし、難しそうなところができちゃえばほかのサービスも全部rubyで書けるようになるからやろうかなー。

コードは/platform/iphoto – CodeRepos::Share – Tracに入れときました。

iPhotoのmixi用エクスポートプラグン

追記 2008.6.2

別スレッドでexportされるiPhotoプラグインの仕様でRubyCocoaで動かすのが難しくて、けっきょくiPhotoからmixiにエクスポートするプラグイン(きちんと動くObjective-Cバージョン)を作ったのでそちらをご利用ください。

ぜんぜんちゃんとしたものではなくていつのまにか無限ループに入ってたりして扱いが難しかったりするんですがiPhotoからmixiにexportするプラグインをとりあえず動くくらいになったので公開。

mixiはどうでもよくてほんとは30days Album™用のを作りたかったのですが、Flashの中で認証コードを生成してるっぽくてそれを解析しつつプラグインを作るのはちょっと厳しかったので(そしてFlickrフォト蔵はもうあって、はてなフォトライフはなんかAPI壊れてるらしいし)mixiになりました。

ダウンロード

バイナリ
iphoto.exporter.mixi.zip
ソース
iphoto.exporter.mixi.20080504.zip

つかいかた

/Applications/iPhoto.app/Contents/PlugIns/
もしくは
~/Library/Application Support/iPhoto/Plugins/
iphoto.exporter.mixi.zipの中にあるsimpleFileExporter.iPhotoExporterをコピーしてください。

そうするとexportメニューの中にmixi exporterというのができてるはずなので、自分のmixiのアカウント情報と写真をアップロードしたいアルバムの名前を書いて入れてください。

そうするとアップロードが終わるまでexporting画面が出てきて、しかもまったく反応しなくなるけどたぶん動いているので大丈夫です。

補足

はじめ調べたらiPhoto exporterの仕様は非公開だと書かれてあったけどiPhoto SDKとかで探すと出てきました。Appleのサンプルコードをベースにmootoh.log – QSTwitter 1.4を参考にRubyCocoaを使うようにしました。mixiにアップロードする部分は買い物ログ別館: mixi-photo.rbのコードを使わせてもらっています。

Objetive-Cもrubyもまともに使ったことがなくていきなりRubyCocoaなのがいけないのですが、なんかプログレスバーをアップデートできなくて困っていて、それに加えて無限ループにはまることがあって、たぶんRubyCocoaを初期化しているスレッドと実際にコードを動かすスレッドが別になってる(結果おかしくなる)?のかなーと思ったけどなにもわかんないのでした。たぶん続く。

追記

NSPrincipalClass::loadでロードしてperformExportでrubyを呼び出すと、一定のCPU時間を使ったあとruby側が反応しなくなる(無限ループにはまっているかんじ)。これはNSPrincipalClass::loadでロードするのをやめてperformExportでロードするように書き直したら解決した。

でもこうするとRubyCocoaがロードされているかどうかがわからなくなる(exportダイアログが再度オープンされたときにはstatic installedが再び初期化されてる)ので、二回呼び出すと

[BUG] rb_cocoa_thread_schedule_hook: expecting to run on NSThread 0x13bb2340 but was 0x125bb410

といって落ちる。

Quicksilverのpluginは起動時に一回しか読み込まれないけどiPhotoはexportダイアログを開くたびに読み込まれるっていう違い?
既に読み込まれているかどうかの判定ができない。メインのスレッドになにかフラグ立てたりできればいいんだけど。

こんなふうには書けないよねーと思ったことがかけちゃうObjective-C+RubyCocoa+rubyの組み合わせはすげいっす。

muxtapeにid3タグを埋め込みつつダウンロードするJSActionsスクリプト

2008.5.9

曲名がずれるとか、同じになるとか、すごくおかしくなってたのを直しました。

MySpaceのMP3ファイルにID3tagを埋め込みつつダウンロードするJSActionsスクリプトのmuxtapeバージョン。要Muxtape MP3 Linkです(音が出るので注意)。

muxtapeのファイルはたいていid3タグが入っているので、そのときは元々のデータをそのまま残してコメント欄が空のときだけmuxtapeのURLを埋め込みます(一部元々コメント欄は空なのになぜかコメントが入らないものがあります on iTunes)。
jsでバイナリファイルのパースなんてしたくないと思ったけどふつうに正規表現書いたりsplitしたりしてできてかんたんでした(マルチバイト入ってなかったからかも)。

id3muxtape.js

synergy binaries for OSX10.5

intelMac&OSX 10.5 Leopard環境でsynergyを快適に利用する – ザリガニが見ていた…。に従ってバイナリを作った。できた。でもクリップボードの内容が転送されてない。こまった。

binaries
source code

こんだけ多くの人が使ってるツールでもメーリングリスト見てみたらほとんどメール流れてなくて驚いた。

バージョン2を作ってるらしい。
SourceForge.net: synergy2-devel

微妙に新しくなったtumblrでReblogCommandを動くようにするためのパッチ

追記 2008.6.2

ReblogCommandの最新版はcodereposからインストールすることができます。

訂正 2008.4.13

reblogCommandのコード、間違ってたみたいです。修正してます。Tombloo0.1.6で動作確認済。

Tombloo 0.1.3、LDR + Tombloo – 実用でGreasemonkeyからTomblooの機能が利用できるようになっているので、それを使ってrelbogするようにするためのReblogCommandのパッチです。Tomblooの機能に依存しているのでTomblooがないと動かなくなります。

Tomblooにもパッチが必要です。Tomblooは extensions/tombloo/chrome/content/browser.xulgetReblogToken TUMBLR_URL を付け足すだけです。

--- browser.xul 2008-04-12 11:17:16.000000000 +0900
+++ browser.mine.xul    2008-04-12 16:24:51.000000000 +0900
@@ -40,7 +40,7 @@
                        GM_Tombloo.Tombloo.Service = update({}, env.Tombloo.Service,
                                'check share posters extracters'.split(' '));
                        GM_Tombloo.Tumblr = update({}, env.Tumblr,
-                               'post remove getInfo read reblog openTab getLoggedInUser'.split(' '));
+                               'post remove getInfo read reblog openTab getLoggedInUser getReblogToken TUMBLR_URL'.split(' '));
                        GM_Tombloo.FFFFOUND = update({}, env.FFFFOUND,
                                'post remove iLoveThis'.split(' '));
                        GM_Tombloo.Flickr = update({}, env.Flickr,

ReblogCommandのパッチは以下。パッチ済みのファイルをreblogcommand.user.jsに置いておきます。

あ、あと消すためのコマンドも入れました。でもバグっててピンをつけたのと違うやつが消えるっぽいので使わないでください!

pinned-or-current-link|deletePost

で消せます。

Tomblooのreblogメソッドで帰ってくるのがMochiKitのDeferredインスタンスなのですが、それをJsDeferredのDefferedListで扱う必要があるのでトリッキーなことをしています。こういうもともと繋がらないのをなんとかして繋げるの好きだなー。

--- reblogcommand.orig.js  2008-04-12 16:12:20.000000000 +0900
+++ reblogcommand.user.js  2008-04-13 11:15:38.000000000 +0900
@@ -86,26 +86,41 @@
 }
 
 function reblog(aURL){
-  var id  = getIDByPermalink(aURL);
-  var d;
-  with(D()){
-    d = Deferred();
-    if(!id) {
-      wait(0).next(function(){d.call()});
-      return d;
-    }
-  }
-  var url = getURLByID(id);
-  window.Minibuffer.status('ReblogCommand'+id, 'Reblog ...');
-  getSource(url).
-  next(function(res){
-    return postData(url, createPostData( parseParams( convertToHTMLDocument(res.responseText))));
-  }).
-  next(function(){ window.Minibuffer.status('ReblogCommand'+id, 'Reblog ... done.', 100); d.call()}).
-  error(function(){
-    if(confirm('reblog manually ? \n' + url)) reblogManually(aURL);
-    d.call();
+  return invokeTumblrMethod(
+    aURL, 'reblog', {
+      start:  'Reblog ...',
+      done:  'Reblog ... done.',
+      manual:  'reblog manually ? ',
+    }
+  );
+}
+
+function deletePost(aURL){
+console.log("deletePost", aURL);
+  return invokeTumblrMethod(
+    aURL, 'remove', {
+      start:  'Deleting ...',
+      done:  'Deleting ... done.',
+      manual:  'delete manually ? ',
+    }
+  );
+}
+
+function invokeTumblrMethod(aURL, methodName, msg) {
+console.log("invokeTumblrMethod", aURL);
+try {
+  window.Minibuffer.status('ReblogCommand'+aURL, msg.start);
+
+  var d = GM_Tombloo.Tumblr[methodName]( aURL ).addCallback ( function (res){
+    window.Minibuffer.status('ReblogCommand'+aURL, msg.done, 100);
+  }).addErrback( function(){
+    if(confirm( msg.manual + '\n' + url)) reblogManually(aURL);
+    d.callback();
   });
+console.log("invokeTumblrMethod", aURL, d);
+}catch(e) {
+  console.log(e);
+}
   return d;
 }
 
@@ -155,9 +171,8 @@
     window.Minibuffer.execute(target_cmd + ' | reblog -m' + clear_pin );
   }});
 
-window.Minibuffer.addCommand({
-  name: 'reblog',
-  command: function(stdin){
+
+var tumblrCommand = function(stdin, method, manualMethod){
     var args = this.args;
     var urls = [];
     if(!stdin.length){
@@ -175,23 +190,46 @@
     // reblog
     if(args.length = 1 && args[0] == '-m'){
       urls.forEach(function(aURL){
-        reblogManually(aURL);
+        manualMethod && manualMethod(aURL);
       });
     }else if(args.length){
       console.log('unknown args...');
     }else{
       urls = urls.filter(isTumblrUserURL);
       if(!urls.length) return stdin;
-      var lst = urls.map(reblog);
+      var lst = urls.map(method);
       if(lst.length > 1){
+        // JSDeferred/MochiKit Deferred compatibility hack.
+      var jsdl = {};
+      for ( var i = 0; i < lst.length; i++ ) {
+        var d = lst[i];
+        jsdl["jsd" + i] = d;
+        d.next = d.addCallback;
+        d.error = d.addErrback;
+      }
+
         with(D()){
-          parallel(lst).wait(2).
-          next(function(){window.Minibuffer.status('ReblogCommand','Everything is OK', 1000)});
+          var d = parallel(jsdl).wait(2).
+          next(function(){
+            window.Minibuffer.status('ReblogCommand','Everything is OK', 1000)
+        });
         }
       }
     }
     return stdin;
   }
+
+window.Minibuffer.addCommand({
+  name: 'reblog',
+  command: function (stdin) { return tumblrCommand.apply(this,[ stdin, reblog, reblogManually] ) }
 });
 
+window.Minibuffer.addCommand({
+  name: 'deletePost',
+  command: function (stdin) { return tumblrCommand.apply(this, [stdin, deletePost, null]) }
+});
+
+
 })()
+
+

Tomblooハックス – background imageをポストできるようにする

2008.11.29 追記

今はデフォルトのTomblooで背景画像をpostできます。

たまに背景になっている画像をpostしたいときがあって、背景画像とかCSSとか調べてとらないとだめなのかなーと思っていたら意外にもコンテキストメニューオブジェクトから簡単にとれるのがわかったので作った。たぶんWeb1.0時代の、画像を壁紙にする、っていうめっきり使わなくなった機能の名残なんだと思う。

あと関係ないけど、menuitemtooltiptextつけたらツールチップ表示されるのを知った。ツールチップで実際にpostされるファイルの名前とか出そうとして、やめた。

ファイル

90_backgroundimage.js

Tomblooハックス – UIつきバージョン ポスト先にはてなダイアリーを追加する

前に作ったTomblooハックス – ポスト先にはてなダイアリーを追加するだと、常にtumblrとはてなダイアリーに追加されるようになって、いまだけはてなに、みたいなのができないので、そのへんのUIでできそうなのを探してやってみました。

Shiftを押してるときに動作を変える、というアプローチ。はじめにコンテキストメニューでShiftが押されているときはサブメニューが出て投稿先が選べる、というのにしようと思ったけどコンテキストメニューのpopupshowingではキーの状態が常にfalseでくるようになっていてだめでした(Windows/OSXどっちも)。

それで他にないかなーと思ってたらNoScript :: Firefox Add-onsとかで出てくるXUL:notificationbox – MDCが使えそうなので、試しに作ってみました。

コンテキストメニューから項目を選択するとき(下のscreenshotだとShare – Photoを選ぶとき)にShiftを押して選択すると

Select menu + shift button

ページの一番上にpostする先がボタンで出てくるので、postしたいところを押します。

Notification is popping up

押すと色が変わって押せなくなるので手がふるえてても二重に投稿されません。

Picture 5-11

コンテキストメニューのpopupで投稿先を選ぶ案(技術的な面でボツ)に比べて、同時に複数の投稿先を動的に変更できるのはいいところ。ページの一番上に出るのでマウスを移動させる距離が長いのが面倒だった(下にも出せるはずなんだけどやり方がよくわかんなかった。どっちに出そうが移動距離が長いのは変わらないけど)。notificationを使うのはいまいち。

でもこれでバリバリはてなダイアリーにquoteとかできる。photoはオリジナルへのホットリンクになるのでご注意ください。
あとはてなダイアリーだと当然tumblrみたいにquoteで文字が大きくなったりしないのでかっこわるいです。いちおうtombloo_quote, tombloo_photoというクラス名つきでpostされるので、CSSいじったらかっこよくなるかも。

コード

30_Tombloo.Service.20080405.diff
90_Hatena.20080405.js

愚痴

notificationのボタンのパラメータのcallbackが曲者で、ふつうのDOMイベントと違ってtrueを返したときにアクションがキャンセルされるようになってて、はじめfalse返したら何とかならんのかと思ってfalseにしたけどボタン押したらnotificationが閉じられちゃうので困ったなーと思って調べてたら mozilla/toolkit/content/widgets/notification.xml でコード見つけてこうなっててなんだそれと思った。

      <method name="_doButtonCommand">
        <parameter name="aEvent"/>
        <body>
          < ![CDATA[
            if (!("buttonInfo" in aEvent.target))
              return;
            var button = aEvent.target.buttonInfo;
            if (button.popup) {
              document.popupNode = aEvent.target;
              document.getElementById(button.popup).
                showPopup(aEvent.originalTarget, -1, -1, "popup", "bottomleft", "topleft");
              aEvent.stopPropagation();
            }
            else {
              var callback = button.callback;
              if (callback) {
                var result = callback(this, button);
                if (!result)
                  this.close();
                aEvent.stopPropagation();
              }
            }
          ]]>

なんでわざわざDOMのイベントと逆のtrueだとstopPropagationにしてるんだろう。おかげでそれを知るためだけにgrepかけたよー。

あと Google code search をみてると notification.hideclose = true で閉じるためのXボタンが消せるみたいなんだけど、実際やってみると消えなくて不可解だった。たしかにソースにもhidecloseで消えそうな部分はなかった。

capture.clipboard.js FirefoxでキャプチャしてimgタグをクリップボードにコピーするJSActionsスクリプト

FirefoxでキャプチャしてFlickrにアップロードしたりするやつの、アップロードしないでクリップボードにdataスキームでコピーするものです。

dataスキームというのはこんなかんじ

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEIUlEQVRYhe2XX0xbdRTHf/wZ4IDbEYOJybKn6YPRqMlexWxvxiUzPmhiNJkkzPhg1CwxmYlm2YuaaHBmvf17O1SCGbgMcG4anKVLmWOScn/33kLBdoWWwoVRaMet0F5uvz7c9kItxe7B7oWH83Jzvqef3/md3zk9BAC8cgpHB4N46ee7FbFjgyF45RQAgHjlFFq7/GA4CSZXZYzhJDx60Q+vnAI53DMJk0vCgQqbySWh3i6APNM7VfEf3257AHsAJQFqbAKqrBTEQkGsFE2cWHbQZk40tFVWimorfTCAWpuA14dmcfluAo6JON68EUFrlx+EpWjmRLSUCMZwEghLYXJJeG1oFhb/Mq6Ek2h3R7HPJpQHsM8u4OhgCIqqFdjsWganb81jv1NEtZUWQLTkoOvtAk555hBY3SjSv/JLGDU7QBQBEJaCm4wXBcibtLKOtoEgCMvDxG1pjlyexuhiqqRuYCYJYim+iiKAGhuFZ14xhFo2i3NjMo5fC6NTuIeNzSwUVcOZ2wsgLAUx83j35pzhb/XHceJ6GB/dXsB6zldRNfDxdTQ6RTDcLgAmTkKdTYBX3jrJH3IKxMzrxWjmcah7At3Tq1BUDWZpGZ+PL0FRNQzOJPHkD4Et32/GcXU2WZA5hpPQvBvAAZcEYqG4Ek4YwqHoGkiuiltcEhocIsgFHu94oobPx6MLIGYedXYBLTk/wlL0hlYNH8+8glqbUDR3dgT4zLdoCGNKBq1dfjQ59Wdoyvl8Re9BUTXcz2ziYmAFVVZqpJfhJDQ6xYIacEzGy6uBaivFq7/OFIhPXA+jKlf5hKV4zxuDomo46Y7g5WthKKqGc2OLICw1XkTbQLAgRsdwtDyARqeIg90TSKQ3DfHVmSQIS9HgEHG4J4D0ZhY3FxSjLvrD+l0/3zeNersIwvLoCqwY+oyWxVOXptDgKOMZ5q+hN7RVB9ks8OJAEOS8D2fHZCiqhi/Gl0A6fSCdPpy+NQ9F1XBBWgY5P45n+6aQ3vYCfo+tocZaZiPKX8PxXGq3G/naB2e+R2Q0/BhK4FJwFSsberb6w0mQTl9B9SuqhrfdO6e/JADDSWiwC+CX1wsCnRldwHN905D/VovgEulNtPUH0eGZK/g+s5Yx5sMDDSNioXjjt4gRKHw/DYt/GbU2AYe6J/HWjQg+uSPj0z9lnHRH8ERPAMRC8SW/hL8SaUP3wUgMhOVLzo+SAEzuPb8/EsOxn0JocUmoyvWD/U4RxEr1Tsjq0/IRh2hcH8NJeKE/iA9HYnjsW3/J0+8KkO+MxEJRaxOKOthuxnD6UCMWHabU6f8ToBK2B/DwAZ5+iAAml6SvZv/+k1AJYzgJdXZBX04Pfj+BJk6s2HLazEl4/LsJfTnNr+ft7gg6hqM45fl/rWM4inZ31FjP/wESYf77Kq1CmwAAAABJRU5ErkJggg==" />

で画像などの中身をそのままbase64で書いたら書かれた内容をファイルの中身として扱ってくれるやつです。
dataスキームはFirefoxとSafari(3.1は出ました)でサポートされていてるほか、IE8でもサポートされるようになるそうです。

少し大きなキャプチャになると表示されなくなっちゃいますが、小さい画像で、ファイルにするのがめんどくさいときとか、そもそもファイルをアップロードしたりできないとき(さらにIEで見ている人のことを無視できるとき)に使えます。

ダウンロード

capture.clipboard.js

Tomblooハックス – Vimeoのサポート

追記

Tombloo 0.1.4でVimeoもサポートされるようになりました。

Twitter / koyachi: jstnはvimeoで働いてるによればMuxtapejstnも働いているらしい個人的に今年最も注目しているVimeoはtumblrのブックマークレットではサポートされてるんだけどTomblooではサポートされてなかったので作りました。

Tomblooハックス – ポスト先にはてなダイアリーを追加するでTomblooの開発者のbrazilさんにいただいたコメントを参考にしてます。

Tombloo.Service.extracters = update( {
        'Video - Vimeo': {
                        check : function(ctx){
                                return ctx.hostname.match('vimeo.com');
                        },
                        extract : function(ctx){
                                return {
                                        type   : 'video',
                                        source : ctx.href,
                                        body   : ctx.title + '(via ' + 'Vimeo'.link( ctx.href) + ')'
                                };
                        }
                }
}, Tombloo.Service.extracters);

これを FirefoxProfileDirectory/extensions/tombloo@brasil.to/chrome/content/library/90_Tombloo.Service.Vimeo.js にして保存して再起動するとメニューにVideo – Vimeoが出てきます。

vimeo support

細かいこと

なんでわざわざupdateなんかしてるのか。

Tombloo.Service['Video - Vimeo'] = {
                        check : function(ctx){
                                return ctx.hostname.match('vimeo.com');
                        },
                        extract : function(ctx){
                                return {
                                        type   : 'video',
                                        source : ctx.href,
                                        body   : ctx.title + '(via ' + 'Vimeo'.link( ctx.href) + ')'
                                };
                        }
                }
};

上のように直接代入すると、for inでenumerateされる順番の関係(先に定義されたものが先に出てくる)でVidemo – Vimeoより先にLinkが出てきて使いにくいのでupdateしています。

ゼロオーバーヘッド・ブロギングの時代

tumblrのファウンダーであるDavidのインタビュー Read/WriteTalk » Blog Archive » David Karp – CEO, Tumblr や、投資しているVC(25%くらい)のTumblrについての説明 Tumblr | Union Square Ventures の中や、lifehacker.comでの紹介 Geek to Live: Instant, no-overhead blog with Tumblrのタイトルで、no-overheadというキーワードが出てくる。いままでのブログに比べて、書くときに必要な付随する作業が少なくてブログを書くのにかかる時間が短くなる、という意味。

はっきりと文字で目にしたのはこのときだけれど、振り返ればWakoopaのときからこのno-overheadの流れがあった。

時間がないのでスケールしない

インターネットでどんなサービスを使うにしても、時間をかけないではできない。だからユーザが新しいサービスを使いはじめるには、今まで使っていた何かを止めないといけないし(自分はtumblrのdashboardをみはじめてdiggを見るのを止めた)、今使っているサービスを、新しいサービスを使うために使わなくなったりする(twitterをつかいはじめてmixiをやめた、とか)。
時間は有限なのだ。

アバウトミーにいくつか自分のフィードを登録してひとつにしてみたとき、自分がインターネットにかけている時間だと、一日にせいぜい20~30くらいのアクティビティしか生成できないことにきがついた。ブログは1日に書けても1~2, ブックマークで5~10, twiterで10~20くらい。逆に言えばソーシャルなCGMサービスは、一人のユーザが一日にこれくらいしか生成できないデータを、自分のところにどれくらい集められるのか、を巡って争っていることになる。たいへんそうだ。

でも、そこにLast.fmの再生した曲リストを入れると、一日のアクティビティは軽く100を越えるのだった。Last.fmの再生した曲リストはiScrobber経由で自動的に送られる。だから一度インストールしたらもう後は時間がかからない。新しく使い始めてもらうのに、今まで使っているサービスのどれかを止めて、その浮いた時間をあててもらう必要もない。

Wakoopaもそうだ。
いちどネイティブのアプリケーションを入れてしまえば、あとはキカイが自動で使ってるソフトウェアのログを取ってサーバにアップロードし続ける。ユーザの習慣の一部を変えてもらって(習慣を変えるのは大変なことだ。だからみんなテレビを見続けているし、メルマガを読み続けてるし、いまどきブログなんか書いてるし、朝早く起きれないでいる)、習慣的に使ってもらうだけの時間を作ってもらう必要はなく、一度だけ気まぐれでアプリケーションをいれてもらうだけでいい。

Wakoopaはともかく、この、ユーザの行動を自動で収集してコンテンツにするアプローチは、使ってもらうのに時間がかからないので(本当にゼロだ)、ユーザに習慣を変えてもらうような絶望的な努力を要求しないし、なによりスケールする。twitterはじめたからmixi使わなくなった、はあっても、Wakoopaをはじめるのになにかを止める必要はない。そしてtwitterをはじめたからといってWakoopaを使うのを止める必要もない。ユーザの行動を自動で収集してコンテンツにするアプローチのものは、ユーザにとって最も貴重で、(先進国においては)最も高価なリソースである時間を巡って競合することなく、いくつでも使うことができる。マシンのリソースが許す限り。

i-modeのあとに残されたもの

キカイでデータを集めるとして、じゃあ何がおもしろいかな、と思って、クリップボードのデータをモニタしてアップロードするやつとか(ControlCがほんとにまじめにやってるのを知ったときにはアホかと思った)、ライフスライスのように定期的にスクリーンショットをとってアップロードするやつとかをぼんやり考えてた。

去年のおわりにちょっとはやったMyMiniCityも、ニンゲンははじめにアカウントをつくってどこかに設置した後はキカイが全部やってくれる。MyMiniCityこそがUGCの最終形態 – webdogと書かれ、その中に

UGCサイトで大切なのはとにかく、ユーザーに手間をかけさせないこと。

という文章を発見したときにこのno-overheadはなにかのキーなのだと再認識した。

昔(2001年くらい)T氏が、彼の先生がi-modeの成功は普段の生活の中にある細切れのわずかな時間を積み上げることを可能にしたことだというようなことを言っていた、と言っていたことを思い出す。i-modeはユーザの今まで何にもならないまま残されていた分単位の時間に入り込んだ。いまでは空いている1分に満たない時間ですら、ケータイでともだちにメールしてみたり、R25のニュースを読むことに費やされる。だからもうユーザには1分単位の時間ですら残っていない。

でも、必要な時間がゼロ、という世界には、文字通り無限の可能性が残されている。

補足 2008.3.11

思ってたより多くの人に読んでもらえたみたいでうれしいです。
mal_blue@tumblrで、キカイが自動でデータを作る(zero-overhead)のと、ニンゲンがデータを作るのを楽にする(less-overhead)は分けて考えるとより考えが深まる、と指摘をいただいき、自分でも書きながらもやもやしていた部分について考えるきっかけになったのでご紹介しておきます。

See Also

盛り込みたかったけどうまく入れられなかったもの。

Memory Plus 生ログ – 気紛 – きまぐれ -
キカイで自動的になにかをするのは、必然的にログをとることにも通じている。ほとんど偏執的ロガーのように思える研究者たちの話。
I am gathering you
スクリーンショットライフスライス。

One more Tombloo patch

TomblooをFirefox3で動くようにするパッチをちょこっと改良して追記。拡張機能をインストール/アンインストールして再起動したあとの1回目でTomblooが機能しないのを解決しました。

components/tombloo.jsのいちばんはじめのほうで

const ExtensionManager = getService('/extensions/manager;1', Ci.nsIExtensionManager);

と代入してしてExtensionManagerを利用すると、たぶんXPCOMの追加/削除が行われたあとでcompreg.datなんかを再構築するときのファイル読み込み順依存でnsIExtensionManagerが利用できる/できないが決まって、それがconstに代入されちゃうので、ほんとはXPCOMの初期化が終わったあとに呼び出されるのでnsIExtensionManagerは常に利用できるはずのTomblooServicecreateInstanceの中でもExtensionManagerが存在しなくてこけてるようでした。

XPCOMコンポーネントから他のXPCOMを使うときは、利用可能になるタイミングを意識しておく必要がありそうです。今回はcreateInstanceの中で使うからかんたんに解決できるけど、registerSelfみたいな初期化の過程で呼ばれるようなところの中で使いたかったらどーするんだろう。

spockを使ってコマンドラインでFirefox3のaddonに署名する

注意 2008.02.27

この記事では単純にnss_sign_dataupdate.rdfをリダイレクトしていますが、リダイレクトする前にいろいろ整形してから入れる必要があります。整形せずに入れても何らかの結果は出力されるので勘違いしていました。

整形の方法が不明な状態です。

さぁ、McCoyをはじめよう!! で紹介されているMcCoy – MDCを使うとFirefox3の拡張機能に署名することができます。

自分はいままで拡張機能をパッケージ、アップロードするのにMakefileを作ってコマンドラインから行っていたので途中にGUI作業が入るのが煩わしくて何とかなんないかなーと思っていたら、やっぱりおんなじことを思ってるひとがいるわけでFirefox 3, Dr. McCoy and Mr. Spock « Snippets code from my daily experience経由でspock | hyperstructというコマンドラインでの署名ツールを発掘しました。
StarTrekネタらしいけど知らないのでわかんないけどDr.McCoyとMr.Spockでspockっていう名前らしいです。

秘密鍵の生成自体はMcCoyで一度やってあげる必要がありますが、一度生成すればあとはMcCoyなしでspock単体で署名が可能です。

How to build nss_sign_data on OSX10.4

spockはCで書かれたnss_sign_dataという名前の暗号化プログラムと、それをRDFに書き込むrubyのスクリプトでできています。Debian Sidのバイナリがパッケージされていますが他のプラットホームで使うなら自分でビルドする必要があります。

OSXでバイナリを作ったのでバイナリとビルドの方法を書いておきます。

nss_sign_data binary for Firefox3b3/OSX10.4
nss_sign_data
  1. nightlyからMozilla1.9用のxulrunner SDKをゲットしてきて展開します。
  2. OSXのダイナミックリンクのパス解決の方法がよくわからないので、そんなことしなくてよさそうなのは承知の上でFirefox3のdylibをコピーしてきます。
    cp /Applications/Firefox.app/Contents/MacOS/*.dylib .
  3. Makefileを修正します。
    --- Makefile.org        2007-11-02 02:55:02.000000000 +0900
    +++ Makefile    2008-02-22 20:32:23.000000000 +0900
    @@ -1,6 +1,5 @@
    -CFLAGS=-Inss -I/usr/include/nss/ -I/usr/include/nspr
    -#LDLIBS=-lnspr4 -lplds4 -lplc4 -lnss3 -lsoftokn3 -lsmime3 -lssl3
    -LDLIBS=-lsmime3 -lssl3
    +CFLAGS=-Inss -I/usr/include/nss/ -I/usr/include/nspr -I./xulrunner-sdk/sdk/include
    +LDLIBS=-lnspr4 -lnss3 -lsmime3 -lplc4 -lnssutil3 -lplds4 -bind_at_load -L.
     OBJS=nss/secpwd.o nss/secutil.o nss/secerror.o nss/pppolicy.o nss/moreoids.o nss_sign_data.o
    
     all: nss_sign_data
    
  4. makeしてできあがり。
  5. How to use?

    em:updateHashにXPIのハッシュを記入済みのupdate.rdfをパイプでnss_sign_dataに入れます。

    $ cat update.rdf |  ./nss_sign_data ~/Library/Application\ Support/McCoy/Profiles/o0dla9hp.default
    MIGTMA0GCSqGSIb3DQEBDQUAA4GBAFTB1l6Fm1Zg8Tq3cFLihfZ+y7PnSjkAy7w34DA6+uQ6bsw1H9fR+UQnWwdm7UjjhiXUNV2FCGyLTACZRf7xGtNtnmSRKTaZ868EM1JVHyZr15CAzVqO3TPNyjcwppM3e4wb+BsRZiqhc5f/lTGGb9FppFNNEWu+e94iPkUl5tZ2
    

    でてきた文字列をem:signatureに書き込んだら署名完了。

    ほんとうはrubyのスクリプトのspockを実行すればem:signatureの書き込みもやってくれるのですが、rubyを使ったことがないのもあってlibxmllibxsltのruby bindingを入れるところがさっぱりわからなくて(gemでlibxml-ruby, libxsl-rubyを入れたけどsymbol not foundとかが出たりした)そこのところは自分でスクリプトを書いてやることにしました。rubyに詳しい方、ぜひ教えてください…

    というわけで今はMcCoyでem:signatureが入らない問題解決のXSLT+spock+perlでパッケージング、署名をしています。

TomblooをFirefox3で動くようにするパッチ#2

補足 2008.3.5

Tombloo0.1.2からFirefox3でも動くようになっているので、動かないときは新しくするといいですよ。

更新 2008.2.27

拡張機能インストール後の再起動時に動かない問題はtombloo.jscreateInstanceの中で改めてnsIExtensionManagerを取得するようにしたら解決しました。下のdiffに反映してあります。

補足 2008.2.22

Tomblooを入れて再起動してはじめのときだけじゃなくて、Tombloo以外の拡張機能でも、入れて再起動したはじめのときには動かなそうです。

TomblooがFirefox3で動かなくて困ってたけどちょっといじったら動くようになった。

Firefox3でnsIScriptableInterfaceInfoがなくなったのでそれを使ってるあたりでエラーが出てるのと、あとなんかインストール後の初回起動時にだけExtensionManagerがtype errorを出しててなんでなのかは知らない。例外をcatchするようにしたらインストール後初回起動時には動かないんだけど、もう一度再起動すると動くようになった。

extensions.checkCompatibilityをdisableにしたらバージョンチェックなくなるんですね。知らなかった。
Change CTRL+N to CTRL+T – MozillaZine Forums

diff --exclude=CVS --exclude='*.o' --exclude=.svn -rwu tombloo-0.1.1/chrome/content/library/00_Components.js tombloo/chrome/content/library/00_Components.js
--- tombloo-0.1.1/chrome/content/library/00_Components.js  2008-01-25 17:31:58.000000000 +0900
+++ tombloo/chrome/content/library/00_Components.js  2008-02-20 12:58:32.000000000 +0900
@@ -48,8 +48,8 @@
   Components.Constructor('@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream', 'setData');
 const InputStream =
   Components.Constructor('@mozilla.org/scriptableinputstream;1', 'nsIScriptableInputStream', 'init');
-const InterfaceInfo =
-  Components.Constructor('@mozilla.org/scriptableInterfaceInfo;1', 'nsIScriptableInterfaceInfo');
+//const InterfaceInfo =
+//  Components.Constructor('@mozilla.org/scriptableInterfaceInfo;1', 'nsIScriptableInterfaceInfo');
 const ScriptError =
   Components.Constructor('@mozilla.org/scripterror;1', 'nsIScriptError', 'init');
@@ -84,6 +84,7 @@
 };
 function createMock(ifcNames){
+  return {};
   ifcNames = [].concat(ifcNames).map(function(ifcNames){
     return ''+ifcNames;
   });
diff --exclude=CVS --exclude='*.o' --exclude=.svn -rwu tombloo-0.1.1/components/tombloo.js tombloo/components/tombloo.js
--- tombloo-0.1.1/components/tombloo.js  2008-01-29 12:31:42.000000000 +0900
+++ tombloo/components/tombloo.js  2008-02-20 12:58:04.000000000 +0900
@@ -74,7 +74,7 @@
 }

 function getContentDir(){
-       var dir = ExtensionManager
+       var dir = getService('/extensions/manager;1', Ci.nsIExtensionManager)
                .getInstallLocation(EXTENSION_ID)
                .getItemLocation(EXTENSION_ID).QueryInterface(ILocalFile);
        dir.setRelativeDescriptor(dir, 'chrome/content');

diff --exclude=CVS --exclude='*.o' --exclude=.svn -rwu tombloo-0.1.1/install.rdf tombloo/install.rdf
--- tombloo-0.1.1/install.rdf  2008-02-02 20:19:16.000000000 +0900
+++ tombloo/install.rdf  2008-02-20 12:59:55.000000000 +0900
@@ -6,17 +6,16 @@
     <em:id>tombloo@brasil.to</em:id>
     <em:type>2</em:type>
     <em:name>Tombloo</em:name>
-    <em:version>0.1.1</em:version>
+    <em:version>0.1.1p1</em:version>
     <em:description>Tumblr Utilities</em:description>
     <em:creator>to</em:creator>
     <em:optionsURL>chrome://tombloo/content/prefs.xul</em:optionsURL>
-    <em:updateURL>http://www.asahi-net.or.jp/~xe4r-kmt/extension/tombloo/update.rdf</em:updateURL>
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>2.0</em:minVersion>
-        <em:maxVersion>2.0.0.*</em:maxVersion>
+        <em:maxVersion>3.0.0.*</em:maxVersion>
       </Description>
     </em:targetApplication>
   </Description>

json2infogami siteinfo format converter

jsonで書いてあるSITEINFOを、infogamiのフォーマットにあわせるの、いつも手作業でやっててアホだったのに気がついた。

function json2infogami(firstarg) {
   var infogamiEscape = function (v) { return v.replace(/([_\*])/g, "\\$1" ) };
    var textfilter = infogamiEscape ;
var a =  (firstarg.constructor.name == "Array") ? firstarg : arguments;
    var r = [];
       for(var i = 0; i < a.length; i++) {
                 var siteinfo = "";
         for ( var n in a[i] ) {
            var v = a[i][n];
            siteinfo += n + ":    " + textfilter(v) + "\n";
      }
      r.push('<textarea class="autopagerize_data" readonly="readonly">\n' + siteinfo + '</textarea>\n');
    }
    return r.join("\n\n");
}

配列を渡してもいいし、引数として複数書いてもいいです。argumentsがArrayのインスタンスだったらいいのにね。これをFirebug consoleで実行する。


copy( json2infogami( 
{
url:            'http://blogs.yahoo.co.jp/(.+)/GALLERY/.+',
nextLink:       '((//p[@class="forwardNext"]/a))[2]',
pageElement:    '(//div[@id="newestImages2"])[last()]',
insertBefore:   '(//div[@class="clearFix pagingNavi2"])[last()]',
exampleUrl:   'http://blogs.yahoo.co.jp/kyo_he_piece/GALLERY/gallery.html?fid=0&p=12',
},

{
url:            'http://blogs.yahoo.co.jp/(.+)/MYBLOG/yblog.html.+',
nextLink:       '((//p[@class="forwardNext"]/a))[2]',
pageElement:    '//div[@class="entry"]',
insertBefore:   '(//div[@class="clearFix pagingNavi2"])[last()]',
exampleUrl:   'http://blogs.yahoo.co.jp/boooy2005/MYBLOG/yblog.html?fid=0&p=12',
}
) );

copyはさっき知ったので入れただけ。クリップボードに文字列をコピーする。Firebugの出力コンソールは改行とかスペースとか変になったりするので、そういうときにcopyは便利だ(とおもう)。

こういうのはどういう風に使うといいんだろう。userchrome.jsなんかで window.FirebugCommandLineAPI.prototype.json2infogami に追加してFirebugのconsoleAPIにしておくのが便利かなあ。でも追加しても使おうと思ったときには絶対名前とか忘れてると思う。囲んだ部分をjsonとして渡して起動するブックマークレット?元のデータがvim上でみてるやつのときには不便。

Gyazowin tumblr for Windowsにファイルのアップロード機能を追加して名前をGyamblr for Windowsに変更

前に作ったGyazowin tumblr for Windowsに、ファイルのアップロード機能をつけて、ローカルの画像やテキストファイルをアップロードできるようにしました。

Gyamblr for Windows (was Gyazowin tumblr for Windows)のページからダウンロードできます。

ほんとうははじめI am gathering youを読んでから、OSの持ってるクリップボードのデータのログを取るクリップボードソーシャル、というのをネタで考えていたらTechCrunch Japanese アーカイブ » ControlC―カット&ペーストをウェブサービス化にほんとにまじめに作ってるやつが載ってたのに触発されて、Windowsのコンテキストメニューのコピーとかにcopy to tumblrみたいなのを追加できないかなと思ったんだけど、標準のやつはともかくアプリケーションの右クリックはアプリケーション管轄で制御されていてWindowsシロウト同然の自分には外からいじれなそうだったのであきらめました。

かわりに、じゃあどうやったらかんたんに普段使っているデスクトップとtumblrとをつなげられるかを考えて、で、エクスプローラの送るメニューがわりと便利かなあというわけで調べたらGyazowinにもともとファイルのアップロード機能がついていたのでまたちょっといじって画像だけじゃなくてテキストファイルもアップロードできるようにしてみました。文字コード変換する方法がわからなかったところで一番苦労して嫌になりました。なんかすごい細かくコンパイラに型が違うっていわれたし。BYTE*char*は昔はてきとうにうまいこと流してくれた気がしたけどデフォルトの警告レベルか定義の中身が変わったのかな。

関係ないけどControlCを使ってみての感想

2日ほど使ってみて、アイディアはともかくControlCの実装は使い物にならなかった。名前の通りCtrl-Cを押したときに、そのデータがサーバに送られるのだけれど、カードの番号とかパスワードとか、公開されるとまずいデータがコピーされることもあるので、データ自体はアップロードされるんだけど、デフォルトでは非公開に設定される。あとでブラウザでアクセスして中身を確認して設定を公開に変えて初めて他のひとから見られる状態になる。

やってみるとわかるけど、ふつうに作業をしていると、クリップボードにはどうでもいいようなデータが流れている。文章をかーいているときに、やっぱりこの順番のほうがいいな、と思って並べ替えたときの文章だったり、コードを書いているときにコピーしたひとつの行だったり、同じように#0063DCとかだったり。
ライフログとしてログを取るのは楽しいかもしれないけれど、人に見てもらうような価値はまったくない。どうでもいいようなデータばかりが流れるクリップボードを、もしかしたらコピーしているかもしれないカード番号をチェックするために、手動で確認して公開/非公開を変更するのも no-overhead blogging という観点からあり得ない。

あとから公開したいデータをちまちまマウスで選ぶくらいなら、はじめからCtrl+Alt+Cを押したらコピー&アップロードされるような実装にしたらいいじゃんか。はじめの、クリップボードのログを取るというアイディアにこだわりすぎていると感じた。

今回Windowsのコードを書いていて、もうWindowsのコードを書ける自信がなくなったけど、単純に、起動したときにクリップボードに入ってるデータの型に応じてアップロードするやつを作って、それのショートカットをCtrl+Alt+Tとかで登録しておいて、アップロードしたいときに押す、というのでいいんじゃないかなと思いました。

Tomblooハックス – ポスト先にはてなダイアリーを追加する

Tombloo 0.0.10.1をベースに、LinkとQuoteをはてなダイアリーにもpostするやつを作ってみました。

tombloo@brasil.to/chrome/content/library21_HatenaDiary.jsをコピーして、30_Tombloo.Service.jsにちょこっと下の行を付け加えて、あとabout:configextensions.tombloo.posterFilter(Tumblr|HatenaDiary)にしたら動きます。

--- library/30_Tombloo.Service.js       2007-12-04 00:01:04.000000000 +0900
+++ /Users/kuma/tombloo.lib/30_Tombloo.Service.js       2007-12-21 22:50:56.000000000 +0900
@@ -115,6 +115,12 @@
                                return FFFFOUND.post(params);
                        }
                },
+               HatenaDiary : function (ctx, params) {
+                       if( params.type != 'link' && params.type != 'quote' )
+                               return succeed();
+
+                       return HatenaDiary.post(params);
+               }
        },

        extracters : {

つくりかた

Tomblooの対応サービスを増やすのはかんたんに書けます。

クラス名を決める
クラス名はファイル名と結びついてるみたいです。今回はHatenaDiaryという名前にしたので、作るファイルの名前は21_HatenaDiary.jsでした。数字の部分は読み込まれる順番を制御しているだけなのでなんでもokです。
Tombloo.Serviceに追加する
Tombloo.Service.postersにクラス名でメソッドを作ります。サポートしているtype(Tumblrでいう7つのコンテンツの種類です)じゃないのがきたときにはsucceedを返して、それ以外のときはpostメソッドを呼びます。別になんでも呼べますが慣習としてpostなのでpostにしておくのがいいでしょう。
postメソッドを書く
postメソッドにはtypeに応じてパラメータがくるのでそれをHTTPでPOSTするデータに変換してdoXHRを呼ぶだけです。ここでも慣習として

HatenaDiary[capitalize(params.type)].convertToForm(params);

というかたちで変換するのがいいでしょう。

タイプごとのconvertToFormを書く
typeに応じてどういうデータをpostするかを書きます。HatenaDiary.Quote = { convertToForm: function (m) { .... } } こんなかんじでサポートしたいtypeぶんだけ書きます。

これでできあがり。

かんそう

対応サービスを増やすのは非常に簡単でいいかんじになってます。常にextensions.tombloo.posterFilterに書いたサービス全てに反映されてしまうのがやや不便ですが、そこはUIで解決できると思います。postするtypeを選んだあとにもう一段そのtypeをサポートしているサービスからひとつを選ぶメニューをつけるとか、あらかじめ7つのタイプ * サービスのマトリクスをつくっちゃうとか。

こんなかんじ、っていうのがさらっと書けていいかんじでした。

ブログがウェブページの構造を規定したことで、それを作るためのツール、プラットフォームができて便利になったのと同様、コンテンツを7つ(7つじゃなくてもいいんだけど)に規定することでなにかが見えそうな気がします。出てくる問題が1-1/N-1/N-Mの関連付けをどうするか(ひとつのエントリでひとまとまりの曲を紹介したいときはどうするのかとか)、DBMSで出てくる問題と似ている気がします。