2008/01/31


これはすごい

speeddating.vim - Use CTRL-A/X to increment dates, times, and more : vim online

なんと

  • 2008-01-31 11:51:31
  • I,II,III,IV,V
  • 3rd,4th

等いった日付、時刻、数字表記に対して<C-A>や<C-X>で値をインクリメント/デクリメント出来る様にするという凄いスクリプト。

試しに
2008-01-31 11:51:31
の51の所で30分繰り上げる為に「30<C-A>」とした所、
2008-01-31 12:21:31

と時間まで動くじゃないですか!!!

スバラシス...

そのままでも素晴らしいのですが、このスクリプトの素晴らしいのは、拡張出来る所。例えば

~/.vim/after/plugin/speeddating_japanese.vim
scriptencoding utf-8

SpeedDatingFormat %Y年%m月%d日 %H時%M分%S秒

let s:japanese_number = '0123456789'
function! s:japanized_number(string,offset,increment)
    let n = tr(a:string, s:japanese_number, '0123456789') + a:increment
    return [tr(n, '0123456789', s:japanese_number), -1]
endfunction
function! s:function(name)
    return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
endfunction
let g:speeddating_handlers += [{'regexp': '-\=\<[1234567890]\+\>', 'increment': s:function('s:japanized_number')}]

こんなファイル用意すれば、先ほどと同様に

2008年01月31日 11時51分31秒

の51の所で「30<C-A>」とすれば

2008年01月31日 12時21分31秒

となってくれるのです。

さらに↑の拡張スクリプトでは、全角数字の上で<C-A>/<C-X>するとインクリメント/デクリメント出来る様にする処理が入っていますので、「550」<C-A>を押下すると「551」とインクリメントされます。

素晴らしい。

あー、豚まん食べたくなってきた。

Posted at by




もごもごにURLをポストするツールを作りました。
なかみはTumblrから少し、いや幾分、いやかなりパクってます。

以下がブックマークレットです。
HTML側のソースは以下の様になっています。
ここのサーバにも一応HTMLを置いてはおきますが、いずれ削除するかもしれないので、出来る事ならば適当なサーバに以下のHTMLだけ置いて、ブックマークレットのURLをそこに書き換えて使って下さい。
もちろん、もごもごはSBMではありませんから、たんなるURLお知らせツールにしかなりませんけども...
再配布、改造、などは勝手ながら自由にさせて頂きます。

続きを読む...

Posted at by




C言語を使える奴は凄いのか?


C Language

数年前ならYesだ。でも今は「間違なくYesだ」とは言えない。昨今業務で求められているスキルはC言語使いだけではない。

求められているのはJava使いであり、VB.NET/C#使いであり、LL(javascript、php、python、perl, ruby)使いなのだ。

最近ではエンドユーザも賢くなってきて、単にパフォーマンス性や万が一に備えた柔軟性よりも、保守性やメンテナンス性、二次利用という利点、はたまたバグ侵入率を下げるにはどうしたら良いかを勉強してきている。

まれにC言語使いは、C言語を使えない人達から崇められる事があるが、はたして本当に凄いのだろうか?

ポインタや演算子オーバロードを知っている人が、MFCやATL/WTLを知っている人が、socketをCで組める人が、kernelソースを読める人が、本当に今後も凄いと言われ続けるのだろうか。

例えばGUIを作るとしよう。pythonとGTKを使えばウインドウを出すのに10行あれば十分でしょう。

ソケットで通信してXMLを送受信するならばperlで10数行でしょう。

SHA1で長々とコードを書くまでもなく、.NET Frameworkならば、予めクラスライブラリが用意されているでしょう。

昨今ではアプリケーションのプラグインもスクリプト化しつつある。

mallocとfreeを追っかけ徹夜する開発者と、コーディングと数回のデバッグで仕事を終え、定時で帰ってしまう開発者のどちらがカッコいいのでしょう。

「言ってるその言語自体はC言語で出来ているんだ」
そうでしょうね。でもあなたが作った訳じゃないですね。
「C言語じゃないと出来ない処理だ」
そうですね。ただ、それが必要な処理ならば、いずれ誰かがクラスライブラリを作ってくれるでしょうね。
「他の言語は今後仕様が変わってしまう可能性がある」
そうかも知れませんね。きっとその頃には、そのC言語で作られたシステムも作り変えですね。

確かにハードやkernelまわり、組み込み系ならばC言語でしょう。でも今後デスクトップアプリは他の言語に置き換わって行ってしまうかもしれない。

C言語はハードの制御と新しい言語の開発だけに使われ、デスクトップアプリケーション開発で御飯を食べるのが難しくなってしまいC言語使いの仕事と言えば組み込みだけになってしまう近未来が本当にやって来てしまうかも知れない。

C言語使いの凄いところってなんだ?

あるとすればクラッシュの仕方一つで、なんとなく原因が想像出来てしまう事。

時間が掛かる処理で、なんとなく無駄なループのソースが見えてしまう事。

そんな知識がいったい何時まで「凄い」と言われ続けるんだろうか…

「C言語だけでは御飯が食べられない」そんな将来がやってくるのだろうか。


そして近い未来、こんなネタで釣りな記事が@ITやITmediaに掲載されてしまう日が来るんだろうか。

Posted at by




はてな匿名ダイアリー:
「某大手国立大学卒業、25才、大手IT系企業に勤める優秀な社員が今感じてる閉塞感をリアルな言葉にしてやるよ」


明日休むつもりで、今日頑張ってみようよ。明日まだ倒れて無かったら、も少しだけやってみようよ。

私生活犠牲にしてまで働く事に、どんな意味があるのか教えろと言われれば、そりゃ君が選んだ仕事がそういう仕事なんだから仕方がない、としか言えないけど。

数年後、絶対笑える話になるんだよ。「あん時はキツかったー」って。

続きを読む...

Posted at by




コードを晒け出して下さい。
プログラミングに自信があろうと、無かろうと。
貴方の書いたコードを見たいと思う人がいなかったとしても、晒け出して下さい。

私はオープンソースであることは、素晴らしい事だと信じています。たとえそれが、ちっぽけなサンプルプログラムであっても、人に見せる事で自分の書くコードに「見られる」という観点が入り込み、色んな見えかたが出来るようになります。さらに運が良ければ人からレビューしてもらえるんです。
私は、会社の部下、他社様の若手の方々によく「オープンソースコミュニティに参加しましょう。英語が分からなかったら日本のコミュニティでいいので入りましょう。」と言ってきました。これからも変わりません。
これは、単に「勉強しなさい」と言っている訳でなく、オープンソースコミュニティの特性がコーディング能力を養うには非常に効力があると信じているからです。
オープンソースコミュニティでは、修正ソースを送る際にはパッチ(patch)と呼ばれるソース差分を送る事になります。受け取った側は、そのパッチを一度適応してみて、間違いが無いかを確認します。そのコミュニティがMLを持っているならば、MLに参加している皆にそのソース差分が送付されます。
そして皆が自分の書いたコードに目を通すんです。

「恥しい...」そんな事を思ったのならば勿体ない話です。もちろん腐ったソースコードを人に見せるのはどうか、という議論もありますが、せっかく人に見てもらえるんだから見てもらわなくては損ですよ。

「自信がない...」そんなもの、みんなそうなんです。私だって自分の書いたコードに100点なんかあげられません。でも私は晒け出すんです。間違いを指摘してもらえるんなら、ありがたい話です。
自分で書いたプログラムの間違いを見つけるのは一苦労です。人に客観的に見てもらった方が間違いには気づきやすかったりします。そして人のソースも見ることで自分のソースコードの貧弱さに気づいたり、時には自信につながったりする物なんです。
#海外のコミュニティで自分が書いたソース差分が初めて取り込まれた...なんて事あればドキドキしますよ。


それはそうと、ここ最近「どう書く?org」というサイトを時間を見つけては覗いています。
ここでは、プログラミングに関する出題に対して、色々な言語の使い手が色んな手法で回答していきます。面白いのでfeedも登録しています。
電車で帰宅する際にも携帯から「へぇ...こんな書き方もあるんだ」と眺めてます。
一見コード書きに自信のある人達の集まりに見えますが、おそらく皆自分のコードに100点を付けて回答している訳ではないと思います。(HelloWorldは除きます)
「もしかしたら間違ってるかも」と思いながら回答している方も多いと思います。でも晒け出すんです。「俺こんなコード書けるんだぜ」なんて自慢大会じゃないんです。プログラミングを楽しんでいるんです。自分の書いたコードを見てもらい、たった一人でも「へぇ」と思ってくれる人が、もしかしたらいるんじゃないか?そんな気持ちだと思います。

コードを見せる人によっては、時には「そんな書き方、2億年前からあったよ。」とか「それ実行すると10秒後にはホームディレクトリが無くなってるよ」なんて手厳しいコメントを貰うかもしれません。でもそれって有り難い話。人に見せない限りそんなコメントは貰えません。


人に見せてなかったら、今頃貴方のハードディスクからホームディレクトリが無くなっていたかも知れませんよ...
Posted at by




GIGAZINE - 死んだマリオはいったいどこへ行くのか?

アンパンマンの顔も何処に行くんだ?あれ、生ものだぞ...
Posted at by




最近、ネットを見ていると「人の話を上手に聞くコツ」とか「相手を納得させる上手な説明方法」なんて記事を良く見る。

確かに参考になるし、なるほどねと思う事がある。まだ実践した事はないが、使って見る価値のありそうな物もある。

もちろんこうした記事には「他者と差別化を図るには?」をテーマに自己啓発する術が書かれていて、それ自身良い読み物だと思うし、アクセス数があがるのも分かる。


でもこの類の話って本来、会社の上司から教わったりするものだったんじゃないかと。


客先で上手く説明出来なかった時に先輩から「こういうとお客さんに納得してもらえるよ」てな具合に、教えてもらったり質問したりするものだったんじゃなかろうか。

最近では上司の絶対度は薄れ、部下からすると上司のアドバイスは耳の横を通り過ぎる蚊ほどでしかなく。フリーランスや転職が当たり前になってきた昨今では、半永続的にな信頼を抱く上司よりもネット…な風潮になって来ているのかも知れない。

もしこの流れが真実だとしたら本当に淋しい事だし、はなから失敗を恐れ教科書通りの行動しかしない部下を持つ上司の苦労が想像出来てしまう。

もちろん度合いにもよるが、許される内ならば自分のやり方で失敗して見ても良いと思う。最近はそこまで寛大な会社も無いかも知れないけと、良かれと考えての事なら教科書と違ったやり方を貫いても良いと思し上司に聞いてもいいと思う。


もしくは、そんな事が許された頃に新人だった私は、運が良かったのかもしれない。

Posted at by




ちょっとポップな感じに変更しました。

実は、blosxomを捨て、MTに浮気しようかと思ってました。
理由は、管理画面。
確かに誰にも見られない、秘密の管理画面ってなんとなく魅力的に見えてしまったのですが、よくよく考えたら存在意義が無い事に気づきました...orz
Posted at by




あまり事情も詳しくなく、業種もWebエンジニアではないのでソースは貼らないでおこうと思います。

私はWebエンジニアではありません。確かに仕事でWebもやりますしデザイナさんが書いた画面で製造した事もあります。

色々なブクマを拝見させて頂いて、少しだけ話がそれ始めているんじゃ?と思う事があり、この記事を書いています。

エンジニアとデザイナの対立


一件デザイナさんの世界はチャラけててオシャレな物に見えますし、実際にそうだったりするかもしれない。イベント/パーティ開いて今後のWebをマーケティングと絡め、Webをリードしていくのはデザイナだ!と言ってる風に見えるかも知れない。

それに比べてエンジニアはデザイナの書いた画面を、いかにデータベースと結び付け、ハッキングし、速度劣化を防ぎ、セキュリティホールを塞ぎ、効率良く開発およびテストするかを考え、リードというよりはフィードバックで貢献しているように見える。
私は今回の件を傍観者としてこんな感想を抱いた。

デザイナさんとはエンジニアの事なんて気にせずWebのマーケティングについて模索し続け、業界人と呼ばれてリッチな生活を送るのもある意味良いと思う。イベント/パーティで儲けてもいいと思う。もちろんエンジニアを卑下する事は許されないけど。
ああいったイベント/パーティは、内面的にもWebをビジュアル的に感じられ、いわば「俺達がWebをリードしてやるんだ」くらいの気持ちになれ、興奮出来る要素が必要なのかもしれない。
デザイナさんの中には「エンジニアは俺たちが描いた構図をただ実装してりゃいいんだよ」なんて言う人も居るかもしれない。

私は、あながち間違ってないと感じる。

専門は専門家に任せるべきであって、デザイナは表面を作りエンジニアは内面を作るのが良いと思う。デザイナとエンジニアの間で摩擦が起こる事は、良い物を作る上で不可欠だと思う。それがWebの発展へ繋がると信じてます。

イベント料金について


イベント料が高いという意見は企業としてではなく個人の意見なら全く問題ないと思う。
人によっては安いと感じるかもしれないし、参加してよかったという意見もあるでしょう。
そんな意味で今回、開催者側が収支を公開されたのは私にとってある意味ショックな事でした。できれば「有料イベントですから...」くらいで返すべきではなかったかと思います。

また今回、幾らかの方が不信の念を抱いた件と、amachang氏が不信の念を抱いた件は明確に別件で、混同する事は両者にとってマイナスになり得ると思いました。どちらの件も別途議論すべきです。
もちろんエンジニアがWeb界をリードしていないという訳でなく、amachang氏を始めとする多くの技術力の高いエンジニアがWebを、また開発手法をリードしていって下さっています。

今回のイベント/パーティが明確に招待客のターゲットを決めておられたならば...
Webの新しい世界を精神的の感じたい人、デザインでマーケティングが生まれる事を認識したい方をターゲットにしていたならば...
このイベント/パーティは元々ギークな人間には向かなかったんじゃないかと感じました。


少なくとも私は、ボタンの位置を数ピクセル移動しようか一日迷う仕事よりは、一日中javascriptを書いている方が興奮を得られる方の人間です。
Posted at by




突然ですが、サーバを解約しようと思います。
2006年の1月からですから、ほぼ2年ここで色んな事を書いた気がします。

最近は、はてなブックマークdel.icio.us等でもブックマークして下さる方も増え、このサイトを消してしまうのは惜しい気もしますが、また新たな場所で始めたいと思っています。

つきましては現在、移転先を探しています。
これまで、blosxomというblogツールと自己拡張でやってきましたが、次のサイトにそれほどこだわりはありません。
選択肢に、はてなダイアリーや、Voxも考えています。
もちろんCGIが動けば尚の事良いのですが...
ちなみ私、小遣い制度のためお金はありません。自鯖が持てる程の環境もありません。
これまで書いた資料性のある記事は、再度アップしたいとも思います。

皆さん、良い所知りませんか?

コメント、ブックマークコメントお待ちしております。

Posted at by




最近、すごく感じている事。

「調整って言葉すごいな」って事


どの職業にもある言葉なんですが、ことIT業界においてはこの「調整」って言葉、結構頻繁にやり取りされるんですよね。
まず良く出てくるのが...

続きを読む...

Posted at by




なんだかShibuya.pmとやらで、みんなスライド使っててカッコイイな...
と思ってたら、ついカッとなって自分もスライド作ってみたくなった。

続きを読む...

Posted at by




私のRSSリーダ(Firefox/Sage)には、サイトの生死確認の意味で、ここのページのRSSが登録してあります。
ココ最近、なんか重たいなぁ...

と思ってました所、めったに見もしないアクセスログ(テキスト形式)が、原因しておりました。

ページアクセスに対して1行程の追加なのですが、しばらく見ない間に13MB程にまで、パンパンに太り上がっておられました。
しかたないので、ロデオボーイに...別名にリネームした所、なんとなくスイスイ動くようになったような気がしました...
Posted at by




最近ずっと、携帯からGoogle Readerを使ってます。

他の携帯向けリーダーと違い、記事を読んだ後「戻る」を押すと記事一覧からは既読記事が消え、次の記事が読みやすくなっています。

記事を未読状態に設定したり、スターと呼ばれるマークを設定し後からPCで確認しなおす事も出来ます。

ただ、まだGoogle Labsを卒業してない事もあり、色々と不都合な点もあります。以下私が気になっている問題です。

●既読記事が参照出来ない

記事に未読設定しないまま「戻る」を押してしまうと、以降PCでログインするまでその記事が読めなくなります。夜中にこれをやってしまうと朝までその記事が気になって眠れなくなります。

●携帯許容サイズを超えて表示する

画像たっぷりの記事を読むとエラーが発生します。GIGAZINEのように「詳細は以下の通り」といった感じでフル記事を飛ばしてあると、Google Reader Mobileのリンククリックで開くGoogle Mobile Proxyで閲覧出来ます。まぁこれは好き嫌いもありますし、フル記事にはドでかいバナー広告が出たりレイアウトが崩れたりする事もありますから、できればGoogle Reader側でなんとかして欲しいですね。ちなみにこのサイトは携帯閲覧可能です。

●出来れば携帯からURL指定でヒィード登録したい

これは単なるわがまま。最近出張が多くて携帯からのニュース&ブログ閲覧が多いので、出来ればURL指定で登録させて欲しい。まぁ携帯からだと、ブックマーク登録する際のURLをコピーして貼り付けるしか術がないので大変ではありますが…

以上3点の気になる問題がクリアされれば、PC版とほぼ同じインタフェースになり、ヘビーユーザにはもって来いなヒィードリーダーになる事は間違ないと思っています。

皆さん試しに使ってみてはどうでしょう?

http://www.google.com/reader/m/





Posted at by




アルファって何だ?

アルファブロガーって何だ?

アルファギークって何だ?

アルファブックマーカーって何だ?


はてなダイアリーキーワードによると
アルファブロガー あるふぁぶろがー
多くの読者に読まれている、影響力のあるブロガー。
しかし英語圏ではこの言葉は定着せず、代わりに「Aリストブロガー(A-list blogger)」という表現が用いられるようになっている。

アルファギーク あるふぁぎーく
「産業を変化させる力を持つ新しい技術に早いうちに飛びつき、ああでもないこうでもないといじくっているうちに、技術が進むべき方向性を示し始める、先鋭的で飽きっぽいエンジニア」(Tim O'Reillyの定義による)。

アルファブックマーカー あるふぁぶっくまーかー
SBMを用いていろいろやってる人たち。
との事。
なんか最近「アルファ」の使い方が、あちこちでずれてる気がする。
「すごい」とか「定評のある」とかで「アルファ」を使ってる人も見る。これってちゃんと定義された物なんじゃなかろうか...

だったら...

昨日、家に帰ってからウチの近所の中じゃぁ「アルファ女房」でもある嫁が作ってくれた、アルファ手料理を頂いた。
家族にも定評のある、「アルファ肉じゃが」で晩酌してると、横で子供がウルトラマンアルファのフィギュアで遊んでた。
なんだかアルファ懐かしくなった。

テレビでアルファ芸人「大島よしお」のギャグ「タンポポ関係ねぇ」を見てたら、眠くなってきたので風呂に入った。
その後仕事してたら、アルファネットウォーカーでもある友人からメールが来て
「お前のブログ、無断リンク禁止だったよな?なんかアルファブックマークされてるぞ」
と教えてもらった。
急いで見たら、アルファブックマークの他に、アルファコメンテーターから複数のコメント、しかもアルファスターが付いてる。
アルファブックマークなんかされたら、もうおしまいだ。明日にもブログを閉じよう。

こんな使い方を肯定している事になる。いかんな。こりゃ。


#なんかアルファ無意味な記事だな、コレ

Posted at by




最近、私になにかと降り掛かってくる、天気雨と言いますか、なんといいますか...

先日、関東のとある場所に出張に行きました。

その会社では、50才を越える人たちが現役でバリバリとプログラミングをしており、かなりテクニカルな質問を私にぶつけ、技術に没頭し、いまもなおプログラミングを愛したまま仕事をされておられる方の集まりでした。

ある意味、カルチャーショックを受けると共に、「うらやましい」と正直に思いました。

私の今置かれている立場や役職では、会社から既にを決められてしまっており、私はその線路の上を走り、知らない間に「営業職」となっていく。そんなが見えている気がするんです。

私は、既に役職的には管理職、ですが未だ現場で開発もします。そしてこの役職になるまでに何度も

「まだ開発をさせておいて下さい」

と会社にお願いして来ました。

しかし会社が私に求めている何かと、私が望んでいる何かは、決して同じ線路ではなかったりするのですよ...ハイ


先日、部下が「僕、技術を辞めて営業になります」と口に出しました。

私は何をおもったか、その彼に

「技術が嫌になったのか?それとも見切りを付けたのか?」

と聞いてしまいました。その彼は「いや、実は昔から営業がやりたかっただけです」と答えました。

この時自分が、「見切り」や「定年説」みたいな、境界線と言うものを、知らずと意識してるのかな...と気づきました。

もちろん自分の技術に、有る程度は自信があります。

未だ、自社の後輩には誰にも負けていないつもりです。

ですが今後、何かを諦めて、人の引いた線路の上を走らなければならないかもしれないと、少しでも考えた自分が、イヤーーーーーな気分になりました。


もう少し。
もう少し。

開発を続けさせてください。
Posted at by




コメント欄まだやってる...
佐藤秀の徒然\{?。?}/ワカリマシェン:小飼弾氏が赤木智弘氏を嗤える本当の理由
日本人にとってソフトウェアが日本語で使えるかどうかは、そのソフトウェア自身の価値を左右する尺度であり、商用ソフトウェアならば直接マーケティングにも影響する。
例えばソフトウェア自身でなくとも、ダウンロードページやチュートリアルが英語というだけでダウンロードを辞めてしまう事もある。
大手企業では日本法人や日本人社員にl10n化やi18n化を任せる場合もある。こういう点で言えばmiyagawaさんも言うように日本人にとってi18n化はJob Securityになり得る。
企業としての立ち位置ならば、それは完全な業務だろう。

最近はあまり活動してないですが、私もこれまで色んなソフトウェアのi18n patchをオフィシャルに提供して来た。もちろん無償で...

例えばvim。私がvimのオフィシャルにpatchを送り始めたのはvimのバージョンが5.6だった頃。それまではMLで英語に苦労しながらvim/cvsheadの追っかけをやってました。それまでのvimは各文字コード毎に実装を個別に対応した処理になっており、今から考えるとそれはひどいコードでした。
私が最初に送ったpatchは「set guifont」で設定したフォントをIMEキャレットにも設定するといった物。Bram Moolenaar氏は適当な英語にも快く答えてくれ、extra patchながら取り込んでくれました。ぜんぜん大したpatchでは無いがftpサーバに「5.6.004」というファイルで置かれたパッチを見た時は飛び跳ねる程嬉しかった。
それからvimには数多くのi18n patchが適応され、現在では内部でutf-8を処理出来る素晴らしいエディタとなった。1文字毎をメモリに持つのではなく全てバイト列で処理すると言った方法の為、他のアプリケーションとは違うi18n化の方法であり特殊ではあるが、逆に言えばvimは壊れたutf-8でも編集出来る様になっている。
最近はBram Moolenaar氏自身が何か新しい機能を取り込む際、マルチバイト文字列に非常に気を使っていてくれて、私がpatchを送る事は殆ど無くなった。でもそれが私の望んだ事だった。

私は、forkが嫌いだ。
私はl10nやi18n化した物をforkとして公開する形が好きではない。出来る事ならばオフィシャルに統合させるべきだと思っている。これはvimのcontribute authorでもあるKoRoN氏も同じ考え。私はl10n化やi18n化はオフィシャルを説得して取り込ませるべきものであって、そうでなければ公開する価値はあまり意味のない物だと信じている。
軽量GUIライブラリ、FLTKには以前、fltk version1をベースととしたfltk-utf8というforkがあったが、私はその頃まだベータ版であったfltk2をutf-8化するpatchを書いて送った。
fltk-utf8の作者には少し申し訳ない気もしたが、このpatchにより現在では当たり前かのようにfltk2で日本語が表示/入力出来る様になった。
良いものは良いものとして後押しするのがベターだと思った。
fltkに限らず多くのソフトウェアがボランティアで作成されており、素晴らしい事だと思う。その多くのエンジニアは見返り等求めない方であって、それをネタに自分のJobを生み出そうなんだ思ってもいない。
dankogai氏のEncodeもperlを後押しする為の物。例えばdankogai氏がperlに手を加えforkとしてEncode対応のperlをリリースしたならば、反論を受ける対象となり得るかもしれない。
しかしそれどころかEncodeはCodeReposにオープン開発という意味で公開されている。つまりdankogai氏はEncodeを独り占めしようとは思っていないと言う事だ。
外国人プログラマがEncodeにpatchを送ったならば、dankogai氏はきっとwelcomeメールを送るだろう。

私のようなちっぽけな開発者にはperlでEncode程のライブラリを作ってオフィシャルからforkさせられる程の実力は無いので大した事は言えないが、ボランティアでpatchや拡張を作っておられる方々に対して"「日本語」という最大最強の非関税障壁に守られた既得権益者"という言葉をあびせるのは明らかに間違っている。

もし「dankogai氏はEncodeをネタに名声を売っている」と言いたかったならば、それは貴方も「dankogai氏をネタに名声を売ろうとした」事になるのではないかな...。

Posted at by




いつもボーと見てるGoogle Readerで、「PR:」と先頭につく広告記事はほぼ読まないのですが、なんとなくこの記事が目に入りました。
第7回 ずっとフリーランスでいられますか?
私はフリーランスではありません。会社で1社員として働いています。
ですが今までフリーランスについて考えた事が無いとは言えず
自分の力でお金を稼いで見たい
と考えた事もあります。もちろん、フリーランスで頑張ってらっしゃる方からすれば、「そんな甘いもんじゃない」と言われるかもしれませんし、「そんな良いもんじゃない」とも言われるかもしれません。
ただ、リンクの記事に目が止まったということは、少なからず私の中で「フリーランス」という仕事の仕方に対して「興味」「期待」「憧れ」「不安」といった葛藤みたいなものが働いていると自分ながら感じています。
私の周りにも、契約社員の方や、自分で会社を起こした方がいらっしゃいます。時に意識したりもします。組織人としては、抜け出す事への不安もありますし、岐路として「フリーランス」、「起業」が同じものにも見える程、自分にとってリスクのあるものに見えます。逆に、組織に属している安心感を得ていたいという気持ちもあります。
もちろん明日からもし「フリーランス」になったとしたら、まず誰を頼ったらいいのかも分からなくなると思います。
この記事を読んで記事内に出てきた「鎌田さん」が本当に実在するならば、「フリーランス」の方で不安を抱いている方が少なからずともいらっしゃるんだ...と少し感慨深くなりました。

岐路点は、今後どのような場面でやってくるかもしれないですが、もしその時が来たらもう一度この件について考えてみたいと思います。
Posted at by




AddClipsを付けてブクマ数激増!!!

続きを読む...

Posted at by




わさび鉄火で...
さて仕事しよう...

wasabitekka
Posted at by




freenodeのsubtech系IRCチャネルはutf8を使う事!

http://subtech.g.hatena.ne.jp/miyagawa/20071024/1193192377
http://d.hatena.ne.jp/yappo/20071024/1193197297
http://d.hatena.ne.jp/tokuhirom/20071024/1193197797
周知。
私が覗いているsubtech系チャネルは、#codereposか#plagger-jaくらいですが...

先日紹介したbitlbeeでは
set charset utf-8
save
でおけ。
周知だけでは面白くもないので今日はIRCにまつわる昔話の適当訳を...


原文:Worlds worst hacker. IRC transcript
IRCにて起きた、ハッカーbitchcheckerの悲しい結末」
現在リンク切れの模様。探してます。
追記
オリジナルが消えている模様。別の所で同じようなものを見つけたので...
http://themostboringblogintheworld.wordpress.com/2006/09/13/worlds-worst-hacker-irc-transcript/

用語
ban:
  IRC用語で、迷惑な発言などを理由に
  部屋からはじき出す事を指す。
  ※kickと同語

IP:
  ネットワーク上のコンピュータを指し示す
  番地を意味し、127.0.0.1は自分の
  コンピュータを意味する。

pingタイムアウト:
  ネットワークが切断された事を意味する。


とあるIRCチャットでの話...

* bitchchecker (~java@....) がチャットを抜けました(タイムアウト)
* bitchchecker (~java@....) がチャット#stopHipHopに参加しました
<bitchchecker> なんで俺をkickしたんだ!
<bitchchecker> お前はまともに討論もできないのか?
<bitchchecker> 答えろ!
<Elch> いや、kickなんかしてないよ

<Elch> 君はpingタイムアウトしたんだ
* bitchchecker (~java@...) がチャットを抜けました(タイムアウト)
<bitchchecker> pingなんちゃらって何の事だ!
<bitchchecker> 俺のPCの時刻は正常だ!
<bitchchecker> サマータイムさえ設定してるぜ

<bitchchecker> お前が俺をbanしたんだ
<bitchchecker> 仲良くしようぜ、このゲス共
<HopperHunter|afk>
<HopperHunter|afk> サマータイムって...ぷぷっ
<bitchchecker> 黙れ!サマータイムを設定してるんだ

<bitchchecker> もう2週間だ
<bitchchecker> Windowsを起動したらサマータイムを設定しろとメッセージが出るだろ!
<Elch> ホントにコンピュータのエキスパートなの?
<bitchchecker> うるさい!お前をハックしてやる!
<Elch> 分かったよ。黙るよ。君がどんなにスゴいハッカーか、私達に見せない為にも...ぷっ

<bitchchecker> お前のIPアドレスを教えろ!お前をオシマイにしてやる
<Elch> え~っと...そう、129.0.0.1
<Elch> あ、いや...たしか 127.0.0.1 だ
<Elch> よし 127.0.0.1 だな。今からスゴい攻撃を仕掛けてやるから待ってろ
<bitchchecker> 5分以内にお前のハードディスクは全部削除されるぞ

<Elch> うわ!やられる~
<bitchchecker> 黙れ!お前はもうすぐオシマイだ
<bitchchecker> お前のIPをプログラムに入力したぞ。お前はもう死んでいる...
<bitchchecker> じゃぁな
<Elch> 誰に言ってるの?

<bitchchecker> ってにお前だよ
<bitchchecker> バイバイ
<Elch> うわ~...君がそんなスゴいハッカーだったなんて...ガクガク(((( ;゚Д゚))))ブルブル
* bitchchecker (~java@...) がチャットを抜けました(タイムアウト)
数分後...
* bitchchecker (~java@...) がチャット#stopHipHopに参加しました

<bitchchecker> 俺のPCがたまたまクラッシュしたから、命拾いしたな
<Metanot> ぷゲラwww
<Elch> じゃぁ、もっかいハックしてみろよ。IPは同じ127.0.0.1だ。
<bitchchecker> バカが...
<bitchchecker> バイバイ!

<Metanot> あ~、[閲覧禁止文字]
<bitchchecker> バイバイelch
* bitchchecker (~java@..) がチャットを抜けました(タイムアウト)
* bitchchecker (~java@..) がチャット#stopHipHopに参加しました
<bitchchecker> elch、このクソ野郎!
<Metanot> bitchchecker、君幾つ?

<Elch> ご機嫌いかがbitchchecker君?
<bitchchecker> ファイラーウォールなんか使いやがって
<bitchchecker> ちがう、ファイアーウォールだ
<Elch> あ~良く知らないけど、たしか
<bitchchecker> 俺は26歳だ

<Metanot> 26でそれかよwww
<Elch> どうやって僕がファイアーウォールを使ってると?
<Metanot> 僕ちゃんお行儀が悪いよ
<bitchchecker> 俺の送った電源強制終了信号を、お前のファイアーウォールが俺側に跳ね返したんだ
<bitchchecker> その糞ファイアーウォールをoffにしやがれ!

<Elch> カッコイイ...そんな事できるなんて知らんかったよ
<bitchchecker> ふっ...俺のウイルスでお前のPCを破壊してやる
<Metanot> 君をハックするの?
<Elch> あ...bitchchecker君は僕をハックしようとしてるんだ
<Metanot> bitchchecker君、あのさ、もしハッカーなんだったらファイアーウォールなんかすり抜けられるよね。って僕でも出来るよ。

<bitchchecker> あぁelchをハックしてるんだが、この糞野郎がファイアーウォールを...
<Metanot> どんなファイアーウォール?
<bitchchecker> 女々しい奴め
<Metanot> 普通の、ごくごくありふれたハッカーだったら、ファイアーウォールぐらい突破するもんだが...あんたが女々しいのか...
<He> 僕~、自分のでもしゃぶって、まぁ落ち着け。挑発にのってネタにされてるだけだぞ。

<bitchchecker> ファイアーウォールをoffにしろ。そしたら今すぐ俺様がウイルスを送って[閲覧禁止文字]してやる
<Elch> ヤダ
<Metanot> ってかなんでoffにさせんの、君がoffにすべきだろ
<bitchchecker> お前は怖いんだ
<bitchchecker> んなファイアーウォールの陰に女々しく隠れている奴なんか俺様はハックしないさ

<bitchchecker> elch、今すぐoffにしろ!
<Metanot> 色々言いたいことはあるんだが...まずハッキングの意味わかってる???彼がoffしたらそれは侵入を招待してる事になるんだぞ?その時点でハッキングじゃないだろ
<bitchchecker> うるさい!
<Metanot> www
<bitchchecker> 俺の婆ちゃんはファイアーウォールでネットサーフしてる

<bitchchecker> お前らは自分らがクールだと思ってるだろうから、あえてファイアウォールなしでインターネットに出るなんてしないだろけどな
<Elch> bitchchecker君、友達がファイアーウォールをoffにする方法を教えてくれたよ。もっかいやってみて
<Metanot> bitchcheckerでなくbitchhackerだからハックなんかできないだろうけどね
<Black<TdV>> ワロタwww

<bitchchecker> うるさい
<Elch> bitchchecker君、アタックを待ってるんだけど...
<Metanot> 何度も言うけど彼はハッカーじゃない...
<bitchchecker> お前ウイルスが欲しいのか?
<bitchchecker> お前のIPを教えろ お前のハードディスクを消去してやる

<Metanot> 笑。私がハッカーだったら諦めないな。僕はハッカーってどういうものか分かってるけど、君は100%ハッカーじゃないよ
<Elch> 127.0.0.1
<Elch> 簡単な事だ
<bitchchecker> www ふざけた奴らだ。もうオシマイにしてやる
<bitchchecker> もう最初のファイルが削除され始めたぞ

<Elch> ママーン...
<Elch> ちょっと待ってみよう
<bitchchecker> レスキューしようたって無駄だぜ。ゲス
<Elch> そりゃマズイ
<bitchchecker> elch、お前のGドライブは削除されたぞ

<Elch> うは、太刀打ち出来ない
<bitchchecker> そして20秒以内にFドライブもオサラバだ

<bitchchecker> tupac rules (mattn:ここ良く分からん)
<bitchchecker> elch、お前のFドライブは無くなった。Eドライブもだ


<bitchchecker> そしてDドライブも45%削除されたぞ!ガハハハハ
<He> Metanotは何も言うことないの?
<Elch> きっと床で転がりまくって笑ってるのかと...
<Black<TdV>> ^^

<bitchchecker> Dドライブ削除だ
<He> 勝手にやってろバカ(BITCH)

<bitchchecker> elch、インターネットにIPを晒すなんてバカな事、2度としない事だ...
<bitchchecker> Cドライブも30%だ

* bitchchecker (~java@....) がチャットを抜けました(タイムアウト)
Posted at by




まさか、そんな直接的に告げられるとは思ってませんでした。

続きを読む...

Posted at by




狐の王国 はてなスターで甦る高橋名人の想い出。
実は私、最盛期には16.5連射を放つ事が出来ました。シュウォッチと呼ばれる連射測定機で16.5連射を記録した事があります。その頃高橋名人が16連射を誇る頃、小学生だった私はヒーローだったのです。

もちろん、顔は画面等見れずプルプルした状態の連射なので、実用出来る程では無かったのですが...

皆から「スゲー」の歓声を浴びて数日、毛利名人が紙面に登場しました。毛利名人は人差し指、中指、薬指を八手のようにコントローラに当て、左右に反復運動する事でほぼ2倍の連射をあみ出しました。そして私の「ヒーロー」という肩書きは消え去りました。
しかしながら「連射が出来る」事は、その頃の子供にはステータスであり、それは名人との距離を測るものでもありました。
特に燃えたのが、ハイパーオリンピック。物差しを使い、机と専用コントローラに対して斜めに置き、下から物差しを弾く事で驚異的な連射を実現できる技は、その頃の子供達にとってセンセーショナルでした。

ここで、これだけで飽き足りなかったのが、私。
その頃流行っていたミニ四区「ファイヤードラゴン」のモーターを取り出し、先に付いているギヤを1枚おきに削り落とし、ファミコンコントローラのボタンに斜めに当てたのです。
単2電池をセロハンテープで止めたモーターは、けたたましい音と共に回転し、画面の「スターフォース」ではほぼ無敵の連射能力でした。
周りにいたオーディエンスも、スーパーマシンの繰り出す連射にただただ感動し、泣き出す子供も...(ウソ)

そしてボスを手前にした頃、部屋中にほんのりとした焦げ臭い匂いが...

コントローラを見た私の目に入って来たのは、グリグリにえぐり取られたボタンと、飛び散ったプラスチック片でした。
Posted at by




物事には、許せる物と、許せない物がある。
私にもそれはある。
普段は温厚な私で、多少の間違い位なら見逃す方だが、これに限っては許せなかった。
いくらなんでも、使っていいソースと、使っちゃいけないソースがあることくらい、常識的に分かっていると思ってた。

続きを読む...

Posted at by




最近、ネット上のトラブル記事や非難記事をよく目にする。

どれもネット上で、人がキーボードを叩き、当人が「自分の言いたい事」を、さも独り言かのように、かつその独り言を注目させたいかのごとく、過激な脚色を付けて発言したばっかりに起こった物ばかりだ。
もちろん最近のネットでは多少内容に関係無く、皆から注目された記事が、ランキングや注目度として扱われ、時に人を傷付け、言い放った人間だけがストレス発散し、傍観者の数で注目度が決まり、「自分の気分が悪いなら言ってしまっても良い」という空気だけを残す。

メディアでは、少し過激に、時には偽り、感情を煽る事でフィードバックを期待する事がある。ブログで注目を浴びたり、アフィリエイトで集客したい人もいるだろう。結構結構。ただブログにしてもソーシャルブックマークコメントにしても、喋るんじゃなくて、タイプするんだから、一回その言葉が誰に対して発信しているもので、相手や傍観者が読んだらどう意識するかぐらい考えようよ。

所詮ネット上に散らばるテキストかもしれないけど、何処にも向けたつもりは無いと思っていた矢印が、時には人の懐をかすめる事もある。
そう考えてしまうと私も怖いし、何も書けなくなってしまうかもしれない。


それでも…
インターネットは楽しむもの

そう思いたい。





Posted at by




昨年お世話になった方々、ありがとうございました。
今年も例年通り、「自分は自分らしくありたい」と思います。

さて、正月はゆったり過ごしながら、はてなブックマークで「コード書きがコード書きに対して非難するコメントやブクマ」の一連を見てました。
一貫してテーマとなっているのがSTFUAWAC(shut the fuck up and write some code)という言葉。
この言葉に対して各人色々な捕らえ方はあるだろうが、私にとってこの単語から連想出来るのは
アレコレ言ってる暇あったらコード書こうよ
って雰囲気。自前のコードに文句付けられて、言い訳してる暇があるならコード書こうよ。ってレベル。
どっちかっていうと非難に使うものじゃなくて、叱咤激励かな。
会社で上司がダベってる社員の後ろに来て
ダベっとらんと、さっさと仕事しろよ苦笑
くらいの一言。もちろん前提で上下関係なんか無いんだけど。
もちろん私は数多くのほったらかしプロジェクトの持ち主でもあるので、「さっさと仕上げろや」という意味でSTFUAWACと言われるかもしれないけど...。もしSTFUAWACって言われたら、それは「使いたいのにマズいコードがあるから直してよ」って読み取れるんじゃないかな。

しかしながら私は生粋のPerlerじゃないし、時にはスーツ的な振る舞いもする立ち位置の仕事をしているので、geekと呼ばれる資格は無いのかもしれないけどやっぱり...
geekやIRCではDISるのは当然...
とかをあまり接頭語(片付ける言葉)にしたくないし、誰しもがそんな行為は建設的ではないと思っている事を信じたいものです。
特にコード添削という行為はどうしても水平より下な目線に見られがち。だから私は気を使っているつもりです。たとえIRCやgeekだから、ブログ上だから…だったとしても礼儀は忘れたくないものです。
そしてそんな考えがスーツ的と言われるならば私は「mattnはgeekでない」と言われても全くかまわないと思っています。

そういう意味でも今年も自分らしくありたいと思っています。

本年もよろしくお願い致します。
Posted at by




amachangがやってたので私も
typing-test
まぁ、徹夜明けで一睡もしてないしこんなもんか...orz
Posted at by




以下個人的な勝手な見解。そんなに熱く語ってる訳ではありませんので...
昨今色々なSNSが出回り、個々のSNSでそれぞれの色を出してアピールしています。
その中でもmixiがアクティブユーザ数はダントツで誇っており、これから数年も変わらないであろうと思われます。
mixiを代表とするSNSを色づけるSNS特有の機能として「足跡」があります。足跡とはそのSNSユーザ(もしくはゲスト)が対象のユーザページにアクセスした際、アクセスされた側に残されるログを指します。
otsuneさんも言うとおり、自前のブログを運営されている方であればHTTPアクセスログは一度は目を通した事があるでしょうし、知らない人が閲覧しているのは当たり前だと認識していると思います。
よく「足跡機能の付いてるSNSは嫌いだ」という人が稀にいますが、私が思うに
「足跡機能が必要ない」ではなく「足跡機能が嫌いだ」という人は、"自分が踏まれる事"よりも"自分が踏んでしまう事"で、踏まれた側から"勝手な先入観を持たれる"のが嫌いなのであって、単に見なければいいはずの足跡を"嫌い"という理由を明示しない。
という事。

私は、何個かSNSを掛け持ちしています。足跡機能があるSNSにも参加しています。時には足跡も見ますし、このサイトのアクセスログも見ています。
時にはtrackerfeedを使って、リファラにアクセスしたりもしますし、ハンドルネームで検索したりする事もあります。

ネットでコミュニケーションしたいなら普通の行動だと思っていますし、私のようにソフトウェアを開発している人間にとっては、作ったアプリケーションやプラグイン、パッチがまわりでどんな風に使われているのか知りたいと思うことは当然の事だと信じています。
まさか自分の為だけに作ったソフトウェアを自分だけで動かしてウヒヒヒヒ...なんてつもりはないですし、もちろんこの考え方も個々の勝手でいいとも思っています。

閲覧者は、客観的に人を判断している訳ですから、どう判断されたって致し方ないし、そもそも足跡をつけてしまっただけで勝手な先入観を持たれるコミュニケーションなんて成立しないんじゃ...と。

別に
貴方に関する記事書いたのにリアクション無いし、足跡見たら踏んでるのにスルーですか?
なんて可愛い事を言うなと言ってる訳でもないですし、平然とクールに足跡は閲覧すべきだ!と「SNSのルール」を語ってる訳でもないです。

ただただ足跡の有無でSNSの良し悪しを公言したり、足跡なんか気にするのは××だと公言するのはどうかと...

その「足跡拒否反応のおしつけ」こそが逆に
熱いはずの熱湯風呂に"熱い"と絶叫しながら入れない人間はおかしい
と言ってるように私には見えてしまうのです。

まぁ、その個々の考え方の不一致がSNSを面白くしているんでしょうけどね。

以上、独り言でした。
Posted at by




先日ようやくPownceのAPIが公開されました。
APIはGoogle Groupで公開されています。
また、Python, Perl, ColdFusionを使ったライブラリも公開されています。ただし07/31時点では...
  • ColdFusionのディレクトリは空っぽ
  • PerlはMessageとFileしか対応していない
  • Pythonは全て対応しているけども中身が×××
って事で、MixiAPI.pyのようなものを作ってみました。
フィードの取得はPownceAPI.Session(userid, passwd)でセッションオブジェクトを作成し、get_notes()でfeedparserオブジェクトが返ります。また、ポストは
  • PownceAPI.Message(message)
  • PownceAPI.Link(message, url)
  • PownceAPI.File(message, file)
  • PownceAPI.Event(message, location, name, datetime)
で作成したオブジェクトをsession.send_item(to, item)で送信します。
簡単な例を以下に示します。
import PownceAPI

# セッションを作成
session = PownceAPI.Session("example""password")

# フィードを取得
print session.get_notes().entries[0].summary

# メッセージを送信
item = PownceAPI.Message("do you have a time?")
session.send_item("public", item)

# ファイルを送信
item = PownceAPI.File("my location""map.png")
session.send_item("all", item)

# リンクを送信
item = PownceAPI.Link("gugure!""http://www.google.com/")
session.send_item("public", item)

# イベントを送信
item = PownceAPI.Event("this is a party!""My Home""Yakiniku")
session.send_item("all", item)

今のところ、send_itemで指定出来るtoに制限があるのか、File等をallではなくpublicで送信すると500 Internal Server Errorが発生します。
また、APIを使ってもマルチバイト文字は表示されません。
とりあえず、動いている...といったところです。
もしPownceが使いたいが招待状がないという方は、コメントなりで連絡下さい。招待状を送らせて頂きます。

続きを読む...

Posted at by




お星様。ごめんね。

「このメッセージは最初の☆が付けられた際に消えます。」が消えないから...

自分のサイトに、はてなスターがカウントされないから...

はてなに問い合わせを出したら
「消えます。」のメッセージが消えると共に

なくなっちゃった。

lost_hatena_star

僕の心の中には、そして個別の記事にはちゃんといるから...

お星様。ごめんね。
Posted at by




はてなブックマークは、はてなブックマークだからしょうがないのです。
はてブ批判をすると、何故か釣りと言われる
よく考えてみよう。書き逃げのネガティブコメントを行った場合、その人は発言に責任を負わなくなってしまう。それはよくない。電車での痴漢と同じである。

idも明記されているしpermalinkもあるはてなブックマークコメントを「書き逃げ」と感じる理由がよく分からない
公開の場にテキストを書くという意味ではblogと何ら変わらない責任が発生すると思っています。

普通の人にとっては書き逃げだと思います
管理人とコメンテーターとは、とても対等とはいえないと思います。管理人はコメントを承認制にしたり、削除したりできるし。逆にコメンテーターは、邪魔臭いコメントを大量に書いて嫌がらせすることも可能です。

続きを読む...

Posted at by




はてなハイクが面白すぎる。
もう、いままでの様に「はてなスターは気に入った記事にしか付けない」なんて考えの人は、一度行ってみるべき。
スピード感、一発ネタ、五臓六腑に染み渡るダジャレ。
どれもこれもたまりません。
あと、手前味噌ですが昨日作った「LDRizeでpin付けたノードにMinibufferから「はてなスター」を付けるグリモン」がめちゃ便利。
LDRizeとMinibufferがあれば「j/k」で上下移動し「H S」で、はてなスターを付けられます。
※pinを付けて無くてもスターを打てる様に改良してあります。
見てるだけでも面白いですよ。
Posted at by




擬似GM_xmlhttpRequestを使ってたので、はてなリソースしか動いてなかった。
擬似GM_xmlhttpRequestでは、はてなスターの場合だけJSONで動かすようにしたので、きっと行けると思う。
JSONデータから文字列に戻す部分はこれを使わせて頂いた。
前とそれほど変わらないけど、unsafeWindowの宣言場所を上にもってったので前回のような簡単なパッチの当て方は出来なくなった。

--- HatenaStarEverywhere.user.js.orig   Mon Oct 01 10:37:41 2007
+++ HatenaStarEverywhere.user.js    Fri Oct 05 23:08:10 2007
@@ -92,6 +92,9 @@
     ensure(window.Hatena, 'Star');
 }
 
+if (typeof unsafeWindow == "undefined") {
+    var unsafeWindow = window;
+}
 if (typeof unsafeWindow.Hatena == 'undefined'
         || typeof unsafeWindow.Hatena.Star == 'undefined'
         || !unsafeWindow.Hatena.Star.loaded) {
@@ -138,4 +141,137 @@
             GM_setValue('configExpire', '');
         }
     });
+}
+
+if (typeof(GM_setValue) != 'function') {
+  function GM_setValue(key, value) {
+    document.cookie = [
+      name, '=', escape(value),
+      ';expires=', (new Date(new Date() + 365 * 1000 * 60 * 60 * 24)).toGMTString()
+    ].join('');
+  }
+}
+if (typeof(GM_getValue) != 'function') {
+  function GM_getValue(key) {
+    var r = new RegExp('/' + name + '=([^;]*)/'), m;
+    if (m = document.cookie.match(r)) return unescape(m[1]);
+    return null;
+  }
+}
+if (typeof(GM_xmlhttpRequest) != 'function') {
+  var GM_xmlhttpRequest_Data = null;
+  function GM_xmlhttpRequest_Handler(data) {
+     GM_xmlhttpRequest_Data = toJsonString(data);
+  }
+  function GM_xmlhttpRequest(opt) {
+    if (opt.url == 'http://s.hatena.ne.jp/siteconfig.json') {
+       var s = document.createElement('script');
+       s.charset = 'utf-8';
+       s.onload = function(e) {
+          s.responseText = GM_xmlhttpRequest_Data;
+          opt.onload(s);
+         GM_xmlhttpRequest_Data = null;
+       }
+       s.src = opt.url + '?callback=GM_xmlhttpRequest_Handler';
+       document.body.appendChild(s);
+   }
+    var x=new XMLHttpRequest();
+    x.onreadystatechange=function() {
+      switch(x.readyState) {
+        case 4:
+          opt.onload(x);
+          break;
+      }
+    };
+    x.open(opt.method,opt.url,true);
+    x.setRequestHeader('Content-Type',opt.mime);
+    x.send(null);
+  }
+}
+
+/*
+ * include http://code.google.com/p/trimpath/wiki/JsonLibrary
+ * with few modify for opera.
+ */
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+function toJsonString(arg) {
+    return toJsonStringArray(arg).join('');
+}
+
+function toJsonStringArray(arg, out) {
+    out = out || new Array();
+    var u; // undefined
+
+    switch (typeof arg) {
+    case 'object':
+        if (arg) {
+            if (arg.constructor == Array) {
+                out.push('[');
+                for (var i = 0; i < arg.length; ++i) {
+                    if (i > 0)
+                        out.push(',\n');
+                    toJsonStringArray(arg[i], out);
+                }
+                out.push(']');
+                return out;
+            } else if (typeof arg.toString != 'undefined') {
+                out.push('{');
+                var first = true;
+                for (var i in arg) {
+                    var curr = out.length; // Record position to allow undo when arg[i] is undefined.
+                    if (!first)
+                        out.push(',\n');
+                    toJsonStringArray(i, out);
+                    out.push(':');                    
+                    toJsonStringArray(arg[i], out);
+                    if (out[out.length - 1] == u)
+                        out.splice(curr, out.length - curr);
+                    else
+                        first = false;
+                }
+                out.push('}');
+                return out;
+            }
+            return out;
+        }
+        out.push('null');
+        return out;
+    case 'unknown':
+    case 'undefined':
+    case 'function':
+        out.push(u);
+        return out;
+    case 'string':
+        out.push('"')
+        out.push(arg.replace(/(["\\])/g, '\\$1').replace(/\r/g, '').replace(/\n/g, '\\n'));
+        out.push('"');
+        return out;
+    default:
+        out.push(String(arg));
+        return out;
+    }
 }


追記
[はてな][ネット]Twitterとはてなスターは相性がいい」こちらで紹介して頂きました。
一応、私の手元で動いている物をアップします。
はてな事務局さん、問題があればご連絡下さい。
HatenaStarEverywhere.user.js
Posted at by




注意:os0xさんからの指摘で、ちゃんと動かないらしいです。

javascript:var d=document,t,i,l,m=[];l=d.getElementsByTagName('span');for(i=0;i<l.length;i++){if(l[i].className=='hatena-star-star-container')m.push(l[i]);}t=d.createElement('input');t.type='text';t.onblur=function(){var v=d.createElement('div');v.innerHTML=t.value;m[0].appendChild(v);m[0].removeChild(t);};m[0].appendChild(t);void(0);
  • ↑のコードをアドレスバーにゴニョゴニョゴニョ
  • 出てきたテキストボックスにゴニョゴニョゴニョ
  • フォーカスを外す為に、アドレスバーを選ぶ
  • 出来た文字列を選ぶ
  • ゴニョゴニョボタンを押す!
たまーに上手くいかない場合もある
追記1
最後に余計な文字を1個作って、それを選択せずにゴニョゴニョするとよさげ

追記2
ちょっと改良
javascript:var d=document,t,i,l,m=[];l=d.getElementsByTagName('span');for(i=0;i<l.length;i++){if(l[i].className=='hatena-star-star-container')m.push(l[i]);}t=d.createElement('input');t.type='text';t.onblur=function(){var v=d.createElement('div');v.innerHTML=' '+t.value+' ←ここまで選択';m[0].appendChild(v);m[0].removeChild(t);};m[0].appendChild(t);void(0);

追記3
あ、それでもダメな時があるね...
パターンがつかめない...orz
Posted at by




はてなのお気に入りユーザーの情報を取得できる「お気に入りAPI」(Favorites API)を公開しました」って事でさっそく...


ソースはこんな感じ...
追記:このソースはFirefoxでしか動きません。IEで動くものはこのページのHTMLを直接参照下さい。(Firefox限定時の残骸あり) <script type="text/javascript">
<!--
function hatenaFavorites(data) {
  var container = document.getElementById('hatenaFavorites');
  Array.slice(container.childNodes).forEach(container.removeChild, container);
  data.favorites.forEach(function(i) {
    var div = document.createElement('div');
    var img = document.createElement('img');
    img.src='http://www.hatena.ne.jp/users/'+i.name.substring(0,2)+'/'+i.name+'/profile_s.gif'
    div.appendChild(img);
    div.appendChild(document.createTextNode(' ' + i.name));
    container.appendChild(div);
  });
}
function loadHatenaFavorites() {
  var container = document.getElementById('hatenaFavorites');
  Array.slice(container.childNodes).forEach(container.removeChild, container);

  var img = document.createElement('img');
  img.src='http://mattn.kaoriya.net/images/ajax-loader.gif';
  container.appendChild(img);

  var script = document.createElement('script');
  script.charset = 'utf-8';
  script.src = 'http://www.hatena.ne.jp/mattn/favorites.json?callback=hatenaFavorites';
  document.lastChild.appendChild(script);
}
--></script>

追記
os0xさんからの指摘でIEで動かないのを直しました。
↑のソースは張り替えませんので、直接ソースを閲覧して下さい。(残骸が残ってますが...)
Posted at by




はてなブックマークのコメントをニコニコ動画コメント風に流すスクリプト書いてみた...
この記事にはてなブックマークコメントを付けると、下部に流れ出します。
もしかしたらJSONに反映されるまで流れないかもしれません。
Firefoxではjavascriptからのmarqueeが生成出来ないので、こちらのサイトのスクリプトを使わせて頂きました。

使い方は簡単。上記JavaScript Marquee Demoからmarquee.jsを、また下のリンクからhatebu_ahhhhh.jsをダウンロードし、scriptタグで読み込みます。あとはHTML本体に"HatebuAhhhhh"というIDをもったDIVを一つ用意します。
注意:はてなスターのように複数設置する事は出来ません。パーマリンクに一つとお考え下さい。
注意:はてブはパーマリンクにお願いします。

続きを読む...

Posted at by




どこでもスターグリースモンキーがSafariとfubに対応しました
Operaのuser.jsに対応してみました。
たぶん行けそう
patchの適応の仕方が分からない人は、下の「+」が付いてる行だけ抜き取って先頭の「+」を全部削除、その後「HatenaStarEverywhere.user.js」の一番おしりに貼り付けて保存するか、はてな側の対応を待ちましょう。
--- HatenaStarEverywhere.user.js.orig   Mon Oct 01 10:37:41 2007
+++ HatenaStarEverywhere.user.js    Wed Oct 03 12:44:20 2007
@@ -139,3 +139,37 @@
         }
     });
 }
+
+if (typeof unsafeWindow == "undefined") {
+    var unsafeWindow = window;
+}
+if (typeof(GM_setValue) != 'function') {
+  function GM_setValue(key, value) {
+    document.cookie = [
+      name, '=', escape(value),
+      ';expires=', (new Date(new Date() + 365 * 1000 * 60 * 60 * 24)).toGMTString()
+    ].join('');
+  }
+}
+if (typeof(GM_getValue) != 'function') {
+  function GM_getValue(key) {
+    var r = new RegExp('/' + name + '=([^;]*)/'), m;
+    if (m = document.cookie.match(r)) return unescape(m[1]);
+    return value;
+  }
+}
+if (typeof(GM_setValue) != 'function') {
+  function GM_xmlhttpRequest(opt) {
+    var x=new XMLHttpRequest();
+    x.onreadystatechange=function() {
+      switch(x.readyState) {
+        case 4:
+          opt.onload(x);
+          break;
+      }
+    };
+    x.open(opt.method,opt.url,true);
+    x.setRequestHeader('Content-Type',opt.mime);
+    x.send(null);
+  }
+}
追記1
すみません。はてな内リソースでしか有効にならないようです。もう少し考えてみます。(_ _;)
追記2
修正はこちら
Posted at by




はてなスターの☆をクリックする度に画面を鮮やかに彩るグリモン書いた」は好評で、はてなスター日記でも取り上げて頂きました。
ただ、はてなブックマークでは、「Internet Explorerの為見れない」といったコメントを頂きました。

現在、Hatena Beautiful Starは
  • Firefox
  • Opera
  • Safari
にて動作確認をさせて頂いておりますが、実はInternet Explorerでも動かせない訳ではありません。

続きを読む...

Posted at by




クリックすると、お星様がいっぱい!
その名も「Hatena Beautiful Star」

続きを読む...

Posted at by




リンク先のはてなブックマーク数を表示するブックマークレットは、del.icio.usで使うと面白いのが分かった。
ただそんだけ...

delicious_hatena
Posted at by




その1:RSSからstrfile形式のファイルを作る

まず、XML::RSSが入ってなかったのでcpanから入れた。
その後、anontwit/twitterのRSSフィードを適当にstrfile形式にするperlを書く。
#!/usr/local/bin/perl

use encoding 'utf-8';
use strict;
use LWP::Simple;
use XML::RSS;

my $content = get('http://twitter.com/statuses/user_timeline/anontwit.rss');

my $rss = new XML::RSS;
eval {
$rss->parse($content);
};

print "%\n";
for my $item (@{$rss->{'items'}}) {
print "$item->{title}\n%\n";
}

exit;

その2:fortune形式に変換して設置する

なんでstrfileって、/usr/sbinに入ってるんだろ...
# mkdir ~/.anontwit
# ./anontwit.pl > ~/.anontwit/anontwit
# /usr/sbin/strfile ~/.anontwit/anontwit ~/.anontwit/anontwit.dat

その3:メーラに仕込む

愛用のメーラ「mutt」の設定ファイル、~/.muttrcにシグネチャ設定を入れる send-hook '~A' 'set signature="fortune ~/.anontwit|"'

その4:メールを書く

anontwit

その5:殺伐とした空気を味わう

殺伐とした空気を味わいながら、ただただ返信を待つ。





良く考えたら、メール書く際に最新取ってくるってのもいいかも。あとtwitterって20件までしかデータ取れないのね...orz。
Posted at by




既に記事タイトルに効力がなくなってます...汗

昨日は、C++でmixiの足跡APIを叩いてみましたが今日はpythonと行きましょう。
ただ、そのままpythonライブラリ...というのも面白くないので、本日リリースされたIronPython2.0アルファ2に合わせて、IronPythonでやってみようと思います。
基本的には、もうVisual Basic.NETです。pythonらしきソースが無くなってます。

続きを読む...

Posted at by




もう、いっそ...
掲示板...[作成]
アドレス帳...[作成]
こんな風にして、「1クリックで出来るWebアプリケーション」...
とか「3秒で出来るWebアプリケーション」...ってのは駄目?

それか注目度を逆利用して

「30日掛けて作り上げる、高品質Webアプリケーション」...なんて本があったら、売れるでしょうか...苦笑

さ、アフォな事考えとらんと、さっさと寝よう...
Posted at by




IMAPが来ない来ないとわめいてましたが...
Gmailの言語設定画面で「English(US)」に設定したらIMAPの設定項目が現れました。
gmail_lang_en
まだかまだかとお悩みの方、一度試して見られてはどうでしょう?
gmail_setting_imap
今はGoogle独特のポートに接続出来ない環境なので実際に試せませんが、後で試してみたいと思います。
Posted at by




もうタイトルは無視して頂いてかまいません。

さて、先日からC++版IronPython版CPython版とmixi APIを使ったコードを書いてきましたが、今日は実際にアプケーションを作ってみたいと思います。ただ先日も書いた通り、現状のmixi APIは足跡くらいしかWebAPIとして使える物がなく、POST(PUSH)やDELETEなどの更新系メソッドも無い為、純粋に、ただ純粋に、足跡をXMLデータとして参照するくらいしか出来ない状況にあります。

さっさと他のAPI出してくれなきゃブログネタが持たないよ...

唯一できそうなものとして、足跡監視ツールくらいですか...
先日作ったCPython用のMixiAPI.pyを使って足跡をポーリングし、新しい足跡があったらお知らせするってシステムを作ってみましょう。
作り方は簡単。get_footstamps()でfootstampディクショナリのリストを受け取り、前回値と比較します。日付ソートした状態でIDが異り始めれば、最初のレコードから違ったレコードまでが新規足跡になる訳です。
以下のソースでは、相異点毎にコンソール出力しています。コンソール出力部分の直下に、「firefox-remote」等を引数「link」で起動するよう修正すれば、「自動足跡踏み返し機」が出来上がる訳です。これを使えば貴方のたるんだ腹筋もどんどん引き締まっていきます。

はぁ...こんなものしか作れないAPIって...
もう...寝ます...

#しかしこのAPIサーバ、よく失敗を返しますね...

MixiAPI.pyにHTTPステータス判定を追加しました。
以下のソースと合わせ、下のダウンロードリンクからダウンロード願います。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import time
import MixiAPI

if len(sys.argv) < 3:
    sys.exit()

service = MixiAPI.Service(sys.argv[1], sys.argv[2])
oldstamps = []
while 1:
    try:
        footstamps = service.get_footstamps()
        # 比較対象がある場合のみ処理
        if len(oldstamps) > 0:
            # 更新日付でソートする(降順)
            footstamps.sort(lambda x, y: cmp(y['updated'], x['updated']))
            n = len(footstamps)
            for i in range(n):
                # idが異なり始めるインデックスを取得する
                if footstamps[i]['id'] == oldstamps[0]['id']:
                    break
            # 全てが異なる場合は全項目検知とする
            i -= 1
            while i >= 0:
                print footstamps[i]['title']
                # ここに footstamps[i]['link'] を引数に持った
                # firefox の起動コマンドを入れておけば、
                # 足跡踏み返しシステムが出来上がる。
                i -= 1
        oldstamps = footstamps
        # del oldstamps[0]
    except:
        pass
    time.sleep(3)

ダウンロード:,
Posted at by




コトバコAPIが公開されています。
RESTを使って記事投稿出来ます。iframeを隠して使えばWeb上にも投稿画面を貼り付けられそうです。
とりあえず、動くものとしてHTAアプリにしてみました。
アクセスキーは、ログイン後に「ツール」を開くと確認出来ます。

cotobacohta

ダウンロード:
Posted at by




PLAYLOGMashUp Awardが開催されています。
適当にYahoo Pipes!でパイプってJSONで取得するものを作ってみました。
URL:PLAYLOG Playlist(JSON)
ただ、Yahoo Pipes!側が悪いのか、日本語を含むアーティストが文字化けを起こしています。だれか解決方法知ってたら教えて下さい。
使用は適当にどうぞ... <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script type="text/javascript"><!--
if (window.addEventListener) window.addEventListener("load", loadScript, false);
if (window.attachEvent) window.attachEvent("onload", loadScript);
function loadScript(){
  var script = document.createElement("script");
  script.charset = "utf-8";
  script.src = "http://pipes.yahoo.com/pipes/pipe.run?...";
  document.body.appendChild(script);
  document.getElementById('result').innerHTML = "<em>reading...</em>";
}
function callback(data){
  var html = "";
  for(n = 0; n < data.count; n++) {
    link = data.value.items[n]['playlog:artistlink'];
    artist = data.value.items[n]['playlog:artist'];
    title = data.value.items[n]['title'];
    html += "<a href=\"" + (link ? link : "javascript:void(0)") + "\">";
    html += (artist ? artist : "unknown") + " - " + (title ? title : "unknown");
    html += "</a><br />";
  }
  document.getElementById('result').innerHTML = html;
}
// --></script>
<title>Sample</title>
</head>
<body>
<h1>PLAYLOG</h1>
<div id="result"></div>
</body>
</html>
Posted at by




mixiの足跡APIをC++、libxml2、libcurlで書くと、どんなに長いソースになるかを実証する。
AtomPP/WSSEなんかやめちゃえ...

perlなら20数行だし、pythonで書いても大した事にはならないだろう...。
Basic認証ならまだしも、WSSEなんか使ったら敷居も高いし、派生アプリケーションが出てこなくなるのはもう分かってるはず。

C++で書くと、こんな事になるんだ...
#以下ソース
#例によって適当クオリティなので添削し放題です。

続きを読む...

Posted at by




たんなる独り言。
mixiのあしあとAPI発掘 - ZeroMemory
先日mixi stationで足跡APIが出ている事が判明したらしいのですが、結局mixiがすんなりAPIとして公開しないのは...
  • 公開したAPI保守にどれだけ時間を取られるか想定出来ない
  • アタックでサーバダウンした時のユーザ対応が想定出来ない
  • リスクが想定出来ない
  • メリットも想定出来ない
  • 不正に情報が漏れてしまった場合のユーザ対応が想定出来ない
こう見えてしまう。

そして時間が経つにつれて、どんどん公開し辛くなって行くんでしょうね。
今回の足跡APIは、「足跡ならば...」と思ったのか、「今のところ足跡しか...」と判断したのかは分かりませんが、ちゃっちゃと...^H^H^H
もし日記APIを公開するとなったら、AtomPP/WSSEなんだろうなぁ...。公開されたら自前のブログエディタで対応しようかなぁ...。

さぁ、これをキッカケに開けチャッカー!...じゃなく開けAPI!
Posted at by




MixiAPI.pyで現状出来ていたのは足跡一覧取得だけでしたが、photoアルバムに対応してみました。
現在動作するAPIは以下の通り
  • get_footstamps: 足跡一覧取得
  • create_album: アルバム作成
  • get_albums: アルバム一覧取得
  • upload_photo: 画像アップロード
なんか、このAtomAPIしっくりきません。
以下、疑問に感じたもの
  • 全てのサービスを束ねるルートエントリポイントがない
  • http://mixi.jp/atomはHTTP/404。足跡も、日記も、photoも全てを束ねたルートエントリポイントがない。
  • アルバムを作成出来るが、削除できない
  • photoアルバムのURLにDELETEメソッド、X-Http-Method-OverrideヘッダでDELETE等送信してみたが、HTTP/405やHTTP/400が返る。
  • アルバムのURLにGETでアクセスしてもphoto一覧が取得出来ない
  • Atomならば普通、一覧が返ってきて欲しいところ...
まぁ、誰もまだ「API公開」と公に言った訳じゃないから、仕方ないのかもしれないけど...
こっそりmixi stationなんかで公開すr

とりあえず、以下からダウンロード

ダウンロード:

#アルバム作成、画像アップロードのサンプルはMixiAPI.py本体に記述してあります。
Posted at by




IronPythonでのmixi API操作は既に出来てますので、CPythonで動く物を作ってみました。
とは言っても、まだ足跡APIしか出ていませんので、これからAPIの公開に従い順次追加していく予定です。

メソッドは今のところ、get_footstamps()だけ。
いずれ
  • post_diary
  • edit_diary
  • delete_diary
  • get_friends
等といったメソッドが増えていくのではないか...と思います。
とりあえず、get_footstamps()の使い方は、以下のようなイメージです。
import MixiAPI

service = MixiAPI.Service("user@example.com", "password")
footstamps = service.get_footstamps()
for footstamp in footstamps:
    print footstamp['id']
    print footstamp['title']
    print footstamp['link']
    print footstamp['updated']
    print footstamp['author']['tracks:image']
    print footstamp['author']['tracks:relation']
    print

あれ?mixi APIのAtomPP/WSSE批判はどこいった?
ダウンロード:
Posted at by




データだけ乗っけてscriptソース載せず...ってのは好きじゃないなぁ...
otsuneさんあたりに、「元ソース記事載せないのは好きじゃないなぁ...」と言われそうだけど

use strict;
use Web::Scraper;
use URI;
use YAML;

my $emoji = scraper {
  process '//table[@width="100%" and @cellpadding="2"]//tr/td/font/../..',
    'emoji[]' => scraper {
      process '//td[2]/font', code => 'TEXT';
      process '//td[3]/font', char => 'TEXT';
    };
  result 'emoji';
};

my @urls = (
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_01.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_02.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_03.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_04.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_05.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_06.php',
);
my $res;
foreach my $url (@urls) { push @$res, @{$emoji->scrape(URI->new($url))} };
warn Dump $res;
Posted at by




virtualearth

Microsoftの地図サービス、Live Search Mapsがいつまでたっても使えません。
空へはばたけるのは、いつの日か...
Posted at by




私が自宅で使っているPC(ノート)はそれほど良いスペックではありません。それでもやっぱりC言語のソースをコンパイルしたり、重たいスクリプトを走らせたりする事があります。そんな時、横で淡々と流れていて欲しいustream.tvがブラウザ内をデカデカを乗っ取って、しかもリソースをどんどん食っていくのはとても耐え切れなかったりします。
ただもう最近は、ブラウザを使わずustream.tvを楽しんでいます。
今日はその方法をご紹介。

映像/音声ですが、Linux版に用意されているスタンドアローン版flashplayerを使っています。ネット上にあるFLVもコマンドラインから起動して閲覧出来ます。
# gflashplayer http://www.example.com/path/to/flv/example.flv
ustream.tvでは、flashを全画面で開く「Open in larger window」というリンクがありますが、このリンクはustream.tvのFLVプレーヤへの直接リンクとなっています。ですからこれをgflashplayerで起動すれば動画が見れます。
もちろんこのflashは、コントロールパネル付属のプレーヤでしか無く、このflash自身から実際のストリームを再生していますから再生していて中断しちゃった...なんて場合は再接続ボタンを押せばうまく再生し直してくれる場合があります。
この「Open in larger window」というリンクを取得し、gflashplayerに渡して再生する以下のシェルスクリプトを私は用意しています。
#!/bin/sh

GFLASHPLAYER=/usr/bin/gflashplayer

if [ "x$1" == "x" ]; then
  echo "usage: `basename $0` [channel]"
  exit
fi

USC=`curl -s "http://ustream.tv/channel/$1" | grep 'Open in larger window' | sed -e 's!^.*<a href="/\([^"]*\)".*$!\1!'`
if [ "x$USC" == "x" ]; then
  echo "currently offline?"
  exit
fi
URL="http://ustream.tv/$USC"
echo playing $URL
$GFLASHPLAYER "$URL"
このシェルスクリプト「ustplayer」をコマンドラインから # ustplayer erogeek-con と実行しています。
twitterで、followerさんが「http://ustream.tv/channel/xxxxx 見てる」と言えば、この"channel"の後の部分、"xxxxx"を実行引数に渡します。
またchatですが、私が自宅で使っているOSはLinuxですからxchatというX Window/GTK2で動くIRCソフトウェアがインストール出来ます。ustream.tvでは「chat1.ustream.tv/6667」というIRCサーバが公開されていますから、わざわざブラウザを上げるまでもないのです。charsetを"utf-8"にさえしておけばchat出来ます。ただしustream.tvのIRCサーバはustream.tvアカウントの認証が必要になりますから、設定画面等でログインパスワードを設定しておく必要があります。

で、昨日開催された「エロギークカンファレンス」を見ていた時のキャプチャがこれ...

erogeek-con

余談ですが、xchat等PC-UNIX上でIRCソフトウェアを使われる方でお薦めしたいのがbitlbee。xinetdデーモンとしても動作するIRCサーバで、MSN,Jabber,ICQ等各種IMプロトコルを中継してくれます。Jabberを使えばGTalkとおしゃべり出来ますから、xchat一つ起動しておけばtwitterも出来るし、jaikuも出来るし、MSNチャットも出来るし余分なリソースを取られなくて嬉しい限りです。

2007/10/21 追記
ustream.tvのHTMLフォーマットが変更された模様。それにあわせシェルスクリプトを修正
#!/bin/sh

GFLASHPLAYER=/usr/bin/gflashplayer

if [ "x$1" == "x" ]; then
  echo "usage: `basename $0` [channel]"
  exit
fi

USC=`curl -s "http://ustream.tv/channel/$1" | grep 'ustream\.tv/.*\.usc' | sed -e 's!.* src=\&quot;http://ustream\.tv/\([^&]*\)&.*$!\1!'`
if [ "x$USC" == "x" ]; then
  echo "currently offline?"
  exit
fi
URL="http://ustream.tv/$USC"
echo playing $URL
$GFLASHPLAYER "$URL"
Posted at by




以前書いた、「ブラウザを全く使わずにustream.tvを楽しむ方法」という記事、なかなか好評だったのですが、 IRCチャットはともかくスタンドアロンFLASHプレーヤがLinuxしかないので、Windowsの方にはそれ程ありがたい記事では無かったかもしれません。

erogeek-con
※Linux版スタンドアロンFLASHプレーヤ

って事でWindows版のUstreamプレーヤを作りました。
  • GUIツールキットはFLTK2
  • FLASHはブラウザが使用しているActiveXだけを表示
  • 入力エリアにチャネル名を入力するだけで再生
名前はFLTK2アプリケーションには必ずと言っていい程、先頭に「FL」が付くので、「flUstPlayer」としました。
実行画面はこんな感じです。

続きを読む...

Posted at by






そう...我々がwebなのだ
Posted at by




microsummaryという言葉をご存知でしょうか。microsummaryはMozilla Groupが提案しているWebの要約表記方法の事を指し、ブラウザのブックマーク等で動的に更新されるコンテンツを生成出来る機能です。
Microsummaries - MozillaWiki
例えば、貴方が任意のWebサイト一覧を作り、そのWebサイト毎の最新サマリを日々更新したと思った場合どうするでしょうか。 Web::Scraperを使って、そのサイトの本文もしくはタイトルらしき辺りをスクレイピングするでしょうか。 もしくはmicrofomatsを使ってエントリを一覧し、その先頭を最新と信じて出力するでしょうか。

これらはコンテンツ製作者の意図とそれを二次利用する者とで事前に
  • サマリは一番最初のコンテンツのタイトルである
  • サマリは<title>タグに含まれている
  • サマリとして使用して良いサイトである(もしくはない)
  • サマリは2時間おきに更新される可能性がある
などと言った情報が交換されていない事が原因で、現状コンテンツ制作側からは何も公開していない事になります。
それを実現する機能がmicrosummaryです。

microformatsは既存のコンテンツに対するrel属性およびclass属性を定義する事で、二次利用者側に欲しいコンテンツの場所を示しています。しかしこれでは複数の情報を結合したサマリを生成したり、不要な情報を省く事が難しくなる。つまりプログラマブルでは無い。microsummaryは、link要素に指定したURLに格納されるmicrosummary定義ドキュメントに埋め込まれるXSLT(XML Stylesheet Language Transformations)をコンテンツ自身に適応する事により、microformatsよりもより柔軟なサマリを提供出来るようになっています。

まず、このサイトのHTMLを見て下さい。
<link rel="microsummary" href="http://mattn.kaoriya.net/microsummary/entry_title.xml" />
という部分に、microsummary定義ドキュメントへのパスが記述されています。
そしてそのドキュメントの中身には <?xml version="1.0" encoding="UTF-8"?>
<generator xmlns="http://www.mozilla.org/microsummaries/0.1" name="Big Sky - Entry Title">
  <template>
    <transform xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
      <output method="text"/>
      <template match="/">
        <value-of select="html/head/title"/>
        <text> - </text>
        <value-of select="//div[@class='xfolkentry'][1]/h3"/>
      </template>
    </transform>
  </template>
  <update interval="240"/>
  <pages>
    <include>^http://mattn\.kaoriya\.net</include>
  </pages>
</generator>
となっています。
この定義ドキュメントのpages要素を見て下さい。
pages/includeには、そのサイトでmicrosummaryを適応すべきURLパターンが記述されています。
つまりコンテンツ製作者側が、サイトページに応じてmicrosummaryを出力するかしないかを定義出来る事になります。
次にtransform要素。ここはXSLTスタイルシートと同じ構成になっている。上の例ではこのページのHTMLにある、html/head/title要素と、xfolkentryというクラス名の付いたdiv要素直下にあるh3要素を、結合しサマリとして扱うと定義しています。
もちろん、XSLTなのでもっと複雑なサマリを生成する事も出来ます。
また、update要素ではサマリの更新間隔を指定する事もでき、通常30分である更新時間を任意に指定する事も出来ます。

microsummaryは、はてなダイアリーにおいても既に導入されておりhttp://d.hatena.ne.jp/microsummary/entry_title.xmlに定義ドキュメントが格納されています。これにより、例えば「はてなスター日記」をFirefoxでブックマークすると、最新記事のタイトルがサマリーとして生成され、上の記述にもある様に240分毎(update要素)にサマリが更新されるブックマークをコンテンツ製作者側から提供する事が出来ます。
microsummary-hatenastar
利用方法では、ニュースのヘッドラインを流す事も可能です。
参考:FirefoxのブックマークツールバーでYahooヘッドラインニュースを流す方法
さらに、ライブドアリーダのフィード詳細画面では、購読者数とレートがmicrosummaryとして配信されており、動的なブックマーク(ライブタイトル)を実現出来ます。 例えば私のサイトのフィード詳細でもmicrosummaryが配信されています。以下のブックマークレットで確認出来ます。
参考:しげふみメモ:livedoor Reader購読者数のライブタイトル
この他、定義ドキュメントに記述出来る要素については
Microsummary XML grammar reference - MDC
に日本語訳化された物があるので興味のある方は参照してみて下さい。
なお、MozillaのドキュメントWikiにあるMicrosummaries - MozillaWiki Standardizationという項目では、microsummaryが記述されるlink要素はmicroformatsとして標準化されるべきだ、との文章もあります。いずれmicroformatsのDraftに入ってくるのかもしれません。

さて、このエントリを作成するにあたって、以前microsummaryの記事でご紹介したbookmarkletを改造し、microsummaryを表示するブックマークレットを作ってみました。
※IE6、Safariでは動作しません。動作確認は、FirefoxとOperaにて行っています。
bookmarlet:microsummary
表示しているドキュメントのlink要素の内、ref="microsummary"の物を検索し、定義ドキュメントをXHRで取得しXMLDOMとしてパース。その後pages/includeにURLがマッチしている事を確認した後にXSLTプロセッサでサマリを生成する。といった仕組みです。
IE6で動作しない理由は、XSLTプロセッサに渡すドキュメントエレメントはXMLである必要があり、document.documentElement.innerHTMLを渡してもパースエラーになってしまう為です。また、SafariではtransformToDocumentにdocument自身を渡すとエラーとなっています。

ちなみに、HTMLを無理やりXHTMLに変換し、MSXML2.DOMDocumentで処理するコードの残骸が入っていますがお気になさらず...

microsummaryはおそらくブラウザのブックマークに限られた話ではありません。
冒頭で述べた様に、コンテンツサマリ一覧を作成する様な二次利用者に対しても有用で、CGI等も必要とせず、かつ記事のエントリに付与されたclass属性を壊す事無くサマリを集約出来ます。おおよそGRDDLのサマリ版といった所だと認識して頂けると思います。
ただし、現状のmicrosummaryでは1つの定義ドキュメント内に1つのスタイルシートしか記述出来ず、URLパターンに応じたスタイルシートを選ばせる事が出来ない上、出力する内容も1つに限られてしまいます。
これはブックマークのサマリとして登場したmicrosummaryの性でしょうか...。

上で述べた様に、膨大なコンテンツを扱う二次利用者側からすると、microformatsで得られる情報は大きすぎ、かつコンテンツ提供者側が意図しない内容になってしまう可能性もあります。これを解決出来るmicrosummaryに、少し期待出来るのでは?と考えています。
Posted at by




携帯電話でブラウズしている時に突然、はてなブックマークに登録しておきたくなったりしませんか?
はてなブックマークから画面遷移している時は、はてな専用携帯変換ページが表示されブックマークへのリンクも付けられている為「このページはてブしたい」と思った時にも簡単にはてなブックマーク出来るでしょう。
でも直接そのページを見ている時には携帯では術がありません。
私はよく
  1. 携帯でブラウズ
  2. 携帯のブックマークに登録
  3. PCでブックマーク登録
という煩わしい作業をしてたりします。
例えば、専用ページを作ってリファラを見ても画面遷移では無いので登録しようとしているブックマークURLは取得出来ません。

これを何とかしようと言うのが今日のお話。
殆どの携帯電話には、「URLでメールを送信」という機能が備わっています。
これを使い、サーバ側に登録させるのです。
ニワンゴ開発サイトに「hatebu」というコマンドを作りました。
ニワンゴ開発サイトは、簡単な形式で記述されたメールからコマンドとしてWebAPIをキック、その応答をメールで返すという機能を提供してくれています。
この「hatebu」コマンドから呼ばれるサーバ側CGIとして以下の様なphpのコードを置きました。
※phpなんか久しぶりに書いたのでかなり適当
※何故phpなのかというと、無料で使えてソケット系APIが動くサーバで見つけたのがphpしか動作しなかったから...

<?php
header('content-Type: text/plain; charset="Shift_JIS"');

# URLかどうか...(適当)
function isUrl($str) {
  return (preg_match('/^(https?)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)$/', $str) && $str!="");
}

# コマンドとパラメータ
$c="";
$p2="";
$subject="";

if(isset($_REQUEST['c'])){
    $c = $_REQUEST['c'];
}
if(isset($_REQUEST['p2'])){
    $p2 = $_REQUEST['p2'];
}
if(isset($_REQUEST['subject'])){
    $subject = $_REQUEST['subject'];
}
$c = mb_convert_encoding($c, "SJIS");
$pi = mb_convert_encoding($pi, "SJIS");
$subject = mb_convert_encoding($subject, "SJIS");

# コマンドのチェック
if ($c != 'hatebu|| $p2 == "" || !isUrl($p2)){
  echo "response=ERROR\n";
  echo "subject=".$c." ".$p2."\n";
  echo "body=Failed\nInvalid parameter \"$c\" \"$p2\" \"$subject\"";
  exit;
}

# AtomPubで送信
error_reporting(E_ALL);
require_once('class.atomapi.php');
require_once('class.wsse.php');

$username = 'xxxxxxxxxxxxxxxx';
$password = 'xxxxxxxxxxxxxxxx';
$endpoint = 'http://b.hatena.ne.jp/atom/post';
$auth     = 'WSSE';

$entry = new AtomEntry();

$entry->set_title('HATENA');
$entry->set_content('BOOKMARK');
$entry->set_summary($subject);
$entry->add_link($p2, 'related', '', 'text/html');
$auth_obj = new WSSE($username, $password);
$post = new AtomRequest('POST', $endpoint, $auth_obj, $entry->to_xml('POST'));
$post->exec();
if ($post->error()) {
  echo "response=ERROR\n";
  echo "subject=".$c." ".$p2."\n";
  echo "body=Failed\nInvalid parameter \"$c\" \"$p2\" \"$subject\"";
} else {
  echo "response=SUCCESS\n";
  echo "subject=".$c." ".$p2."\n";
  echo "body=Succeeded";
}
exit;
?>
この状態で、ニワンゴ開発サーバからコマンドを発行する為に
m at open dot niwango dot jp
※「at」は「@」に、「dot」は「.」に置き換えます
というアドレスへ hatebu [URL] ※[URL]はとりあえず、http/httpsのみにしました。
とうい本文のメールを送信すると、はてなの「i_am_not_mattn」というアカウントのはてなブックマークに[URL]で指定したサイトがブックマークされます。メールの題名がブックマークコメントになっています。
適当に使ってみて下さい。
※なお、ニワンゴ開発OpenAPIの仕様で私にメールアドレスがばれる事はありません。
※如何わしいサイトが登録された場合はアカウント停止します。

アプリとして言うならば「i_am_not_mattn」のアカウントだけでしか動作出来ないのが苦しい所...
まさかメール本文にパスワード書くってのも嫌だし...
専用サーバでqmailからキックさせるか、sidebar.jpでXMLRPCをキックさせるのが良いんでしょうね。
Posted at by




最近、「今何してる?」でつながり合うTwitterというサービスで遊んだりしてます。

http://twitter.com/

基本的にはチャット、しかも「垂れ流し」系のチャットで、どちらかというと「カテゴリ無し」で「オーナー無し」の掲示板とも言えます。

SNSを歌うだけあってか、friendsと呼ばれる、お気に入りユーザを互いに登録する機能もあります。

ただfriendsといっても「垂れ流し」ですから、どちらかと言うと各ユーザがラジオのパーソナリティのようで、friends登録はラジオのチャンネルを登録しているって感じです。

あくまで、その時に、あの人が、何をしているかを互いに晒し合って楽しみます。

いわば束縛の「ゆるい」、SNSと言って良いかもしれません。

最近のSNSのように日記記事1件に対して、複数のコメントやレスが繋がる形式だと、「読み逃げ」等といった、「一文にも値しない価値観」に悩まされたりしますが、Twitterの場合は「垂れ流し」ですから、書く側は「見られてなくてもいい」、見る側も「コメントなんか要らない」といった割り切りが最初から存在するんです。
前に書いた記事のコメントにレスしなきゃ新しい記事が書けない!そんな心配も要りません。

チャットだから見逃したら、そこで話題はお終いって所が、書く側も、見る側も認識出来るのです。

最近、mixi等SNSに疲れを感じる人には良い保養所になりそうな気がします。

ちなみに私はココにいます。

http://twitter.com/mattn_jp

「垂れ流し」ですから、レスもないかもしれませんけど、気軽にfriends登録して頂いても構いません。
Posted at by




とうとうやってくれました。
PLAYLOGでブログパーツが誕生しました。

自分のブログに再生履歴が表示されるようになりました。バチパチ

さっそく入れて見ました。ここの画面右下あたりに出てるのが、そのブログパーツです。

おそらく私が1番に違いない。笑

なんせ発表されてから、1分以内に作業しましたからね...苦笑
Posted at by




きょうじんAPI
ごめんなさい。「きょうじん」が何を意味してるかも知らないんですが、とりあえず...

アナタの「きょうじん」発言は...
Posted at by




Share a Tab from Your iGoogle Page

iGoogleでタブが共有出来るようになったようです。

igoogle_shared_tab1

試しに共有するタブを作成し、「Share this tab」を選ぶと

igoogle_shared_tab2

共有したい人にメールを送信出来ます。
メールを受け取ると
igoogle_shared_tab3

こんなメールが来て

igoogle_shared_tab4

iGoogleへの追加画面が表示されます。

別のアカウントにタブを持っていったり、便利なタブ出来たから見てよ!って場合にはつかえそうです。
Posted at by




ブログ引退表明サービスRetiredAPIをpythonから使うサンプルです。
URLを指定すると引退しているかどうかがTrue/Falseで戻ります。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import httplib

def isRetired(url):
    conn = httplib.HTTPConnection("retired.at")
    conn.request('GET', '/api/badge/%s' % url)
    response = conn.getresponse()
    return response.getheader("Location") == "http://retired.at/image/retired.gif"
ちなみに、hkn?さんの日記で試すと、Trueが戻ります。
>> print isRetired("http://d.hatena.ne.jp/hkn/20070829/1188318885")
True
Posted at by




# telnet baka-kyoudai.com 80
GET / HTTP/1.0
Host: baka-kyoudai.com:80
User-Agent: Anchan/0.1 (compatible; IKARIYA 6.0; DRIFTERS 5.1)

兄:ドンドンドン!ドンドンドン!
弟:「誰だ?」
兄:「あんちゃんだよ。お前のあんちゃんだよ」
HTTP/1.1 401 Unauthorized
Server: Apache-Drifters/1.1
Pragma: No-cache
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 09:00:00 JST
WWW-Authenticate: Basic realm="Resource Of Brothers"
Content-Type: text/html;charset=utf-8
Content-Length: 979
Date: Thu, 27 Sep 2007 12:48:36 GMT
Connection: close
...
弟:「ほんとに俺のあんちゃんか?」
兄:「ほんとにあんちゃんだよ」
弟:「それじゃあ、これに答えてみろ。電車に必要なのは乗車券、飛行機に必要なのは搭乗券、じゃあ映画に必要なのは?」
GET / HTTP/1.0
Host: baka-kyoudai.com:80
User-Agent: Anchan/0.1 (compatible; IKARIYA 6.0; DRIFTERS 5.1)
Authorization: Basic dGFrYWt1cmE6a2Vu

兄:「高倉健」
HTTP/1.1 200 OK
Date: Thu, 27 Sep 2007 12:55:21 GMT
Server: Apache-Drifters/1.1
Content-Length: 176 Content-Type: text/html;charset=utf-8

...

弟:「あっ、あんちゃんだ! 入れよ入れよ」
Posted at by




w.google.co.jp -> www.google.co.jp

ww.google.co.jp -> www.google.co.jp

さすがに

wwww.google.co.jp -> エラー

ちなみに

w.yahoo.co.jp -> エラー

ww.yahoo.co.jp -> エラー

Posted at by




Blogging APIは何処に行こうとしているのか...

先日は、XMLRPCについてのお話をさせて頂きました。
今日はその中で出てきたBlogging APIについて。
著名なブログツールの多くは、リモートからブログが更新出来る仕組みを提供しています。
その中でも、一般的な物が先日お話した「XMLRPC」をベースにした「Blogger API」や「metaWeblog API」、「MovableType API」があります。
※現在ではBloggerはXMLRPCではなく、Atompubを使用しています。
それとは別に、Atomフィードを使用したAtomPub APIがあります。
XMLRPCの場合は、リモートメソッドとしてXMLを生成してブログの投稿、削除等を行いますが、AtomPubの場合は送受信されるXMLの単位自身が文書になります。
この文書をHTTPのGET/POST(PUT)およびDELETEメソッドを使用して文書(ブログ)を更新します。
AtomPubではAtomフィードにひもづけられた登録用URIに対して文書をPOST(PUT)する事で新規エントリ、既存エントリにひもづけられた編集用URIに対してPOST(PUT)する事でエントリ更新、そして既存エントリの編集用URIに対してDELETEメソッドを送信する事でエントリ削除という動作になります。
一部のサーバではDELETEメソッドを受け付けない物もあるので、「X-Http-Method-Override: DELETE」というヘッダでDELETEメソッドと同じ処理が行える様になっているサーバもあります。

この2つのAPIの大きな違いとして、文書形式と認証方法が挙げられます。

XMLRPCの場合は、各メソッドに対してユーザIDおよびパスワードを渡す事になります。
またXMLRPCでは送信されるXML自身はメソッドパラメータが含まれ、その中にはユーザIDやパスワードも含まれてしまいます。二次利用する事は出来ません。

AtomPubの場合は、上で説明した通り送受信されるXML自身が文書である為に二次利用も可能です。
また認証方法は一般的にはBasic認証か、WSSE認証が用いられています。

巷のブログサービスは以下の様なAPI実装を行っています。
ブログサービス提供APIフォーマットエントリポイント
teacupXMLRPCHTMLhttp://white.ap.teacup.com/applet/[username]/postmsgrpc
EGOISTブログXMLRPCHTMLhttp://[blogid].ebsystems.jp/xmlrpc.php
自分のブログサイトの先頭に付いているblogid
FC2ブログXMLRPCHTMLhttp://blog.fc2.com/xmlrpc.php
JUGEMXMLRPCHTMLhttp://[blogid].jugem.jp/admin/xmlrpc.php
JustBlogAtompubHTMLhttp://app.justblog.jp/t/atom/weblog/blog_id=[blogid]
Livedoor BlogAtompubHTMLhttp://cms.blog.livedoor.com/atom/blog_id=[blogid]
MSN SpaceXMLRPCHTMLhttps://storage.msn.com/storageservice/MetaWeblog.rpc
NetLaputaXMLRPCHTMLhttp://blog.netlaputa.ne.jp/rpc/mt-xmlrpc.cgi
News HandlerXMLRPCHTMLhttp://blog.nettribe.org/xmlrpc.php
SeesaaブログXMLRPCHTMLhttp://blog.seesaa.jp/rpc/
WordPressXMLRPCHTMLhttp://faq.wordpress.com/xmlrpc.php
Yahoo!ブログXMLRPCHTMLhttp://api.my.yahoo.co.jp/RPC2
BloggerAtompubHTMLhttp://[blogid].blogspot.com/feeds/posts/default
BloggerXMLRPCHTMLhttp://blog.goo.ne.jp/xmlrpc.php
pwBlogXMLRPCHTMLhttp://www.pwblog.com/xmlrpc
VoxAtompubHTMLhttp://[blogid].vox.com/library/posts/atom.xml
はてなブックマークAtomPubTEXThttp://b.hatena.ne.jp/[username]/atom/
はてなダイアリーAtompubはてな記法http://d.hatena.ne.jp/[blogid]/edit
アメーバブログAtompubHTMLhttp://ameblo.jp/servlet/_atom/blog/[blogid]
ココログXMLRPCHTMLhttp://app.f.cocolog-nifty.com/t/api
ドリコムXMLRPCHTMLhttp://blog.drecom.jp/api/xmlrpc
ブログ人XMLRPCHTMLhttp://app.blog.ocn.ne.jp/t/api/
PLAYLOGXMLRPCWikihttp://playlog.jp/_atom/blog/[blogid]
ちなみに、アメーバブログはWSSEヘッダを作成する際、パスワードをMD5した値でSHA1/NONCEを作成する必要があります。
これは正直言ってしまうと、バグとしか思えません...汗
Atompubの場合、実際には上記エントリポイントを直接参照するのではなく、ブログページからオートディスカバリする事が推奨されます。
例えば、私のVoxのサイトのHTMLを参照してみると <link rel="EditURI" type="application/rsd+xml" href="http://mattn.vox.com/rsd.xml" title="RSD" />
というヘッダがあるのが分かるかと思います。
次にこのhrefを参照すると <?xml version="1.0"?>
<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
    <service>
        <engineName>Vox</engineName>
        <engineLink>http://www.vox.com/</engineLink>
        <homePageLink>http://mattn.vox.com/</homePageLink>
        <apis>
            <api name="Atom" preferred="true"
                apiLink="http://www.vox.com/services/atom" />
        </apis>
    </service>
</rsd>
このようなXMLが戻ります。ここまでは認証無しに参照出来ます。
このapiノードに記述されているapiLinkにX-WSSEヘッダを付けて要求すると、以下の様な登録/編集用URIが記述されたXMLが返されます。
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://purl.org/atom/ns#">
  <link rel="service.post" href="http://www.vox.com/services/atom/svc=post/collection_id=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" title="matt'n roll" type="application/x.atom+xml"/>
  <link rel="alternate" href="http://mattn.vox.com/" title="matt'n roll" type="text/html"/>
  <link rel="service.feed" href="http://www.vox.com/services/atom/svc=asset/XXXXXXXXXXXXXXXXXX" title="matt'n roll" type="application/atom+xml"/>
  <link rel="service.upload" href="http://www.vox.com/services/atom/svc=asset" title="matt'n roll" type="application/atom+xml"/>
  <link rel="replies" href="http://www.vox.com/services/atom/svc=asset/XXXXXXXXXXXXXXXXXX/type=Comment" title="matt'n roll" type="application/atom+xml"/>
</feed>
service.feedにはAtomフィードURIが、service.postは新規エントリポスト用のURIが格納されています。
Voxの場合は画像ファイル等のアップロード(service.upload)に対応しているようですね。
また、service.feedから取得したフィード自身にはrel属性とtype属性で指定されたlink要素も付与されていますから <entry xmlns:default="http://www.sixapart.com/ns/atom/privacy" xmlns:default1="http://www.w3.org/1999/xhtml">
  <id>tag:vox.com,2007-11-03:asset-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</id>
  <title>スケスケ</title>
  <published>2007-11-03T09:25:46Z</published>
  <updated>2007-11-03T09:25:46Z</updated>
  <link rel="alternate" href="http://mattn.vox.com/library/post/%E3%82%B9%E3%82%B1%E3%82%B9%E3%82%B1.html" title="スケスケ" type="text/html"/>
  <privacy xmlns="http://www.sixapart.com/ns/atom/privacy">
    <allow policy="http://www.sixapart.com/ns/atom/permissions#read" name="Everyone" ref="http://www.sixapart.com/ns/atom/groups#everyone"/>
  </privacy>
  <link rel="alternate" href="http://www.vox.com/services/atom/svc=asset/XXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" type="application/atom+xml"/>
  <link rel="replies" href="http://www.vox.com/services/atom/svc=comment/xid=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" type="application/atom+xml"/>
  <author>
    <name>mattn</name>
    <uri>http://mattn.vox.com/</uri>
  </author>
  <category term="携帯から" label="携帯から"/>
  <content xmlns:default="http://www.w3.org/1999/xhtml" type="xhtml">
    <div xmlns="http://www.w3.org/1999/xhtml">
      <p>今日、とある遊園地の横を通り過ぎた時に嫁が「この遊園地スケスケやなぁ」って言って来た。少し考えた後「うん」と答えた。どうやら嫁は「この遊園地スカスカやなぁ」と言いたかったらしい。ところで「スケスケ遊園地」って、ちょっとエッチ…</p>
    </div>
  </content>
</entry>
このエントリフィードの、type="application/atom+xml"であるlink要素のhrefに対してPOST(PUT)すれば更新、DELETEすれば削除となります。
POST時に送信する文書は、feedからentryのみを抜き取った、丁度上のようなXMLを送信する事になります。
前回、今回とブログサービスが提供するAPIについてお話して来ましたが、最近はAtomPubの方が活発だったりします。
XMLRPCの場合は特に決められたXMLネームスペースが存在していない為、貧祖なXMLパーサでもクライアントを実装でき、それ程敷居は高くないかと思います。
※但し、XMLRPCのパラメータは各サービスによって型が統一されていない場合があります。汎用的に作るのならば苦労するかもしれません。

それに比べAtomPubでは仕様が結構キッチリと決められている為、各サービス毎に異なる仕様で苦しめられる事はあまりありませんが、XMLネームスペース/スキーマを意識してXMLを操作しなければならなくなります。
幾分AtomPubを使ったクライアント実装の方が敷居が高い気もします。
※参考にするならばVoxが良いと思います。

今後、ブログAPIの主流としては、AtomPubがリードして行くと私は思っていますが、クライアントアプリがXMLRPCもAtomPubも対応しなければならない時代は、早く消え去った方が良いのでは?と切に願うばかりです。

ちなみに...
私のサイトではblosxomというブログツールを使用しています。blosxomをXMLRPCで操作しようというライブラリは既に存在しており私も使ってはいるのですが、AtomPubで実装した物はありませんでした。
昨日からですが、少しずつ作り始める事にしました。
ある程度出来上がったらCodeReposに上げる予定です。

追記
id:teahutさんから、ご指摘頂きました。
確かにAtomPub(app)という点で、Voxは参考にならないかもしれません。
app要素の入った新しいAtomPubについては、こちらに仕様書の日本語訳があります。
Posted at by




pythonからSumibiWebAPIを扱うサンプルです。
SOAPpy使うと楽だこと...
#!/usr/bin/python
# -*- coding: utf-8 -*-

from SOAPpy import WSDL

wsdlobject = WSDL.Proxy("http://www.sumibi.org/sumibi/Sumibi_stable.wsdl")
result = wsdlobject.doSumibiConvert(query="sumibi")

for r in result.resultElements:
    print "%s:%s" % (r.type, r.word)

結果は j:炭火
h:すみび
k:スミビ
l:sumibi
となります。
queryが「hanasu」だと j:話す
j:離す
j:食なす
j:放す
j:跳なす
j:撥なす
j:刎なす
j:貼なす
j:張なす
j:はなす
h:はなす
k:ハナス
l:hanasu
となります。
SKKのように送り仮名開始位置が取得出来ればIMEに使えて便利かな...と思いました。
Posted at by




ソーシャルブックマークサービスと言えども数多くの物が登場してきている。代表的な物ならば、Wikipediaに挙がっている物でリストアップされているので、色々試して見てもいいだろう。
私の知識だけで言うならば、日本人が使ってる有名どころといえば
といったところだろうか。 上記の中でも私が使っている物は となる。なぜこれに絞っているか。それは人の集まりとAPI。

del.icio.us

del.icio.usは基本的にユーザインタフェースが英語の為、日本人向きではないかもしれない。しかし使用者は多い。これは使用者間でのコミュニケーションがあまりない事で逆に、コメントをソーシャルコメントではなくメモとして使う人が多い為。つまり個人の情報収集に向いているからと思われる。基本的にブックマーク登録は個人用の登録エリアか、ブラウザ拡張で行う為、ユーザは既にあるブックマークエントリページをほぼ見ない。これを見るのは被ブックマークエントリ投稿者。だからブックマークエントリページはほとんど検索エンジンからはヒットしない。さらに、はてなブックマークのようにユーザのブックマークひとつひとつにpermalinkが張られる訳では無いのでメタブクマも無く、ブックマークエントリを見て個々のユーザが周りを意識したコメントをする...という事も滅多に無い。
さらにブックマークエントリ毎に公開/非公開を設定出来るのが良い。私個人は技術情報等も合わせ全てdel.icio.usに登録してしまっており、実はブラウザ自身のブックマークにはBookmarkletしか入っていない。
※del.icio.usが死んでたらどうすんの?
なお、del.icio.usはマルチバイト文字が通るが、ブックマークエントリページにてIMEの確定ENTERキーを拾ってしまい、書きかけのエントリがポストされてしまうという不具合がある。これはFirefoxの拡張や、GreasemonkeyでENTERキーの動作を強制するスクリプト等で対応出来る。
(IMEの確定をENTERでなくCTRL+Mでやるのもアリ)
タグはスペース区切りで、ユーザの中にはdel.icio.usでマルチバイトのタグ名を付ける事を敬遠する人もいる。
また、モバイル端末で使うにはオフィシャルではないMobilicio.usを使う方法がある。
※ただし私は未だにMobilicio.usでブックマークを登録した事はない。

del.icio.usは登録照会系APIも提供しており、ブログパーツとしてもWidgetやJSONでエントリを出力するAPIもあれば、現在permalinkに幾らブックマークされているかを画像で返すAPIもある。このサイトも実際に使用しており
ブックマーク追加画面: http://del.icio.us/post?url=[perlmalnk]&title=[title]
ブックマークエントリ: http://del.icio.us/url/[md5_hash(permalink)]
被ブックマーク数画像: http://del.icio.us/feeds/img/savedcount/[md5_hash(permalink)]?aggregate
というリンク/画像をエントリ下部に表示しているので気になる方は試して頂きたい。
※ただし被ブックマーク数画像は他のサービスに比べ画像が数ピクセル大きい為、デザインしづらい。

はてなブックマーク

日本では最大級のソーシャルブックマークサービス。ユーザの集まりも多ければ、いざ人気が出るとブックマークされるスピードも半端ではない。私は以前、まぐれでホットエントリに入ってしまった記事を書いた事があるが、ブックマーク数が2日から3日で200まで到達した。500超えしている人等はもっとすごいんだろうな。
はてなブックマークはソーシャル性が強く、コメントひとつひとつにpermalinkが張られている事もあり、周りを意識したコメントが目立ったり、メタブクマも多く見られる。タグは[タグ名]という形式。[後で読む]タグを使った別サービスもある。
機能としては申し分無いが、公開/非公開が全体でしか設定出来ない事が唯一の難点だと思っている。しかしながら、はてなユーザ間でのメッセージ送信機能(IDコール)や、はてなスターという機能により、はてなの「はてなブックマークはソーシャルを極める」という意識が見えて良い。
時折、このソーシャル性がユーザに対して非難を受ける対象となるが、私個人としては「はてな」は好きだし、ユーザが固まり易いというのは「はてな」というサービスを使っている以上致し方ないと思っている。

私個人は、はてなブックマークをソーシャルな用途で使用している。
はてなブックマークはモバイル対応出来ており、はてなブックマークと連携したモバイル版ブラウザも使える。個人的にはdel.icio.usがモバイルに対応仕切れていない為、携帯からは「はてなブックマーク」に登録しておき、後でブックマーク同期を行う事にしている。
APIも豊富で、登録照会系APIに合わせ、IDコールやはてなスターとの連携機能も提供されている。このサイトでは
ブックマーク登録画面: http://b.hatena.ne.jp/append?[permalink]&h=[title]
被ブックマーク数画像: http://b.hatena.ne.jp/entry/image/[permalink]
というリンクが記事毎に張られている。なお、はてなブックマークはモバイル対応している為、本サイトでは携帯端末の場合だけ
ブックマーク登録画面: http://b.hatena.ne.jp/addmobile?mode=confirm&title=[title]&url=[permalink]
というリンクに変更するようになっている。

livedoorクリップ

個人的にはGoogle Readerを使っているので良く知らないが、livedoor readerと併用する方が多いらしい。ただし私のサイトにブックマークを置いていってくれる人を見る限り、livedoorクリップを使う方は大概他のサービスのミラーとして使っておられる事が多い。私個人はブログの被ブックマーク数表示にのみ使用している。
APIは登録照会系の他、被ブックマーク数画像も提供しており本サイトでも
ブックマーク登録画面: http://clip.livedoor.com/clip/add?jump=myclip&link=[permalink]&title=[title]
被ブックマーク数画像: http://image.clip.livedoor.com/counter/[permalink]
というリンクが記事毎に張られている。ただしモバイルアクセスの場合には
ブックマークエントリ: http://clip.m.livedoor.com/page/detail?link=[permalink]
というリンクに差し替えている。

なお、本サイトではBuzzurlもブックマークツールバーとして表示しているが、Buzzurlのブックマークエントリや登録画面はモバイルからアクセスするとモバイル専用ページが表示される為、上記の様な小細工は必要ない。
ブックマーク登録画面: http://buzzurl.jp/config/add/confirm?url=[permalink]&title=[title]
被ブックマーク数画像: http://api.buzzurl.jp/api/counter/[perlmalnk]

POOKMARK Airlines

個人的には、POOKMARK Airlinesを個人資料用途としてもソーシャル用途としても使っていない。POOKMARK Airlinesの設定でtwitterにも登録する機能があり、これだけの為に使っている。実際は使い込んでいない為、良し悪しが判断出来ていない。



以上が、私個人のソーシャルブックマークサービスの使い方。
結論から言ってしまうと、私はdel.icio.usと、はてなブックマークの2つしか本格的には使っていない。
本当は、他にも手を出して見たいがどうしても人のあつまる所に行き着いてしまっており、食わず嫌いな状態にある。
実際2つしか使っていないので、基本的に手作業で同期し、量が多い時のみPlaggerを使って同期している。

上記、私の様な使い方で他に良い物があれば、コメントやブックマークでも良いので教えて欲しい。
Posted at by




otsuneさんがやってたので、まねしー。

なるべくblosxom.cgi本体を弄りたくなかったのもあって、本来はETagとかを返す為に用意されたlastmodified2プラグインを使う事にした。otsuneさんと同じく、もそもろの編集をした後 「Posted at」の部分を Posted at <a href="$url$path/$fn.htm"><abbr class="updated" title="$lastmodified2::story_iso8601">$ti</abbr></a>
とした。とりあえずコノ辺で確かめてみたり、microformat Operatorで行けてそうだから、しばらく様子見。

ところで、microformat Operatorのプラグイン入れすぎで、ツールバーからはみ出してしまってるの、なんとかなりませんか...
↓コレ
microformat-operator-too-long
Posted at by




最近、Javaのお仕事をしているのですが、ドキュメントの生成にはもちろんjavadocを使ってます。

javadocではコメントに記述している引数が、実際のメソッド引数にない場合は警告を出してくれます。

ただ、開発者がよくやるのは、メソッドの引数を増やし、その引数のコメントを書き忘れるって行動。

この場合、javadocは警告してくれません。
(実はオプションであったりして...)


で、先日気づいたのですが、ドキュメント生成ツールであるDoxygenがこの記入忘れのコメントを警告で出してくれるという事実。

これは使える...
Posted at by




私はこの業界に入ってから10年以上となるが、10年前といえば

C言語だけ学んでおけば食いっぱぐれる事は無い

と教えられてきた。

だが世の中の情勢も変わり、リッチなコンテンツがインターネットを介して配信され、Webサービスと呼ばれる形となって世の中に普及した。

プログラマとして求められるスキルも昨今はWeb開発経験のスキルが問われ、perl,php,python,ASP.NET等といったライトウェイトなスクリプト言語を使用しているプロジェクトも全く珍しくなくなった。

その昔、「API」といえば誰しもが「Win32 API」と答えたが、いまではすっかりWebアプリケーションに対するリモートプロシージャコールや、サーバアプリケーション操作用のインタフェース郡を意味するのが一般的になりつつある。

ブラウザもしかり、XMLHttpRequestやJSONという技術でこれまでの限界を、そしてドメインを越え、新たな幕を開けようとしている。

Web開発者は何かを得た事になるんだろうか。

市民権?
永住権?

まぁ数年の間は、Web開発で御飯が食べられる、良い時代が過ぎ行くのでしょうね。

Web開発だけで会社を起こしたり
Web開発だけで家を建てられたり
Web開発だけで嫁さんを貰えたりと...(ないか)


でも、私にはそろそろWeb2.0の底(天井?)が見えてきてしまった気がしてならない。

そろそろ、Web3.0への足音が鳴り出してもいいんじゃないか?
そんな気がする。

最近、mixiなんかのSNSよりも、twitterを代表とするゆる系ソーシャルと、それらが提供するAPIによってマッシュアップされた連携サービスが、巷では大流行しているようです。表面はチャットとして位置付け、とてもログ的で、いうなればストリームを意味し、そのストリームをマッシュアップさせて別のアプリケーションとして生み出して行くのです。
もしかしたら、これが未知なる足音への導き?いや違う。

サービスとサービスが、そしてローカルとリモートが隔たり無く、ユーザが行き来すること無く繋がり合える。そんなのがWeb3.0になるんじゃないかと、なんとなく思った、午前3時半でした。


やばい。寝よう。
Posted at by




PyGTKを使ってクライアントを作ってみた。
とりあえずテスト

#うまくいったら近々公開
Posted at by




ClickCommentsのようなものを作ってしまったよ!

このページの下(Writebacks)に動くものがあるよ!
クリックすると「なると」が増えるよ!
なんで「なると」なのかは聞かないでね!
使うときは、フレーバに $naruto::link
と書くといいよ!
あくまでblosxomとJSONで何か作ってみたかっただけだから、実用性ないよ!
もしかしたらWeb拍手みたいに使えるかもしれないよ!

blosxom大好きっ子は、これを改造してバラまいてClickCommentsのシェアをどんどん下げちゃえばいいと思うよ!!!

元ネタ
http://d.hatena.ne.jp/amachang/20070807/1186485054
http://d.hatena.ne.jp/Hamachiya2/20070804/browser_crasher
amachang、はまちちゃん、勝手にテンプレート使ってごめんね!><
ダウンロード:

Posted at by




もっかいテスト
Posted at by




実は、ここのサーバのJcode.pmがちゃんと動いてなかった事に気づいた。

最近トラックバックスパムがやたらと文字化けを起こすなぁ...って思ってたんです。まぁ、相手のサーバの文字コードなんかお構い無しに放り投げてくるのがスパム!な訳ですから、化けてあたりまえ...
いや待てよ、blosxomのwritebackプラグインでJcode::convert変換してたはず。しかもutf8でだ。
で、ようやく重い腰をあげる。

調べてる内に、ここのサーバではJcode.pmがutf8で全滅している事に気づいた。
このサーバではXSは置けないのでJcode::Unicode::NoXSが動いているものとばかり思っていたのだが、いやいや...

うんちゃらかんちゃら、弄ってる内にメンドクサクなってきたので、ついいつもの適当ハック癖...。
Jcode/Unicode/NoXS.pm内のメソッドをJcode::XXXからJcode::_Classic::XXXに一括痴漢^H^H置換して、お茶を濁す。

--- Jcode/Unicode/NoXS.pm.orig  2007-06-26 00:31:47.000000000 +0900
+++ Jcode/Unicode/NoXS.pm   2007-06-26 03:23:49.000000000 +0900
@@ -53,7 +53,7 @@
 # instead of being 'use'd (No package export done) subs below
 # belong to Jcode, not Jcode::Unicode

-sub Jcode::ucs2_euc{
+sub Jcode::_Classic::ucs2_euc{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
     _init_u2e();
@@ -68,7 +68,7 @@
     $$r_str;
 }

-sub Jcode::euc_ucs2{
+sub Jcode::_Classic::euc_ucs2{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
     _init_e2u();
@@ -84,21 +84,21 @@
     $$r_str;
 }

-sub Jcode::euc_utf8{
+sub Jcode::_Classic::euc_utf8{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
-    &Jcode::euc_ucs2($r_str);
-    &Jcode::ucs2_utf8($r_str);
+    &Jcode::_Classic::euc_ucs2($r_str);
+    &Jcode::_Classic::ucs2_utf8($r_str);
 }

-sub Jcode::utf8_euc{
+sub Jcode::_Classic::utf8_euc{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
-    &Jcode::utf8_ucs2($r_str);
-    &Jcode::ucs2_euc($r_str);
+    &Jcode::_Classic::utf8_ucs2($r_str);
+    &Jcode::_Classic::ucs2_euc($r_str);
 }

-sub Jcode::ucs2_utf8{
+sub Jcode::_Classic::ucs2_utf8{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
     my $result;
@@ -121,7 +121,7 @@
     $$r_str = $result;
 }

-sub Jcode::utf8_ucs2{
+sub Jcode::_Classic::utf8_ucs2{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
     my $result;

とりあえず、動いてそげ。
Posted at by




blosxomでモブログをするには、2つの機能が必要となります。
  1. XMLRPCでのポスト
  2. メール受信でXMLRPCをキックするサーバ
前者については、「BXR: Blosxom XML-RPC Interface>」というスバラシイものがあるので、これを利用します。
ただ、後者については、サーバを用意出来ない方もいらっしゃると思います。そこで登場するのが「サイドバー.jp」です。このサービスでは、モブログ支援ということで、各アカウント毎に用意されたメールアドレスに対して記事を投稿すると、設定していたXMLRPCサーバをコールしてくれる機能を持っています。

続きを読む...

Posted at by




このサイトはPerl/CGIで出来たブログツール、blosxomで構築しています。
個人的には
テキストファイルがデータ本体であり、フレーバと呼ばれるレンダラによってブログエントリ化する。バックアップしたいなら、テキストファイルをアーカイブすればいいし、記事を消したいならば、そのテキストファイルを消せばいい。
って所が好きで、実はこのサイトを作る前から、自分専用のメモシステムとして使ってました。

続きを読む...

Posted at by




ちょこっと時間が取れましたので、ず〜っと宿題のまま残ってて気になってた件を片付けました。
blosxom-xmlrpc.cgiで、幾らかのブログクライアントを使うと、フォルダ名称(ファイルの存在場所)をカテゴリとして保持するblosxomの制約事項により、setPostCategoriesメソッドを呼び出された事で変わってしまった記事ID(ファイル名)が参照出来なくなる問題です。

申し訳ないですが、適当に解決させていただきました。苦笑

setPostCategoriesが呼ばれたタイミングで、元の記事ファイルを指定のカテゴリ(フォルダ)に移動し、「元のファイル名.post」というファイル名で、どこにリネームされたのかを出力しておく。

もし、getPost等で古い記事IDが参照される場合は、「元のファイル名.post」から、実際の記事ファイルを参照し処理するといった仕組みです。
なにぶん適当すぎて、使用価値があるかどうか分かりませんが、一応ソース差分を添付しておきます。
#「元のファイル名.post」というファイルをいつ消すのかについては、今後の宿題にします。

ダウンロード:
Posted at by




このサイトのfeedは、Feedburnerを使って焼いていません。いずれdel.icio.us等と一緒に配信させて頂くことになるやもしれませんが、なんとなくブログのフィード購読者数をカウントするCGI作ってみました。
例によって2〜3分で出来た代物なので(ブログ書いてる時間のほうが長いやろ)、中身のクオリティは適当です。
大体の著明なFeed-Fetcherは、ユーザエージェントに「XXX subscriber」という文字列を埋め込んできます。このXXXにそのフィードリーダのsubscriber数が格納されています。
blosxomのloggerプラグイン(もしくはココ)が出力したログファイルから、このsubscriber部を抜き取り著明なフィードリーダ毎購読者数をカウントします。

livedoor FeedFetcher/0.01 (http://reader.livedoor.com/; 23 subscribers) ...
#!/usr/bin/perl

use strict;
my $logfile = "/blosxom/logs/access.log";
#my $logfile = "access.log";
my %agents;
my %bot_ua = (
  'Hatena' => 'Hatena\x20RSS',
  'NewsAlloy' => 'NewsAlloy',
  'FreshReader' => 'FreshReader',
  'Feedpath' => 'Feedpath',
  'Google Reader' => 'Feedfetcher-Google',
  'Rojo' => 'Rojo',
  'Bloglines' => 'Bloglines',
  'Livedoor Reader' => 'livedoor\x20FeedFetcher',
);
open(FILE"<$logfile");
while(<FILE>) {
  my $line = $_;
  $line =~ s/^([^\t]+\t){2}([^\t]+)\t.*/\2/;
  if ((my $ua) = grep {$line =~ /$bot_ua{$_}/} keys(%bot_ua)) {
    $line =~ s/^.* ([0-9]+) subscriber.*\n$/\1/;
    $agents{$ua} = $line if ($agents{$ua} < $line);
  }
}
close(FILE);

print <<EOF
Content-Type: text/html;

<html>
<head><title>Your Feeds Subscriber</title><head>
<body>
<table border=\"1\">
EOF
;
my $sum = 0;
while(my ($key$value) = each(%agents)) {
  print "<tr><td>$key</td><td>$value</td></tr>\n";
  $sum += $value;
}
print <<EOF
<tr><td>total</td><td>$sum</td></tr>
</table>
</body>
</html>
EOF
;

いや、つまらない事しか書いてないのに、購読者がいらっしゃるとは...
※実際にはクライアントサイドで閲覧していらしゃるでしょうから、正しい値とは限りません。
Posted at by




最近は技術情報ばかり書く機会が多く、このブログにメールから投稿する事はあまりなくなりましたが、もしかすると需要はあるかもしれないので...
blosxomをXMLRC経由でポストするスクリプトBXR(blosxom-xmlrpc.cgi)をWindows Live Writerからポストするとサーバ側でパースに失敗していました。
調べた結果、Windows Live WriterはXMLの先頭にBOMを付けてくれるらしく、XMLRPC::Transport::HTTP::CGIがパースエラーを起こしてました。
とりあえず私の所は応急処置で、User-Agentを見て先頭3バイトを削るワークアラウンドで逃げる事にしました。
またWindows Live WriterはcategoryNameとcategoryIdを正しく区別出来ていないようなので、こちらもUser-Agentを見てcategoryIdのみ処理するよう修正しました。
※Windows用にbinmodeしたほうがいいかもしれない。

これで外部XMLRPCキッカー(sidebar.jp等)を使用したモブログ、ブログエディタ等でポスト出来るようになります。

ダウンロード:
Posted at by




Livedoor Readerの海外版、「FastLadder」が公開されました。
FeedFetcherのユーザエージェントは、LDRとそっくり「Fastladder FeedFetcher/0.01」でした。
さて、弄り倒しますか。

fastladder-20070703

あわせて「フィード購読者数カウンタ for blosxom」も修正しました。
ダウンロード:
Posted at by




XMLRPC(XML Remote Procedure Call)は、HTTPを介してXMLを扱い透過的にサーバ側のメソッドを実行するAPI実装です。

最近のミニブログ界ではJaikuがGoogleに買収されてからJaikuに人が入り始め、賑わってから少し経ちます。ミニブログで代表的なTwitterとJaikuは一見同じミニブログに見えて、APIとしては違うものを採用しています。

TwitterのAPIは基本的にRESTとBasic認証および出力フォーマット指定というAPIを採用しており、クライアントを作成する開発者はURLに対してGET/POST出来るライブラリと、XML/JSON/RSS/ATOMの中から自分の使いたいフォーマットを処理出来るライブラリを選ぶ事が出来ます。
またJaikuのAPIはBasic認証ではなくpersonal_keyと呼ばれるAPIKEYとユーザ名で認証します。取得系はTwitterとそれ程変わりませんが更新系は以前にも書いた通りJSONもしくはXMLRPCに限定されてしまっています。つまりJSONで更新したらJSONのレスポンスが、XMLPRCで更新したらXMLRPCのレスポンスが返されます。JSONが扱いにくい言語を使用する場合にはXMLRPCを強制されてしまう事になります。

さて今日はJaikuのAPIに限った話ではなく、Jaikuを使ってXMLRPCのクライアント実装と、XMLRPCサーバについての話をしたいと思います。
まず送受信されるデータについて。

XMLRPCは、決められたXML構成にメソッド名および構造化可能なパラメータをリクエストとして送信し、同じく構造化可能なレスポンスを受け取るXML送受信APIです。
リクエストフォーマットは以下の様な記述になります。
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
  <methodName>メソッド名</methodName>
  <params>
    <param><value><string>パラメータ1</string></value></param>
    <param><value><string>パラメータ2</string></value></param>
  </params>
</methodCall>
またレスポンスフォーマットは以下の様になります。 <?xml version="1.0" encoding="utf-8"?>
<methodResponse>
  <params>
    <param>
      <value>
        <string>結果</string>
      </value>
    </param>
  </params>
</methodResponse>
リクエストパラメータは「params」というノードを括られており、配列になっているのが分かるかと思います。また値が格納される「value」ノードには「<string>」や「<int>」といった、その値の型が定義されています。
上で書いた通り、リクエストパラメータとレスポンスは構造化出来ますので、例えばレスポンスで構造体を表すならば <?xml version="1.0" encoding="utf-8"?>
<methodResponse>
  <params>
    <param>
      <value>
        <struct>
          <member>
            <name>メンバ1</name>
            <value><int>157</int></value>
          </member>
          <member>
            <name>メンバ2</name>
            <value><string>Sample</string></value>
          </member>
        </struct>
      </value>
    </param>
  </params>
</methodResponse>
と書く事が出来ます。
詳しくはXMLRPCのオフィシャルサイトで確認出来ます。またオフィシャルサイトではXMLRPCを使った処理系毎の実装一覧XMLRPCが使えるサービス一覧も確認出来ます。

ところで、Windows Live WriterBlogWriteubicast Blogger等といったブログ投稿ツールはXMLRPCを使用してブログ記事を操作しています。それらAPIは以下の様に分類されています。
  • Blogger API: Bloggerが提供してるAPI
  • metaWeblog API: Blogger APIに足らない部分を補足する形で登場したAPI
  • MovableType API: Movable Typeが提供してるAPI
  • LiveJournal API: Bloggerが提供してる独自API
一般的に汎用的なブログ投稿ツールでは上記の内、上3点を標準実装しています。
さてJaikuの場合、XMLRPCのインタフェースは一つしか公開されておらず(2007/11/01時点)、そのスペックは presence.send({
    user => 'your username as string',
    personal_key => 'your personal key as string',
    message => 'message as string',
    optional icon => 'icon number as int',
    optional location => 'location as string',
    optional generated => 'generat flag as boolean'
})
こんなイメージとして表現出来ます。
簡単な物をPerlで書くならば

jaiku.pl
#!/usr/bin/perl
use strict;
use Encode qw(from_to);
use XMLRPC::Lite;
use Data::Dumper;

my ($user,$personal_key,$message,$icon) = @ARGV;
from_to($message, "shiftjis", "utf8") if $^O eq "MSWin32";

print Dumper(XMLRPC::Lite
    ->proxy('http://api.jaiku.com/xmlrpc')
    ->call('presence.send', {
            user => $user,
            personal_key => $personal_key,
            message => $message,
            icon => ($icon || 300),
        })->result);
と書く事が出来ますね。これをコマンドラインから
./jaiku.pl username xxxxxxx 本日は晴天なり ※「xxxxxxx」はpersonal_keyです。APIドキュメントの右下あたりに記述されています。
と実行すればJaikuのステータスが更新されます。例では最終引数に省略可能なアイコン番号を付けていますので ./jaiku.pl username xxxxxxx 曇り 398 と実行すれば
jaiku-icon-cloudy
といった感じにアイコンを付ける事が出来ます。
なお、Jaikuのアイコン番号に対する実際のアイコン対応表を作りました。
JavaScriptをOnにして頂いて、以下のリンクをクリックして下さい。
アイコンリストを表示
さて話をXMLRPCに戻して、Perlでは「XMLRPC::Transport::HTTP」というXMLRPCを透過的にPerlのパッケージおよびサブルーチンとバインドしてくれるモジュールがあり、XMLRPCサーバを簡単に実装する事が出来ます。
例えば数値パラメータ2つ貰い足した結果を返すXMLRPCサーバならば以下の様に実装出来ます。 #!/usr/bin/perl

use strict;
use XMLRPC::Transport::HTTP;

package calc;
sub plus {
    shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ );
    my $lhs = shift;
    my $rhs = shift;
    my $res = $lhs + $rhs;
    return {result=>SOAP::Data->type('int' => $res)};
}

XMLRPC::Transport::HTTP::CGI
    ->dispatch_to('calc')->handle;
モジュール名がpackage「calc」に、メソッド名がサブルーチン「plus」にバインドされている事が分かるかと思います。
これを実行するには #!/usr/bin/perl
use strict;
use warnings;
use Encode qw(from_to);
use XMLRPC::Lite;
use Data::Dumper;

my $url = 'http://example.com/xmlrpc/calc.cgi';
warn Dumper(XMLRPC::Lite
    ->proxy($url)
    ->call('calc.plus', 1, 2)->result);
というクライアント側のコードで実行出来ます。
勿論、XMLRPCを扱うならばクライアント/サーバはPerlでなくても良い訳です。pythonで実装するならば from xmlrpclib import ServerProxy
proxy = ServerProxy("http://example.com/xmlrpc/calc.cgi")
print proxy.calc.plus(1, 2)
という実装になります。
XMLRPCは既に色んな言語で実装されており、それぞれに使い方は異なりますが扱う仕様が決められている為に、移植に戸惑う事はそれ程ないかと思います。
※特にpythonに関してはメソッドバインディングされますので、かなり透過的に扱えます。

例を元にXMLRPCを説明して来ましたがどうでしたでしょうか?
色んな言語でJaikuクライアントを実装してみるのも面白いかもしれませんね。

Posted at by




Google Code Searchに「Google ソースコード検索にコードを追加」というページが出来ていました。
単にCVSやSVNのリポジトリだけでなく、.tar、.tar.gz、.tar.bz2、.zipといったアーカイブファイルの中身までクロールしてくれるようです。
コードを晒したいけどCVS、SVNサーバをお持ちでない方には朗報かもしれませんね。
Posted at by




もごもご vimからもごもごを閲覧/更新できるアプリケーションを作りました。
ベースとなっているのは、Twitter : A simple client for Twitterです。
以前、この「Twitter : A simple client for Twitter」のマルチバイト周りとWindowsで動作するように修正した物をtwitter上でひっそりとアナウンスしましたが、それをベースにもごもごバージョンを作ってみました。
もごもごって、public_timelineでも認証がいるんですね...汗
使い方は、twitter版と同じ

publicステータス取得
:Mogo2PublicTimeline friendsステータス取得
:Mogo2FriendsTimeline ステータス更新
:Mogo2StatusUpdate 今日も、もごもご となります。
※スレッドへの発言も同様に
:Mogo2StatusUpdate >>343473 もごもご... と実行します。

ダウンロード:
※witter.pyがpythonスクリプト内でグローバルなメソッド名称定義してくれてますので、mogo2.pyでは接頭語「Mogo2」を付けてます。

また、オリジナルのtwitter版をマルチバイト/Windows対応した物も一緒に配布します。こちらは精神衛生上の都合もあり上記接頭語を「Twitter」に変更しています。
ダウンロード:
上記スクリプトのインストール方法は...メンドイので省略
Posted at by




もごもごAPIコンテストにて「WEB FLASH賞」を頂きました
先日、家のポストに「Dragon.jp」と書かれた郵便物が届いていました。
Dragon.jpからの贈り物1

中を開けると、もごもご運営事務局殿からの手紙と「WEB FLASH」という情報誌。
Dragon.jpからの贈り物2

それと「もごもごステッカー」が5枚入っていました。

「WEB FLASH」はこれから目を通します。ありがとうございました。
※Web屋でもない私が、こんなに高い^H^H いい本持ってていいんだろうか...

APIコンテスト等といったものに参加させて頂くのは今回が初めてでしたが、とても楽しめました。APIを使って実際にアプリケーションを幾つか作る事で初めてtwitterと、もごもごのAPIの差に気付くことが出来たり、上位入賞者の方々のマッシュアップ作品で勉強もさせて頂きました。

このような企画を用意して頂いた、もごもご運営事務局の方にお礼を申し上げます。次回もこのように楽しめる企画を期待しております。

ありがとうございました。

なお「もごもごステッカー」は眼鏡ケースに貼らせて頂いてます。
Dragon.jpからの贈り物3

残りの4枚は、どこに貼ろうか検討中です。
Posted at by




関西の食パンは5枚切りなの?
そうですよ。うちは5枚切りですよ!当たってますよ!
って事で、パンが何枚切りかで関西人かどうかを判断するAPIを作りました。

http://mattn.kaoriya.net/cgi-bin/breadslice.cgi

パラメータ:
slice: 何枚切りか
callback: JSONコールバック
サンプルリクエスト:5枚切り

簡単に作ったアプリケーション
あなたのパンは何枚切り?
あなたは...


これで気になるアノ人が関西人かどうか分かります。
Posted at by




linux用もごもごクライアント、GtkMogo2のバージョン0.0.1をリリースします。

使い方は...感覚で分かるかと思います。





コードリポジトリはgtktwitterと共に、Google Project Hostingに移行しましたので、後日追ってお知らせいたします。

gtkmogo2-20070626
Posted at by




なんか寝つけないので、もひとつ愚痴...
最近、お仕事でJava使ってます。

といってもJavaの画面上にはActiveX(Ocx)が張りついてます。しかもそのActiveXって.NETで出来てるんです。どんなシステムなのかと...苦笑
まぁそれが言いたいのでなく、COMやATLをJavaアプリで動かすので、Javaのメインスレッドを食われない用に、STAやATLで作ったOcxは別スレッド起こして実行してます。
なんかこういう場合にも、メインスレッドを汚ない(出来ればCOMなんか関与しない)ようなコードが、簡単に書けないものか...

非常にメンドイです。

そうそう今日職場で休憩時間にMSから出てるPowerShellっていう管理者用(?)シェルを試してみました。
感想としては、まさしく...「コマンドプロンプトに.NETという毛を生やした」というのが相応しく(いや、変な意味でなく)、私にとってはPowerとまでは行きませんが、違う物としてみれば使える代物なのかもしれません。

ただ、exeもパス内のものが実行出来るのですが、「startコマンド」が使えない。私にはダメージが大きすぎる。
私は仕事中はだいたい70〜80%をコマンドプロンプトの中で生活しますから、たまにエクスプローラを開くときなんかは、「メニューから...」なんて事はしないんですよね。「cd」してエクスプローラが開きたくなったら
C:\foo\bar\> start .
ってやるんです。
この「startコマンド」がないと、まるで周りからはPCド素人に見えてしまうんですよ。私...

マイクロソフトさん、私を助ける意味でもPowerShellに「startコマンド」を付けて下され...


マイクロソフトで思い出しました...
今日IronPythonを試しました。以前に少し触った事はあったのですが、MSエバンジェリストのブログで、「IronPythonを使ったMSAgentのサンプル」を見つけたので、試してみたんです。
ほう...確かに動く。


...で?
これってpythonのwin32com使ってもおんなじやん...
まぁアセンブリ参照みてモジュールベースのコーディングが出来るのかもしれないんですが、pythonって呼出フック出来るから、結局似たようなコードになるんじゃ...
ちなみにExcelを操作するpythonのコードは...
import win32com.client
xlApp=win32com.client.Dispatch("Excel.Application")
xlApp.Visible=1
xlApp.WorkBooks.Add()
sheet=xlApp.Sheets(1)
sheet.Cells(1, 1).Value="Hello Python"
こんな感じ?じゃぁ、「.NET」である必要って何?

あと余談ですが、MSAgentってSTAでしか動かないんすね。おまけにイベントシンク(IAgentNotifySink/IAgentNotifySinkEx)ってInvoke呼出してくれないし...泣
汎用ディスパッチシンクを呼ばせるには、IDispatchを継承してQueryInterfaceで嘘を返してあげればいいの?
#GetTypeInfoCountもGetTypeInfoも呼ばれない、IAgentNotifySinkを継承してないインスタンスをRegisterメソッドに渡すとエラー出るし...


さぁいい加減に寝ないと、死ぬぞ...
Posted at by




以前書いた「LuaでTwitterるわ!」の「もごもご」版です。

続きを読む...

Posted at by




おそろしやおそろしや...

むかーしむかし、Microsoft Visual J++という道具があったそうな...
その道具では、関数呼出の参照渡しを実現するために、配列を渡してJNI内部で値を設定するといった、奇怪な手法が使われておりましたとさ。

さて話は現代に移り、Microsoft Visual J#という道具が巷では出回っていると聞き、さっそく試した私は、腰を抜かしてしまいそうになりました。
public class Test
{
    public static void test1(int a) { a = 2; }
    public static void test2(/** @ref */ int a) { a = 2; }
    public static void main(String[] args) {
        int b = 3;
        test1(b); System.out.println(b);
        test2(b); System.out.println(b);
    }
}
まさか、こんな結果になりますとは...

Javaという言語仕様まで変えてしまうとは...

おそろしや、おそろしや...


#まぁ、Javaって言ってない(J#)から、いいんですか...
#そうですか...
Posted at by




nayoyaグループ - naoyaの日記 - Gearmanのやつ#2
clouderさんは結局、MSG_WAITALLを使う方法を取られたようですね。個人な趣味としてはあまりMSG_WAITALLは使わないほうなので、きっと私の場合はループを回すかな。
理由は大した事ではなく、サーバ側で「Content-Length」を出力し、そのContent-Length数分データを送信するようなCGIを書いた場合、バグで「Content-Length」分満たない内に落ちてしまった場合に、クライアントの受信がMSG_WAITALLだとバッファ全部破棄されてしまいエラーハンドリングし辛いからです。
(※たとえばどこまで受信したかが分からないとか...)
今日は本題から外れますが、上記リンクの中にも出てきたソケットディスクリプタから「fdopen」する処理をWindowsではどう書くかをご紹介。

続きを読む...

Posted at by




ローカルDBやメモリDBとして使えるSQLiteは、開発者にとってかなり有用なツールです。 私はよく「あーーーあのSQLどう書こう」なんて悩む時にSQLiteのshell版を使います。今日はそのWin32 SQLite3のshell版sqlite3.exeの作り方をご紹介。

まず、ダウンロードページから
sqlite-3_5_0.zip
というファイルをダウンロードします。
次に、ソースツリー内の「src」ディレクトリに移動し、以下のMakefileを置きます。
.SUFFIXES: .c .obj

all : sqlite3.exe

sqlite3.exe :  alter.obj analyze.obj attach.obj auth.obj btree.obj build.obj callback.obj complete.obj date.obj delete.obj expr.obj func.obj hash.obj insert.obj legacy.obj loadext.obj main.obj opcodes.obj os.obj os_os2.obj os_unix.obj os_win.obj pager.obj parse.obj pragma.obj prepare.obj printf.obj random.obj select.obj shell.obj table.obj tokenize.obj trigger.obj update.obj utf.obj util.obj vacuum.obj vdbe.obj vdbeblob.obj vdbeapi.obj vdbeaux.obj vdbefifo.obj vdbemem.obj vtab.obj where.obj mutex.obj mem1.obj malloc.obj
    cl /Fesqlite3.exe alter.obj analyze.obj attach.obj auth.obj btree.obj build.obj callback.obj complete.obj date.obj delete.obj expr.obj func.obj hash.obj insert.obj legacy.obj loadext.obj main.obj opcodes.obj os.obj os_os2.obj os_unix.obj os_win.obj pager.obj parse.obj pragma.obj prepare.obj printf.obj random.obj select.obj shell.obj table.obj tokenize.obj trigger.obj update.obj utf.obj util.obj vacuum.obj vdbe.obj vdbeblob.obj vdbeapi.obj vdbeaux.obj vdbefifo.obj vdbemem.obj vtab.obj where.obj mutex.obj mem1.obj malloc.obj

.c.obj :
    cl /c /DSQLITE_THREADSAFE=0 $<
後は、Visual Studioのコンパイラにパスが通ってるならば「nmake」でコンパイルします。 なおSQLiteは、内部処理がUTF-8でやり取りされており、Win32版の場合はNTかどうかでCreateFileA/CreateFileW等、ワイド文字APIの呼び方を変えています。
しかし、コマンドライン引数には対応出来ておらず C:¥> sqlite3.exe データベース.db なんて事すると、エラーが発生します。ちなみにNTでない場合には変換無しにCreateFileAを使いますから問題なく動きます。
これって、コマンドラインをUTF-8にすれば動くんだろうけど C:¥> chcp 65001 やってUTF-8にしても文字は化けるし、IME動かないし意味無いですよね。
適当に --- os_win.c.orig   Mon Sep 03 22:39:38 2007
+++ os_win.c    Fri Sep 28 22:17:50 2007
@@ -137,6 +137,8 @@
 # define isNT()  (1)
 #else
   static int isNT(void){
+    if( GetACP() != CP_UTF8 )
+      return FALSE;
     if( sqlite3_os_type==0 ){
       OSVERSIONINFO sInfo;
       sInfo.dwOSVersionInfoSize = sizeof(sInfo);
こんなパッチ当てて、実行すると日本語のファイル名も扱える様になります。
このEXEをUSBなんかで常備しておくと、いざという時に助けられるかもしれませんね。

Posted at by




ショートカット一発検索で生活が変わる を見ていて、普段自分がWindowsで使ってるキーボードショートカットを書いて見る事にする。

ウィンドウ最大化
Alt-Space Alt-X
押し方はAltキーを押しっぱなしで「スペース」、「X」と連続で押す。
元に戻したい場合は「X」の代わりに「R」を押す。
システムのプロパティ
Win-Pause
押し方はWindowsキーを押しながらPauseキー
まれに無限ループするプログラムを書いてしまった時等に、プロセス強制終了したい場合等に使う。
コンピュータのロック
Win-L
押し方はWindowsキーを押しながら「L」キー
突然会議に召集を掛けられた場合には、すかさず「Win-L」して席を外す。
番外編
普段からコマンドプロンプトを使う事が多い私は、デスクトップにコマンドプロンプトのショートカットを貼り、そのショートカットのプロパティでショートカットーキーを「Ctrl + Shift + Alt + F12」に設定している。
これでどの画面からでも「Ctrl + Shift + Alt + F12」でコマンドプロンプトを起動出来る。
基本的にはオリジナルの設定を変える事はしないほうです。便利だと思ったら付け足すくらい。
ただvimrcは、もう911行もある...。
vimrcでどんな事やってるかについては、また違うエントリで...
Posted at by




久々動かしたら、動かなくなってました。PLAYLOGで、日付に関する制約が入り、バグを直すのを忘れてました。一応これで
3点修正が完了しました。(上記2点は既に修正済)
一応、何もバージョンアップしないというのもカッチョワルイ話なので、Music Player Daemon(mpc)で再生した曲情報アップロードにも対応しました。
個人的には一番軽いので、気に入っています。

これで「音ログ for Linux」がサポートしているプレーヤは
となりました。ただ、まだリリースされたばかりなのでPLAYLOG再生履歴でのクライアント名表示には「?」と表示されます。一応、事務局のほうにも新しいクライアントが増えた事は伝えておくつもりです。
#幾分、知名度がないクライアントですので採用されない可能性もあります。
#ちなみに、この曲をPLAYLOG再生履歴に残したの、私が初めてのようです。苦笑

otolog4linux-20070628

お知らせ:
ところで...
最近のニュースでも取り上げられている通り、MSの強力により、レーベルゲート自信がPLAYLOGのアップローダをリリースする事になる為、「プレイログアップローダ for WindowsMediaPlayer」は、よほど重大なバグでも見付からない限り、バージョンアップを行わない方針に決めさせて頂きました。

これまで幾度かリリースさせて頂きましたが、使用して頂いた皆様、バグ報告を頂いた方、本当にありがとうございました。
Posted at by




音ログ for LinuxをCodeReposに入れました。
/lang/python/otolog4linux/trunk - CodeRepos::Share - Trac
「音ログ for Linux」は音ログをLinuxでもやりたい!という思いから開発を始めたソフトウェアで、現在では音ログAPIを実装したPLAYLOGという音楽SNSで利用出来ます。具体的には
といった音楽プレーヤの現在再生曲情報がPLAYLOGにアップロードされます。
名前にはLinuxとありますが、iTunesを使ってWindowsでも動きます。結構前からコード放置状態にありますが、そのままにしておくのも勿体ないので、CodeReposに上げる事にしました。へたっぴなpythonのコードですが少しでも誰かの役に立つのならば嬉しい話です。

Posted at by




色々と弄っているうちに、Safariのメニュー部のフォントが戻らなくなりました。

safari4win_broken_font
調査の結果 C:\Documents and Settings\xxxx\Local Settings\Application Data\Apple Computer\safari xxxxはユーザ名称

のフォルダを削除する事で、フォントキャッシュが削除される事が分かりました。
どうやらSafari君、高速化の為かフォントをキャッシュしているようですね...

追記1:
削除は自己責任でお願いします。

追記2:
WebKitPreferences.plistに設定するフォント名は、上のフォルダにあるFonts.plist内に指定されている別名で指定すれば、「MS ゴシック」のようなフォント(MS-Gothic)も設定出来ると思います。
Posted at by




昨日のWWDC2007キーノートのサプライズで、Safari for Windowsがリリースされました。家はLinuxだったので手を出せず、先程ようやくダウンロードして起動出来ました。
フォント設定に少し難がありますが、とりあえず(?)動いております。

使った感じですが...

落ちる落ちる落ちる...

化ける化ける化ける...

テキストエリアが縮む縮む縮む...

スクリプトが動かない動かない動かない...

まぁベータですから...汗
最初はブラウザからこのエントリを書こうかと思いましたが、テキストエリアがつぶれて書けませんでした。

safari4win_broken_textarea

でもまぁ、UIとしてはスッキリしてて嫌いではないです。実は私、むかーしむかしはマカーだったりして...

safari4win

日本語を表示する際には、フォント名称に日本語を含まない物が良いのですが、Monaフォントが一番適していると思われます。

さてこれで、AppleはWindowsのブラウザシェアに参入した訳ですが、このベータをどれだけの期間で、使い物になるブラウザに持っていくかでAppleの技術力が問われてしまう事になりそうです。
iTunesはある意味、独自独占ソフトウェアな訳ですが、あのUI感からそれず、かつFirefox等に追いつくには、かなりの改良が必要になるはずです。

なお、印刷は意外と綺麗に出ました。

以下、iGoogleの印刷結果
safari4win_print
Posted at by




バグってました... orz


Windows版はもうすこしお待ち下さい。



Posted at by





http://twitter.com/itkz/statuses/220045412
GtkTwitter の configure がこける理由がわからないので、aclocal.m4 の AM_PATH_GTK_2_0 を解析する羽目になった。仕事ってなにそれおいしいの
http://twitter.com/itkz/statuses/219941692
GtkTwitter をどうやっても起動できない。GTK+ Runtime をインストールしても動かないし、DLL を全て実行ファイルと同じ階層に置いても動かない。関数のエントリポイントが見付からないんだとよ! 舐めてっとぶっ飛ばすぞ。どういうことだ。
http://twitter.com/itkz/statuses/222783012
(続き)もちろん、GtkTwitter の configure は pkg-config をスルーしてるので config.log にエラーなど残っていない。だからコンパイル時の問題だと思って CFLAGS をいじくったりしていたわけだ。
http://twitter.com/itkz/statuses/221271572
結局のところ、数時間かけても GtkTwitter のビルドには成功していない。まずバイナリで落としてきた libcurl をリンクすると「エントリポイントがねえ」と言ってくる。調べると「cURL が MinGW で動かねえと騒いでる糞どもはソースからビルドしろ」と一喝されている

これ以上ほったからしにしておくと、惨殺されるという噂なので焦って記事アップしました...(嘘です)

まず、リポジトリですが
http://gtktwitter.googlecode.com/
にあります。ダウンロード用アーカイブは古いのでsvnから使って下さい。
mingw32の場合は、autotoolを使うまでもないので確認していません。(ごめんなさい)
必要な物は
GnuWin32
Glade/GTK+ for Windows
cURL: win32-ssl-mingw from Mirrors
あたりでダウンロード出来るかと思います。
Makefileは適当に
all : gtktwitter.exe

gtktwitter.exe : gtktwitter.o gtktwitter.res
    gcc -o gtktwitter.exe \
        -Lc:/gtk/lib \
        gtktwitter.o \
        gtktwitter.res \
        `pkg-config --libs gtk+-2.0 libxml-2.0 gthread-2.0` \
        -lcurl

gtktwitter.o : gtktwitter.c
    gcc -c \
        `pkg-config --cflags gtk+-2.0 libxml-2.0` \
        gtktwitter.c

gtktwitter.res : gtktwitter.rc
    windres -o gtktwitter.res --output-format=coff gtktwitter.rc

clean:
    -rm *.o *.res *.exe
あたりで誤魔化して下さい。
あとは、curl.hやlibcurl.aをmingwへ入れ
set PKG_CONFIG_PATH=C:\GTK\lib\pkgconfig
してからmake(mingw32-make)すると出来上がると思います。

どうか、これでお許し下さい。苦笑
Posted at by




URLをクリッカブルにしてみました。
また、rpmも作成しましたので、よろしければどうぞ。




Posted at by




GtkTwitterのWin32版をリリースします。
動作にはGTK+ Runtimeが必要になります。
結局、配布方法が分からないので実行モジュールと、依存ライブラリの配布元リンク提供という形で公開します。
なお、別途リリース案内させて頂いたソースにはWin32用Makefile(Makefile.w32)が含まれております。

gtktwitter-win32-20070626

ダウンロード
GTK+ Runtieme(追加インストール)
GTK+ Runtime Environment for Windowsのリンクから、GTK+ Runtimeをインストールします。
libcurl for Win32(同梱)
cURL and libcurlよりWin32版curlライブラリ(libcurl.dll)を同梱させて頂いています。
最新版は上記リンクから取得し直して下さい。
zlib for Win32(同梱)
GnuWin32よりWin32版zlibライブラリ(zlib1.dll)を同梱させて頂いています。
最新版は上記リンクから取得し直して下さい。
Posted at by




実は、gtktwitterはユーザエージェント対応を結構前からやっておりまして、いつになったら「from GtkTwitter」と表示されるんだ...まってるよAlex君と、ただただ待っていたのですが、どうやらメールしないと取り込んで貰えない事が分かりまして、メールしました。金曜に「次のデプロイでリンクされるよ。ありがとう。」と浜村淳ばりの返事を貰いました。
そしてようやく...



これでようやく他のアプリに仲間入りした感じです。





Posted at by




バグフィックスです。
また、このバージョンからパッケージ名称(ディレクトリ名称)を「GtkTwitter」から「gtktwitter」に変更します。



Posted at by




WindowsにあるようなToggleDesktopをROX用に作ってみました。

http://rox.sourceforge.net/desktop/node/243
ソースはわずか17ステップ...。苦笑
Posted at by




しげふみさんのサイトでmicrosummaryという言葉を初めて目にしました。
livedoor Reader購読者数のライブタイトル
これはFirefoxだけの拡張で置いておくのは勿体無い。
しばらくしたら私のサイトでもmicrosummaryに対応したいと思いますが、とりあえず遊びも兼ねて...

Yahooヘッドラインニュースの最新1件をFirefoxのライブタイトルで表示出来る物を作ってみました。
インストールには、Microsummary Buddyが必要です。
Microsummary Buddyをインストールした後、以下のリンクをクリックして下さい。
※Firefox限定です。
YahooヘッドラインニュースのMicrosummary Generatorをインストール
これを入れた後Yahooヘッドラインニュースに移動し、ブックマーク追加を行います。
microsummary-yahoo-headline-news1
1段目のプルダウンメニューを広げると以下の様な項目が現れます。
microsummary-yahoo-headline-news2
ブックマークの登録先は、「個人用ツールバーフォルダ」に行います
すると、ブックマークツールバーに以下の様なブックマークが登場します。
microsummary-yahoo-headline-news3
※microsummaryが見つかるとアドレスバーに青いアイコンが現れます。
ライブタイトルの更新はデフォルトで30分おきという事なので、30分おきにYahooのヘッドラインニュース最新1件がブックマークツールバーに表示される事になります。

さて、このMicrosummary Buddyの拡張XMLですが中身は以下の様になっています。
<?xml version="1.0" encoding="UTF-8"?>
<generator xmlns="http://www.mozilla.org/microsummaries/0.1" name="Yahoo Headline News">
 <template>
  <transform xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <output method="text"/>
   <template match="/">
    <text>News:</text>
    <value-of select="//h1[@class='yjXL'][1]/a[1]/text()"/>
   </template>
  </transform>
 </template>
 <pages>
   <include>^http://headlines\.yahoo\.co\.jp/hl$</include>
 </pages>
</generator>
"generator/pages/include"でmicrosummaryを生成するURLのパターンを定義し、"generator/template/transform"でライブタイトルとなるmicrosummaryの変換式を書きます。
XSLTで「//li...なんちゃら」とやっている部分がYahooヘッドラインニュースのトップ記事1件を取得しているXPathになります。

面白いですね。twitterの最新発言や、自分のサイトはてなブックマーク数をライブタイトルにしても面白いかも知れませんね。
Posted at by




追記1
タイトルがうまく取れていないっぽい。
あとリンクも見付けれてない...orz
時間見つけて直します。
一応、現在見ているページならポスト出来るみたい。

追記2
直した。

Tomblooが良く出来てて素晴らしい。
Tomblooは現在のコンテンツに合わせてポストする形式を集約し、コンテキストメニューから集約したアイテムをポスト先へ送信する、すばらしい拡張です。
現状、標準のポスト先としてFlickr、Tumblr等がポスト先として選択出来る様になっています。
Minibufferフリークとしては、これをMinibufferから使えないのは物悲しい!
ってことで適当にハックしてMinibufferからpostする方法をご紹介します。
とは言っても今日ご紹介する方法は、ブックマークレットでもなければグリースモンキーでもありません。Firefoxの拡張を弄る事になりますので自己責任でお願い致します。
まずTomblooの拡張が格納されているフォルダ、
${FIREFOX_PROFILE}/extensions/tombloo@brasil.to/chrome/content/library/Mozaic.html
を開き <script type="text/javascript" src="../library/20_Tumblr.js"></script>
<script type="text/javascript" src="../library/30_Tombloo.Service.js"></script>
<style>
となっている部分に <script type="text/javascript" src="../library/20_Tumblr.js"></script>
<script type="text/javascript" src="../library/30_Tombloo.Service.js"></script>
<script type="text/javascript" src="../library/32_Minibuffer.js"></script>
<style>
と32_Minibuffer.jsを追加します。次に
${FIREFOX_PROFILE}/extensions/tombloo@brasil.to/chrome/content/library/32_Minibuffer.js
を作成し、以下のソースをペーストして保存します。
var GreasemonkeyServiceClass = Components.classes["@greasemonkey.mozdev.org/greasemonkey-service;1"];
log(GreasemonkeyServiceClass);
if (GreasemonkeyServiceClass) {
    function update(target, src){
        for(var key in src)
            target[key] = src[key];
        
        return target;
    }
    function addBefore(target, name, before) {
        var original = target[name];
        target[name] = function() {
            before.apply(target, arguments);
            return original.apply(target, arguments);
        }
    }
    var GreasemonkeyService = GreasemonkeyServiceClass.getService().wrappedJSObject;
    addBefore(GreasemonkeyService, 'evalInSandbox', function(code, codebase, sandbox){
        if (sandbox.Minibuffer && sandbox.LDRize) {
            sandbox.Minibuffer.addCommand({
                name: 'tombloo',
                command: function(stdin) {
                    var view = sandbox.LDRize.getSiteinfo()['view'];
                    var xpath_link = sandbox.LDRize.getSiteinfo()['link'];
                    var xpath_title = view || 'descendant::text()[normalize-space(self::text()) != ""]';
                    var nodes = sandbox.Minibuffer.execute('pinned-or-current-node');
                    forEach(nodes, function(node){
                        try {
                            var context = update(update({
                                document  : sandbox.unsafeWindow.document,
                                window    : sandbox.unsafeWindow,
                                title     : sandbox.unsafeWindow.document.title,
                                selection : '',
                                event     : {},
                                mouse     : {x:0,y:0},
                                menu      : null,
                            }, null), sandbox.unsafeWindow.location);

                            var text = '', url = '';
                            with(sandbox.unsafeWindow) {
                                var nodesSnapshot;
                                nodesSnapshot = document.evaluate(xpath_link, node, null,
                                    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
                                if (nodesSnapshot.snapshotLength > 0)
                                    context.href = nodesSnapshot.snapshotItem(0).href;

                                nodesSnapshot = document.evaluate(xpath_title, node, null,
                                    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
                                var allstringlength = 0;
                                var ypos = 0;
                                for(var n = 0; n < nodesSnapshot.snapshotLength; n++) {
                                    var item = nodesSnapshot.snapshotItem(n);
                                    var str = item.nodeValue.replace(/\s/g,'');
                                    allstringlength += str.length;
                                    if(allstringlength > 30){
                                        text += str.slice(0, 30) + '...';
                                        break;
                                    }else{
                                        text += item.nodeValue;
                                    }
                                }
                            }
                            if (text.length) context.title = text;

                            var exts = Tombloo.Service.check(context);
                            var service = Tombloo.Service;
                            forEach(exts, function(ext){
                                service.share(context, service.extracters[ext.name]);
                            });
                        } catch(e) {alert(e)}
                    });
                }
            });
        }
    });
}
あとはブラウザを再起動すれば、Minibufferにtomblooというコマンドが追加され、pinを付けて :tombloo と実行すれば、Tumblrにリンクがポスト出来る様になっています。
現状はTomblooが扱えるコンテンツの中でも、リンクのポストにしか対応していませんが、selection等を弄ればQuote、画像を選択させれば画像ポスト(Tumblr以外からのReblog)がMinibufferから可能になるかと思います。
今後やる気が続行すれば、作者殿にTomblooをプラガブルにする為の提案をさせて頂きたい...と思っています。
※やる気が続行すれば...の話です。

ただ、やっぱり特権のある部分からMinibufferを操作する行為は幾らevalInSandboxとは言え怖い...苦笑


リンク:Tombloo 0.1.0
Posted at by




以前、個人的なブームでPlaggerのPublish::XXXプラグインを作ってた頃の知識を再利用。

Minibufferには、ソーシャルブックマークへ登録するコマンド「bookmark」があります。
minibufferbookmarkcommand.user.js
ちょっと前これを色んなSBMに対応出来る様にプラガブルに修正したのですが、ノウハウを生かして以下2本のbookmarkプラグインを作成しました。

GooBookmarkプラグイン
minibufferbookmarkcommand.goobookmark.user.js
NiftyClipプラグイン
minibufferbookmarkcommand.niftyclip.user.js
それぞれ :bookmark -g
:bookmark -n
で実行出来る様になっています。
Publish::XXXの時と同様に利用している人は皆無かと思いますが、よかったらどうぞ。
Posted at by




こちらを参考に...
MinibufferベースのTwitter Post&Favorites Command Greasemonkey - 0x集積蔵
Jaiku版作ってみました。
以下ダウンロードURLがあります。

ただ、Jaikuの場合twitterと違ってBasic Authのダイアログを出す訳には行きません。Jaikuの場合にはAPIキーが必要となります。
グリモンをインストールした後
グリモンの設定ボタンを押してスクリプト編集に移り、「JaikuUser」と「JaikuApiKey」を修正します。
minibuffer-jaiku
個々のユーザに対するAPIキーはJaikuのAPIドキュメントに書いてあります。(黄色くおねしょしたみたいになっている部分です)
あとはtwitter版と同じ使い方になります。キーバインドとして「J T」および「J R」が追加されます。

なお、先ほどJaiku用のLDRize SITEINFOも書いておきましたので、pinからreplyする事も出来ます。

後は適当に...
minibufferjaikucommand.user.js

mattn the jaiku jaiker jaikest!.
Posted at by




もう眠くなってきたら :shutdown ソースは // ==UserScript==
// @name           Minibuffer Shutdown Command
// @namespace      Minibuffer.Shutdown.Command
// @include        http://*
// ==/UserScript==


(function(){
  var mes = [
    'System is going down for system shutdown now.......',
    'Starting local stop scripts.                       ',
    'Exiting Syslogd!                                   ',
    'Syncing all filesystems:                       [OK]',
    'Unmounting all filesystems:                    [OK]',
    'The system is going down NOW !!                    ',
    'Sending SIGTERM to all processes.                  ',
    'The system is halted. Press Reset or turn off power',
    'flushing ide devices:                          [OK]',
    'System halted.                                     ',
  ];
  function shutdown( stdin ) {
    GM_addStyle(<><![CDATA[
      #gm_minibuffer_flash_status * {
      font-family: 'terminal', 'monospace';
      font-weight: bold;
      }
      ]]></>);
    var n = 0;
    var timer = setInterval(function(){
      window.Minibuffer.status('shutdown'+n, mes[n], 50000);
      if (n++ >= mes.length) {
        clearInterval(timer);
        document.open();
        document.close();
        document.body.style.backgroundColor='black';
      }
    }, 1000);
    return stdin;
  }

  if (typeof window.Minibuffer != 'undefined') {
    window.Minibuffer.addCommand({
      name: "shutdown",
      command: shutdown,
    });
  }
})();
こんな感じ。
インストール:minibuffer.shutdown.user.js
てか実用的な物つくれ!俺
Posted at by




MinibufferBookmarkCommand - 3.14

適当ですが...
はてなブックマークと違いdel.icio.usの場合は、クローラでタイトルを補完してくれませんので、XHRで自前で取得しています。その際、responseTextが文字化けを起こしている可能性がありますので要注意。
--- minibufferbookmarkcomman.user.js.orig   Sat Nov 24 14:53:01 2007
+++ minibufferbookmarkcomman.user.js    Sat Nov 24 14:51:57 2007
@@ -140,6 +140,90 @@
        postToHatenaB(nodes, urls);
    }
    
+   // delicious bookmark
+   var delicious = function(nodes, urls, tags, comment){
+       if(!urls.length) return;
+       var total = urls.length;
+       var time = new Date().getTime();
+       var getParam = function(node, url, fn, afterPost){
+           if(!url) return;
+           var callback = function(res){
+               var html = res.responseText.createHTML();
+               var form = window.Minibuffer.$X('//form[@id="delForm"]', html)[0];
+               var inputs = window.Minibuffer.$X('//form[@id="delForm"]//input', html);
+               var res = {};
+               inputs.forEach(function(node){
+                   res[node.getAttribute('name')] = node.getAttribute('value');
+               });
+               if(keys(res).length == 1){
+                   // already bookmarked
+                   var text = url;
+                   if(window.LDRize){
+                       var siteinfo = window.LDRize.getSiteinfo();
+                       if(siteinfo && siteinfo.view){
+                           var view = window.Minibuffer.$X(siteinfo.view, node)[0];
+                           if(view) text = view.textContent;
+                       }
+                   }
+                   window.Minibuffer.message('<small>'+text+'</small><br/> has already bookmarked.', 2000);
+                   afterPost();
+               }else{
+                   res.action = 'http://del.icio.us/' + form.action.replace(/^https?:\/\/[^\/]+\//, '');
+                   // not yet bookmarked
+                   fn(res, afterPost);
+               }
+           }
+           GM_xmlhttpRequest({
+             method: 'GET',
+             url: "http://del.icio.us/post?v=4&url="+encodeURIComponent(url),
+             onload: callback,
+             onerror: function(res){log('onerror',res.responseText, '\n',res.responseHeaders)},
+           });
+       }
+       var post = function(opt, afterPost){
+           if (!opt.description) {
+               var x = new XMLHttpRequest();
+               x.open('GET', opt.url, false);
+               x.send(null);
+               if (x.readyState == 4 && x.status == 200) {
+                   try {
+                       var html = x.responseText.createHTML();
+                       opt.description = window.Minibuffer.$X('//title', html)[0].text;
+                   } catch(e) {}
+               }
+           }
+           var request = [
+               "url=", encodeURIComponent(opt.url),
+               "&oldurl=", encodeURIComponent(opt.url),
+               "&private=", opt.private,
+               "&description=", encodeURIComponent(opt.description),
+               "&notes=", encodeURIComponent(comment),
+               "&tags=", encodeURIComponent(opt.tags),
+               "&v=", opt.v,
+               "&key=", opt.key,
+               ].join('');
+           GM_xmlhttpRequest({
+             method: 'POST',
+             url: opt.action,
+             headers: {
+                 'Content-Type': 'application/x-www-form-urlencoded'
+               },
+             onload: afterPost,
+             onerror: function(res){log('onerror',res.responseText, '\n',res.responseHeaders)},
+             data: request
+           });
+       }
+       var postToDelicious = function(nodes, urls){
+           if(!urls.length){
+               window.Minibuffer.status('bookmark'+time,'Bookmark 100 %', 1000)
+               return;
+           }
+           window.Minibuffer.status('bookmark'+time,'Bookmark ' + Math.floor((total-urls.length) / total * 100) + '%');
+           getParam(nodes.shift(), urls.shift(), post, function(){postToDelicious(nodes, urls)});
+       }
+       postToDelicious(nodes, urls);
+   };
+
 // var delicious = function(urls, tags){
 //     log('delicious: urls:tags',urls, tags);
 // }
@@ -147,7 +231,7 @@
 //     log('livedoor: urls:tags',urls, tags);
 // }
    addBookmark('hatena',    'h', hatenab);
-// addBookmark('delicious', 'd', delicious);
+   addBookmark('delicious', 'd', delicious);
 // addBookmark('livedoor',  'l', livedoorclip);
    
    window.Minibuffer.addCommand({
Posted at by




ちょ!!www
まだそれ出来てないすから!!!
http://h.hatena.ne.jp/noreply/9236556151112331479
id:noreply
ldrizeでpしたハイクにスターを打つminibufferコマンド欲しい
http://h.hatena.ne.jp/otsune/9236538558961639940
id:otsune
それ id:mattn で
週末時点ではアカウント取れてたしotsuneさんからのidコールには気付いていたけど週末は予定ぎっしりで、結局出来ませんでした。
とりあえず動くようになったので公開...
:pinned-node | Hatena::addStar でピンを付けたノードに「はてなスター」を付けられます。
動作には、LDRizeとMinibufferと、mattnへの愛情が必要です。

動かなかったら...そんとき対応します。
別にはてなハイクで無くてもLDRizeが動いて、はてなスターが付けられる所だったら動くかもしれません。
※とりあえず、はてなハイクでは確認出来ています。

インストール:minibuffer.hatena.addStar.user.js
追記
pinを付けてキー「H S」でも動きます。

mattn the Haiku Haiker Haikest!
Posted at by




デジャブかもしれません
twitterを使ってみんなでアイデアを共有するサービス、ひらめいったーはてなスターサイト上で、twitterユーザのアイコンが表示されるようにするグリモン書いた。

続きを読む...

Posted at by




Firefoxをshellの様に扱えるグリモン「Minibuffer ? Userscripts.org」で使える、Fuck!コマンド「fuck」書いた。 使い方は、はてなブックマークコマンドとほぼ同じ。
目障りな記事を見つけたら :fuck とすれば良い。 もしくはpinを付けて :pinned-link | fuck とすればpinを付けたリンクが全部「Fuck!」になる。
なお :pinned-node | fuck とすれば、pinが指すノード自体が削除される。
例えばtwitterなんかで
minibuffer-fuck1
followerの発言をpinしてfuckしようもんなら
minibuffer-fuck2
こんなんになっちゃう!
※followerの皆様ごめんなさい。

ソースはこんな感じ // ==UserScript==
// @name           Minibuffer Fuck Command
// @namespace      Minibuffer.Fuck.Command
// @description    add fuck command to Minibuffer.
// @include        http://*
// @include        https://*
// ==/UserScript==

window.Minibuffer.addCommand({
  name: 'fuck',
  command: function(stdin){
    if (stdin.length == 0) {
      document.open();
      document.write('<h1>Fuck!</h1>');
      document.close();
    } else {
      stdin.forEach(function(obj){
        if (("" + obj).match(/^https?:.*/)) {
          var links = document.getElementsByTagName('a');
          for(var n = 0; n < links.length; n++) {
            if (links[n].href == obj) {
              links[n].href = 'javascript:void(0);';
              links[n].addEventListener('click', function() {alert(this.innerHTML)}, false);
              links[n].innerHTML = 'Fuck!';
            }
          }
        } else if (typeof obj == 'object') {
          try {
            obj.parentNode.removeChild(obj);
          } catch(e) { }
        }
      });
    }
    return stdin;
  }
});

インストール:minibuffer.fuck.user.js

てか使い道なくね?
Posted at by




オフィシャル側で対応頂いたので、このグリモンの必要性は無くなりました。はてなスター日記
twitter上に、はてなスターを置く為のグリモン「つい☆すた」のサイト、「Twitter: What are you doing?」上でtwitterユーザのアイコンが表示されるようにするグリモン書いた。

続きを読む...

Posted at by




Firefoxをshellの様に扱えるグリモン「Minibuffer ? Userscripts.org」で使える、はてなブックマークコマンド「hatena-bookmark」書いた。 Minibufferは、LDRizeと併用すると「pinned-link」や「pinned-node」というルートコマンド(フィルタコマンドではない)が追加される。
例えば、LDRizeでpinを付けたリンクをタブで開くには :pinned-link | open とすれば良い。ここで注意しなければならないのがグリモンの設定で
  • Minibuffer
  • LDRize
  • ...LDRizeプラグインもしくはMinubufferプラグイン...
という順番にしておかないと、「pinned-link」や「pinned-node」が正しく動かなくなるので注意。

この順番になるように、以下のスクリプト入れると、「hatena-bookmark」コマンドが追加される。
LDRizeを入れておくと、ブログ等の記事1つずつにpinが付けられるようになっていて、そのpinを付けたものに対してパイプ形式にコマンドを繋げられる。
例えば、pinをつけたリンクのGoogleキャッシュを開くには :pinned-link | google-cache | open とすればよい。
※pinをつけるには「p」を押す。

「hatena-bookmark」の使い方は、何もpinしていない状態で「Alt-x」(ALTキーを押しながらx)を押して :hatena-bookmark とすると、現在見ているページのはてなブックマーク登録画面が立ち上がる。またpinを付けた状態で :pinned-link | hatena-bookmark とすると、pinが指すリンクのブックマーク登録画面が立ち上がる。
なお :pinned-node | hatena-bookmark では、pinしているHTMLノードの先頭にあるリンクを使ってブックマーク登録画面を表示する。
ちなみにソースはこんな感じ。 // ==UserScript==
// @name           Minibuffer Hatena Command
// @namespace      Minibuffer.Hatena-Command
// @description    add hatena-bookmark command to Minibuffer.
// @include        http://*
// @include        https://*
// ==/UserScript==

var SCRIPT_VERSION  = 'Thu, 15 Nov 2007'

var Minibuffer_Hatena = new function() {
    var self = this;

    this._run_bookmark = function(u, t) {
        var link;
        if (t) link = 'http://b.hatena.ne.jp/add?mode=confirm&is_bm=1&title='+escape(t)+'&url='+escape(u);
        else   link = 'http://b.hatena.ne.jp/add?mode=confirm&is_bm=1&url='+escape(u);

        setTimeout(function(){
            window.open(link, '_blank', 'width=550,height=600,resizable=1,scrollbars=1');
        }, 10);
    }

    this.bookmark = function ( stdin ) {
        if (stdin.length == 0) {
            self._run_bookmark(location.href, document.title);
        } else {
            stdin.forEach(function(obj){
                if (typeof obj == 'string' && obj.match(/^https?:.*/)) self._run_bookmark(obj);
                else if (typeof obj == 'object') {
                    try {
                        var links = obj.getElementsByTagName('a');
                        for(var n = 0; n < links.length; n++) {
                            if (links[n].href.match(/^https?:.*/)) {
                                self._run_bookmark(links[n].href);
                                break;
                            }
                        }
                    } catch(e) { }
                }
            });
        }
    };
    this.condition = function() { return true; };
};

if (window.Minibuffer) {
    Minibuffer.addCommand({
        "hatena-bookmark": Minibuffer_Hatena.bookmark,
    });
}

インストール:minibuffer.hatena.user.js
ちなみに、ファイル名が「minibuffer.hatena.bookmark.user.js」になっていないのは、いずれ他のコマンドも作って行きたいから...。

あ、あと記事とは無関係ですが、今日CodeReposのLDRize-SITEINFO書いときました。

追記1
bookmarkコマンドで、stdinを返すように修正した。
これで :pinned-link | hatena-bookmark | clear-pin とか出来る様になった。

追記2
申し訳ない。objがtypeofでstringとして戻るパターンとobjectとして戻るパターンがあるようです。修正しました。
Posted at by




んー。
// ==UserScript==
// @name           Speedtest Ikasama
// @namespace      Speedtest.Ikasama
// @include        http://speedtest.10-fast-fingers.com/
// ==/UserScript==

var w=document.getElementById('eingabe');
var v=document.getElementById('vorgabe');
var s = 0;
var z = unsafeWindow.pruefstring;
var f1 = unsafeWindow.welchedown;
var f2 = unsafeWindow.welcheup;
unsafeWindow.wort = (z+z+z+z+z).split(' ');
(function go() {
    clearTimeout(s);
    var t = v.innerHTML.replace(/<[^>]+>/g, '');
    if (t){
        var i=0;
        while(true){
            t = v.innerHTML.replace(/<[^>]+>/g, '');
            var e = document.createEvent('KeyboardEvent');
            e.initKeyEvent('keypress', true, true, window, false, false, false, false, 0, t.charCodeAt(i));
            w.dispatchEvent(e);
            f1(e);f2(e);
            if (++i >= t.length) break;
        }
    }
    s = setTimeout(go, 0);
})();
こんなの使っても
typing-fast
8位って事は、1位の人もHackingやん!www
こんな速度、人間じゃありえん!
speedtest.ikasama.user.js
# 10-fast-fingers.com - Speedtest


ちなみにこのスクリプト、なんでこんな変な事しているかといいますと...
まずど頭で、「z+z+z+ ...」ってやってる部分は、あまりにタイプスピードが速すぎて、内部の配列がオーバーフローしてしまっているのでキャパを取り直しています。次にループ内で再度「t」を取り直しているのは、このSpeedtestがイカサマ防止の為にタイプ中にワードを増やす処理が入っており、それをハンドリングする為に入っています。
Posted at by




そうなのか...知らなかった。

The Future of JavaScript
var a = [1,2,3]; var a2 = a.pop; var b = a2() は?
  this のコンテキストが変わってるのでエラーになる
なんでだろ。 メソッド呼び出し時に正しくコンテキストスイッチしてないって事?

例えば var obj = new(function Clazz() {
  var self = this;
  self.value = 0;
  self.plus = function() { self.value++; };
})();
var func_plus = obj.plus;
func_plus();
alert(obj.value);
なら結果は「1」になるよね?それってもしかして var obj = new(function Clazz() {
  this.value = 0;
  this.plus = function() { this.value++; };
})();
var func_plus = obj.plus;
func_plus();
alert(obj.value);
こうなってるって事じゃないの?うむ...それっていいの?
コンテキスト保持しておけば function Clazz() {
  var self = this;
  self.value = 0;
  self.plus = function() { self.value++; };
}
var obj1 = new Clazz();
var obj2 = new Clazz();
var func_plus = obj1.plus;
func_plus();
obj2.plus = func_plus;
obj2.plus();
alert(obj1.value + "," + obj2.value);
こんな事しても、例外出ずに動くよね?obj1.valueがインクリメントされる結果になるけど...

これってperlなら #!/usr/bin/perl

use strict;
use warnings;

package Clazz;
sub new {
  my $class = shift;
  my $self = { value => 0 };
  return bless $self, $class;
}
sub plus {
  my $self = shift;
  ++$self->{value};
  1;
}

my $obj1 = Clazz->new;
my $obj2 = Clazz->new;

my $func_plus = *Clazz::plus;
warn "func_plus=".$func_plus."\n";
$func_plus->($obj1);
warn sprintf "obj1->{value}=%d, obj2->{value}=%d\n",
    $obj1->{value}, $obj2->{value};

$obj2->{plus} = $func_plus;
$obj2->plus();
warn sprintf "obj1->{value}=%d, obj2->{value}=%d\n",
    $obj1->{value}, $obj2->{value};
こんなサンプルかな。実行結果は func_plus=*Clazz::plus
obj1->{value}=1, obj2->{value}=0
obj1->{value}=1, obj2->{value}=1
サブルーチンにはselfを渡すのはインスタンス自身になるから問題無く動くね。javascriptのようにfunctionの参照自身がインスタンス参照を保持しちゃってる訳じゃなので問題は発生しないか...

pythonなら class Clazz:
  def __init__(self):
    self.value = 0

  def plus(self):
    self.value = self.value + 1

obj1 = Clazz()
obj2 = Clazz()

func_plus = obj1.plus
print "func_plus=%s" % func_plus
func_plus()
print "obj1.value=%d, obj2.value=%d" % (obj1.value, obj2.value)

obj2.plus = func_plus
obj2.plus()
print "obj1.value=%d, obj2.value=%d" % (obj1.value, obj2.value)
こんなサンプルかな?実行結果は func_plus=<bound method Clazz.plus of <__main__.Clazz instance at 0xb7ef828c>>
obj1.value=1, obj2.value=0
obj1.value=2, obj2.value=0
pythonの場合は関数の参照自身がインスタンスの参照も保持しているから、いくらobj2.plusを上書きしてもobj2.valueが更新される訳じゃない。

rubyなら class Clazz
    def initialize() @value = 0 end
    def plus() @value += 1 end
    attr_accessor :value
end

obj1 = Clazz::new
obj2 = Clazz::new
func_plus = Clazz.instance_method :plus
puts "func_plus=%s" % func_plus
func_plus.bind(obj1).call
printf("obj1.value=%d, obj2.value=%d\n", obj1.value, obj2.value)
func_plus.bind(obj2).call
printf("obj1.value=%d, obj2.value=%d\n", obj1.value, obj2.value)
こんな感じ?実行結果は func_plus=#<UnboundMethod: Clazz#plus>
obj1.value=1, obj2.value=0
obj1.value=1, obj2.value=1
rubyの場合はperlぽいけど、UnboundMethodの呼び出しはまずインスタンスをbindしてから呼ぶ事になるので問題は発生しない。ちなみにobj1.plusを取得してobj2.plusに代入できないかやってみたけど、私の力足らずなのか出来なかった。

luaなら function Clazz()
  return {
    value = 0,
    plus = function(self)
      self.value = self.value + 1
    end
  }
end
local obj1 = Clazz()
local obj2 = Clazz()
local func_plus = obj1.plus
print("func_plus", func_plus)
func_plus(obj1)
func_plus(obj2)
print("obj1.value=", obj1.value)
print("obj2.value=", obj2.value)
こんな感じ?実行結果は func_plus   function: 0x8f36270
obj1.value= 1
obj2.value= 1
luaも結局selfを渡す事になるので、問題は派生しない。
ちなみにC++なら #include <stdio.h>

class Clazz {
private:
  int _value;
public:
  Clazz() : _value(0) {}
  void plus() { _value++; }
  int value() { return _value; }
};

int
main(int argc, char* argv[])
{
    Clazz* obj1 = new Clazz();
    Clazz* obj2 = new Clazz();

    typedef void (Clazz::*def_func_plus)();
    def_func_plus func_plus = &Clazz::plus;
    printf("func_plus=%p\n", func_plus);
    (obj1->*func_plus)();
    printf("obj1->value=%d, obj2->value=%d\n",
            obj1->value(), obj2->value());

    // obj2->plus = func_plus; # compile error

    return 0;
}
こんな感じ?実行結果は func_plus=0x8048574
obj1->value=1, obj2->value=0
まぁ、無茶すればobj2->plusも置き換えられない事はないけど、結果は見えてるからいいでしょ...

つまり、著名な言語で問題が発生しているのはjavascriptだけって事か...
それって...

まずくない?
Posted at by




その1 3桁ごとに区切る - PleasureDelayerDiary はてなブックマーク数


Number.prototype.split3_1 = function() {
    var r = ""; 
    var s = this.toString().split("").reverse();
    for(var i = 0; i < s.length; i++) {
        if(i % 3 == 0 && i != 0 && s[i] != "-") {
            r = s[i] + "," + r
        } else {
            r = s[i] + r;
        }
    }  
    return r;
}

その2 iandeth. - javascriptで数値をカンマ区切り文字列に変換する関数メモ はてなブックマーク数


Number.prototype.split3_2 = function () {
    var to = String(this);
    var tmp = "";
    while (to != (tmp = to.replace(/^([+-]?\d+)(\d\d\d)/,"$1,$2"))){
        to = tmp;
    }
    return to;
}

その3 JavaScriptで数値を3桁ごとに区切る - 0x集積蔵 はてなブックマーク数


Number.prototype.split3_3 = function() {
    var m = (this &lt; 0) ? -1 : 1;
    var str = String(this*m).split('.');
    var arr = String(str[0]).split(''), len = Math.ceil(arr.length/3), res = [];
    for (var i =0;i&lt;len;++i) res.push(arr.splice(-3,3).join(''));
    return (m == -1 ? '-' : '') + res.reverse().join(',') + (str[1] ? '.' + str[1] : '');
};

その4 [JavaScript]数値を3桁ごとに区切る はてなブックマーク数


Number.prototype.split3_ore = function() {
  ('' + this).match(/(-?)([0-9]+)(\.[0-9]*)?/);
  var sp = [RegExp.$1, RegExp.$2, RegExp.$3];
  var x = Math.floor(sp[1].length / 3) * 3;
  var len = sp[1].length;
  return sp[0] + (sp[1].substr(0, len - x)) + (len - x == 0 ? '' : ',') +
         (sp[1].substr(len - x, x).match(/[0-9]{3}/g).join(',')) + sp[2];
}
私ならこう書く。
Number.prototype.split3 = function() {
  var r = "", s = this.toString().split("").reverse().join("").replace(/\d{3}/g,
    function(v){r+=v+',';return ''});
  return (r + s).split("").reverse().join("");
}
alert((1000000).split3()) // 1,000,000
文字列を逆にして、replaceに指定した関数で3桁毎にカンマを入れた結果と、空で置換したsubstituteの結果(あまった結果)を足す。その後文字列を逆にして戻す。
マイナスもたぶんOK。


追記1
しまった。チェック甘すぎ。
Number.prototype.split3 = function() {
  var r = "", s = this.toString().split("").reverse().join("").replace(/\d{3}/g,
    function(v){r+=v+',';return ''});
  if (!s.match(/\d/)) r = r.substr(0, r.length-1);
  return (r + s).split("").reverse().join("");
}
計測してみる!
mattnおそ!www
関数呼び出しコストか?
追記2
うむ。小数か...
Number.prototype.split3_mattn2 = function() {
  var r = '', s = this.toString();
  s.match(/(-?)([0-9]+)(\.[0-9]*)?/);
  var sp = [RegExp.$1, s = parseInt(RegExp.$2), RegExp.$3];
  while(s >= 1000) {
    r = ',' + (s%1000) + r;
    s = parseInt(s/1000);
  }
  return sp[0] + s + r + sp[2];
}
-12345678.2356を3桁ごとに区切るテスト

タイム測定



うむ。それでも遅い。
Posted at by




こりゃすげ。
URLを渡すとページの内容(htmlソース)をJSONPとかで取得できるAPIをYahoo! Pipesで作った(管理人日記) - むぅもぉ.jp
Yahoo! PipesにFetch Pageモジュールが追加されたので、さっそく作ってみた。
これで色んなことが出来るようになるし、グリースモンキーに頼っていたクロスドメインな処理がJSONだけで出来るようになる。
これまでにも同様の事が出来るサービスはあったけど、Yahoo! Pipesがサポートしたってのは強い。
試しにYahoo! JapanのHTMLをレンダリングするサンプルを作ってみた。
以下コード
<script type="text/javascript"><!--
function on_load_document(data) {
    document.getElementById('yahoo_html').innerHTML = data.value.items[0].description;
}
function load_document() {
    var url = 'http://www.yahoo.co.jp/';
    var s = document.createElement('script');
    s.charset = 'utf-8';
    s.src = 'http://pipes.yahoo.com/poolmmjp/page_loader?url=' + encodeURIComponent(url) + '&_render=json&_callback=on_load_document';
    document.body.appendChild(s);
}
--></script>

<input type="button" onclick="load_document()" value="Yahoo! Japanを表示">
<div id="yahoo_html"></div>
実行結果は↓

続きを読む...

Posted at by




はてなTIPS - アドレスから「id naoya」でダイアリーに
こんなの使えないかなぁ...
javascript:alert(%s);void 0
これを「js」てキーでレジストリ登録しておけば今まで javascript:alert(document.getElementById('banner'));void 0 ってやってたオブジェクトの存在確認が js document.getElementById('banner') って感じに少し楽になる。
単に評価式を返すので js document.getElementById('banner').innerHTML = 'ばなー'; ってな感じにも使えますね。

あと、もうすこし凝ってダイナミックローダ作ってamachangのjavascript-xpath.js使って xpath //div[@id='foo']//span なんて事も出来るのかも。もちろんIEなのでtoJSONなんかを実装した方がalertで確認し易いけど...。

Posted at by




[Autopagerize]pagerizeをダブルクリックで制御するように変更

[AutoPagerize]Pagerizeしてほしくない時も有るんだよな。誤爆防止に右上の■で切り替えれば良いけど。グッとガッツポーズしたら無効とかほしい
確かに右上のちっちゃいのにマウス合わせて「on/off」をクリックするのってマンドクサイ。
Toggle AutoPager
こんなブックマークレットをブックマークツールバーに置くか、keyconfigで「ctrl+g」とかにこのブックマークレット登録しておいて、「グッ」と発音しながら「ctrl+g」を押すと便利だったり便利じゃなかったり...。
Posted at by




まぁ、ただそれだけなんですが...
はてなスターに連射ボタンをつけるBookmarklet - 0x集積蔵
tyoro.exe はてなスターに連射ボタンをつけるBookmarklet 改造版
Operaは...しらん。
はてスタ連射
コード
(function(){
var SPEED=2000;
var d=document;
function r(b,m){
    b.onclick=function(){
        var t=setInterval(function(){
            var e;
            if(d.all){
              e=d.createEventObject();
              e.target=m;
              m.fireEvent("onclick",e);
            }else{
              e=d.createEvent("MouseEvents");
              e.initMouseEvent("click",true,true,window,1,10,50,10,50,0,0,0,0,1,m);
              m.dispatchEvent(e);
            }
        },SPEED);
        b.innerHTML='[STOP!]';
        b.onclick=function(){
            clearInterval(t);
            b.innerHTML='[&#36899;&#23556;!]';
            r(b,m);
        }
    }
}
var im=d.images;
for(var i=0,l=im.length;i<l;i++){
    var m=im[i];
    if(m.className=='hatena-star-add-button'){
        var b=m.parentNode.appendChild(d.createElement('b'));
        b.innerHTML='[&#36899;&#23556;!]';
        r(b,m);
    }
}
})();
Posted at by




いぬビームさんが作ったはてなスターをプロフィールアイコンに変えるブックマークレットで遊んでたんですが、複数の日記を含んだページや、はてなブックマークに付けられた複数人への「はてなスター」ではいちいち数字をクリックして展開しなくてはなりません。
で...書いた。

久々javascript書いた。

今日も徹夜だ。

同情するなら☆おくれ。

ブックマークレット:はてなスターの数字を展開

ソースコードは↓
javascript:var d=document,e=d.createEvent('MouseEvents');if(typeof d.getElementsByClassName=='undefined')d.getElementsByClassName=function(c){var m=[];var n=d.body.getElementsByTagName('*');for(var i=0;i<n.length;i++)if(n[i].className == c)m.push(n[i]);return m;};void(0);d.getElementsByClassName('hatena-star-inner-count').forEach(function(i){e.initEvent('click', true, true);i.dispatchEvent(e)});void(0);
※たぶんfirefoxでしか動かない。
※ちなみに私のサイトでは数字が出るほど☆がないので、いぬビームさんとこでやるのがいいかと...
追記
修正しました。document.getElementsByClassNameがundefinedでした。
Posted at by




Plaggerで、はてなハイクにポストするPublish::HatenaHaiku書いた。
コードはこんな感じ
package Plagger::Plugin::Publish::HatenaHaiku;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use Time::HiRes qw(sleep);
use URI;
use Plagger::Mechanize;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry' => \&add_entry,
        'publish.init'  => \&initialize,
    );
}

sub initialize {
    my $self = shift;
    unless ($self->{mech}) {
        my $mech = Plagger::Mechanize->new;
        $mech->agent_alias('Windows IE 6');
        $mech->quiet(1);
        $self->{mech} = $mech;
    }
    $self->login_hatena_haiku;
}

sub add_entry {
    my ($self, $context, $args) = @_;

    unless ($self->conf->{default_keyword}) {
        Plagger->context->log(error => 'set default_keyword.');
    }
    my $summary = encode('utf-8', $args->{entry}->title)
        . "\n" . encode('utf-8', $args->{entry}->link);
    my $keyword = $self->conf->{default_keyword};

    my $keyword_behaviour = $self->conf->{keyword_behaviour};
    if ('default' ne $keyword_behaviour) {
        if ('tag' eq $keyword_behaviour) {
            my @tags = @{$args->{entry}->tags};
            $keyword = $tags[0] if ($tags[0]);
        }
        if ('title' eq $keyword_behaviour) {
            if ($summary =~ /^\[([^\]]+)\]/) {
                $keyword = $1;
            }
        }
    }

    my $res = eval { $self->{mech}->get('http://h.hatena.ne.jp/') };
    if ($res && $res->is_success) {
        eval {
            $self->{mech}->submit_form(
                form_number => 1,
                fields => {
                    word       => encode('utf-8', $keyword),
                    body       => $summary,
                },
            )
        };
        if ($@) {
            $context->log(info => "can't submit: " . $@);
        } else {
            $context->log(info => "Post entry success.");
        }
    } else {
       $context->log(error => "fail to post HTTP Status: " . $res->code);
    }
 
    my $sleeping_time = $self->conf->{interval} || 3;
    $context->log(info => "sleep $sleeping_time.");
    sleep( $sleeping_time );
}

sub login_hatena_haiku {
    my $self = shift;
    unless ($self->conf->{username} && $self->conf->{password}) {
        Plagger->context->log(error => 'set your username and password before login.');
    }
    my $res = $self->{mech}->get('https://www.hatena.ne.jp/login?location=http://h.hatena.ne.jp/');
    $self->{mech}->submit_form(
        form_number => 1,
        fields => {
            name  => $self->conf->{username},
            password => $self->conf->{password},
        },
    );
    if ($self->{mech}->content !~ 'http-equiv="Refresh"') {
        Plagger->context->log(error => "failed to login to hatena haiku.");
    }
}

1;

__END__

=head1 NAME

Plagger::Plugin::Publish::HatenaHaiku - Post to hatena haiku automatically

=head1 SYNOPSIS

  - module: Publish::HatenaHaiku
    config:
      username: your-username
      password: your-password
      default_keyword: id:mattn
      #keyword_behaviour: title
      #keyword_behaviour: tag
      #interval: 2

=head1 DESCRIPTION

This plugin automatically posts feed updates to hatena haiku
L<http://h.hatena.ne.jp/>. It supports automatic tagging(keyword) as well.
It might be handy for synchronizing your blog feeds into hatena haiku.

=head1 CONFIG

=over 4

=item username, password

username and password for Hatena Haiku. Required.

=item default_keyword

default keyword string. Required.

=item keyword_behaviour

Optional. if set 'title', it should accept 'plagger' in '[plagger]title'.
if set 'tag', it should accept a first of tags in feed.

=item interval

Optional.

=item timeout

Optional.

=back

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Plagger::Mechanize>

=cut

default_keywordは必須項目として、keyword_behaviourでキーワードの動作が変えられます。
keyword_behaviourに何も設定しなければ(もしくは'default')、default_keywordが使われ、'title'を設定すれば"[書評]ほにゃらら"の"書評"がキーワードに、'tag'を設定すればフィードタグの先頭を使う様になっています。
ただ、やっぱりフィードの転載なのではてなハイクのトップページに掲載する/掲載しないのオプションが欲しいなぁ...=>はてな事務局さん

なお、コードはCodeReposに置いてあります。
lang/perl/plagger/lib/Plagger/Plugin/Publish/HatenaHaiku.pm

ところで、dankogai氏それっぽい事(書評とか)やってるように見えるんだけど、もしかして手動だろうか?
Posted at by




iTunesのライブラリ情報XMLファイルをアップロードする事で自分に合ったアーティスト情報を教えてくれるサービス、「veena!」の検索ボックスを使って、指定のアーティストに関連する
  • YouTube動画
  • Yahooオークション情報
をWeb::Scraperでスクレイピングしてみようと思います。
ソースはそれ程難しくもなく
#!/usr/bin/perl

use strict;
use warnings;

use URI;
use URI::Escape qw(uri_escape_utf8 uri_unescape);
use Web::Scraper;
use YAML::Syck;

my $artist = shift || 'Ozzy Ozbourne';
my $uri = URI->new('http://www.veena.jp/srch_artist.php?artist_name='
    . uri_escape_utf8($artist));

my $youtube_list = scraper {
    process '//table[@class="info_tbl"]/tr/td',
        'video[]' => scraper {
            process '//a[1]', url => sub {
                my $url = shift->attr('href');
                $url =~ s/^.*\?url=(.*)$/$1/;
                uri_unescape($url);
            };
            process '//a[2]', title => 'TEXT';
            process '//img', image => '@src';
        };
    result 'video';
};

my $auction_list = scraper {
    process '//table[@class="info_tbl"]/tr/td',
        'auction[]' => scraper {
            process '//a[1]', url => '@href';
            process '//a[2]', title => 'TEXT';
            process '//img', image => '@src';
        };
    result 'auction';
};

my $artist_list = scraper {
    process '//a[contains(@href, "artist.php")]',
        'artists[]' => scraper {
            process 'a', id => sub {
                my $url = shift->attr('href');
                $url =~ s/^.*id=(.*)$/$1/;
                $url;
            };
            process 'a', 'youtube' => sub {
                my $url = shift->attr('href');
                $url =~ s/artist\.php/http:\/\/veena.jp\/list_youtube\.php/;
                my $list = $youtube_list->scrape(URI->new($url));
                \@$list;
            };
            process 'a', 'auction' => sub {
                my $url = shift->attr('href');
                $url =~ s/artist\.php/list_auction\.php/;
                my $list = $auction_list->scrape(URI->new_abs($url, $uri));
                \@$list;
            };
            process 'a', name => 'TEXT';
        }
};
my $result = $artist_list->scrape($uri);
warn Dump $result;
って感じ。YouTube動画情報一覧とYahooオークション情報はアーティスト情報にぶら下がる形で出力したかったので検索結果一覧用のscraperとその結果を取得するscraperを親子関係にしてあります。
結構一覧としてはキレイに出力されているかと思います。
---
artists: 
  - 
    auction: 
      - 
        image: !!perl/scalar:URI::http http://ac.c.yimg.jp/7/1026/1783/000/img305.auctions.yahoo.co.jp/users/6/4/6/7/rosiertrueblue-thumb-119657918759294.jpg
        title: Ozzy Osbourne
        url: !!perl/scalar:URI::http http://page.auctions.yahoo.co.jp/jp/auction/108393777
      - 
        image: !!perl/scalar:URI::http http://ac.c.yimg.jp/7/1022/1783/000/img245.auctions.yahoo.co.jp/users/6/4/6/7/rosiertrueblue-thumb-119657997018368.jpg
        title: Ozzy Osbourne
        url: !!perl/scalar:URI::http http://page11.auctions.yahoo.co.jp/jp/auction/n61267094
      - 
        image: !!perl/scalar:URI::http http://a1017.lm.a.yimg.com/7/1017/1783/000/img257.auctions.yahoo.co.jp/users/8/2/8/3/kokita74-thumb-119486785113507.jpg
        title: Ozzy Osbourne
        url: !!perl/scalar:URI::http http://page8.auctions.yahoo.co.jp/jp/auction/h52088580
   ...
    id: 216546
    name: Randy Rhoads (Ozzy Ozbourne)
    youtube:
      -
        image: !!perl/scalar:URI::http http://img.youtube.com/vi/MEUbYkLe_wo/default.jpg
        title: Ozzy Ozbourne's top 10 songs
        url: http://www.youtube.com/watch?v=MEUbYkLe_wo
      - 
        image: !!perl/scalar:URI::http http://img.youtube.com/vi/GLtjWi4qkIY/default.jpg
        title: Goodbye to Romance - Ozzy/Randy Rhoads (solo)
        url: http://www.youtube.com/watch?v=GLtjWi4qkIY
      - 
        image: !!perl/scalar:URI::http http://img.youtube.com/vi/AQqbNHhBWcI/default.jpg
        title: iron man
        url: http://www.youtube.com/watch?v=AQqbNHhBWcI
   ...
Ozzy OzbourneのキーワードでRandy Rhoadsも引っかかってウハウハです。
で、このYAMLをどうするか...
use LWP::UserAgent;
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->agent('Mozilla');
for my $artist (@{$result->{artists}}) {
  for my $video (@{$artist->{youtube}}) {
    my $url = $video->{url};
    my $req = HTTP::Request->new(GET => $url);
    $req->header('Accept-Encoding', 'identity');
    my $res = $ua->request($req);
    if ($res->is_error) {
      if ((my $verify_url = $res->request->uri) =~ /\/verify_age\?/) {
        my $verify_req = HTTP::Request->new(POST => $verify_url, {action_confirm => 'Confirm'});
        $res = $ua->request($verify_req);
        $res = $ua->request($req) if $res->is_success;
      }
    }
    if ($res->content =~ /video_id=([^&]+)&l=\d+&t=([^&]+)/gms) {
      my $flv = "http://youtube.com/get_video?video_id=$1&t=$2";
      print "Downloading $flv\n";
      my $download_req = HTTP::Request->new(GET => $flv);
      $download_req->referer($url);
      my $res = $ua->request($download_req);
      if ($res->is_success) {
        open FH, ">$2.flv";
        binmode FH;
        print FH $res->content;
        close FH;
        print "Downloaded $2.flv\n";
      } else {
        print "Failed to download $2.flv\n";
      }
    } else {
      print "Not found flv file\n";
    }
  }
}
やっぱこうなりますわね...

mattn the crazy train scraper!
Posted at by




なんにつかうねん!www
/lang/perl/Acme-Jyogakusei/trunk/ - CodeRepos::Share - Trac
こうか!? #!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Acme::Jyogakusei;

my $jyogakusei = '女子高生';
my $re = Acme::Jyogakusei::regexp;
print "Jyogakusei\n" if $jyogakusei =~ /$re/;
あと #!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Acme::Jyogakusei;

my $jyogakusei = 'なんちゃって女子高生';
my $re = Acme::Jyogakusei::regexp;
$jyogakusei =~ s/(なんちゃって$re)/おばちゃん/g;
print $jyogakusei;
こんなんもありか...
Posted at by




Web::Scraper使うときに、scraperコマンドを使って頑張る人もいれば、FirebugのDOMツリーで「XPathをコピー」とやっている人もいるでしょう。
前者の場合、端末でスクロールアウトするHTMLを見ながらXPathをこさえて間違ったらズラズラズラ…と画面が流れて行ってしまいます。後者の場合は、CLASSやID属性を使わないXPathが出来上がってしまいます。
映画に出てくるHackerの如く一発でXPathを決められればそれは素晴らしい事だと思いますが、いかんせん幾度か失敗しますよね。
で、何回もXPathを確かめられるツールが欲しいなと思い、perl-GTK2で作ってみました。

画面はこんな感じ
WebScraperHelper1
引数に「http://b.hatena.ne.jp/」を付けて起動したらこんな感じ
WebScraperHelper2
URLを変更して「Get」をクリックすれば再読み込みします。
そして、はてなブックマークトップページの「注目の動画」部分にある画像一覧を取得する為に //a[text()="注目の動画"]/../../..//img
というXPathを書いて「Update」をクリックすれば
WebScraperHelper3
こんな感じのHTMLが出来上がります。
あとはこれをWeb::Scraperのprocess部分に貼っつけるだけ。

ちなみにXPathでの属性値参照も出来ますので、はてなブックマークトップページで //meta[@http-equiv="Content-Type"]/@content
というXPathを書けば content="text/html; charset=UTF-8"
という結果が返ります。
起動にはCPANからGtk2モジュールをインストールする必要があります。HTMLのパース方法やノードの取得方法等は大体Web::Scraperと合わせていますので、Web::Scraperが動く環境にGtk2をインストールすれば動くかと思います。
また画面はLinux上で起動した物ですが、UN*Xらしい事は一切やってませんのでWindowsでも動作するかと思います。
ダウンロード:

もう少し機能を足そうかと思いましたが、今日はもうギブアップ。寝ます。
Posted at by




CodeReposに面白そうな物が入ってたので物色中。
どういうロジックでFriendを探しているのかは、まだ見てませんが...

こんなコードを実行すると... #!/usr/bin/perl

use strict;
use Net::Twitter::Friend::Finder::FromGoogle;

my $twitter = Net::Twitter::Friend::Finder::FromGoogle->new( {
        username => 'xxxxxxxxx',
        password => 'xxxxxxxxx' ,
        on_echo => 1,
        limit => 20 ,
        lang => 'ja' } );

$twitter->search;
$twitter->show;
こんな結果が返ります。 .----------------------------------------------.
| Net::Twitter::Friend::Finder::FromGoogle     |
'----------------------------------------------'
.---------+------------------------------------.
| Keyword | twitter                            |
'---------+------------------------------------'
.-----+----------------------+-----------------.
| #   | Twitter id           | Found count     |
'-----+----------------------+-----------------'
.-----+----------------------+-----------------.
| 1   | ikasam_a             | 1               |
| 2   | tsuda                | 1               |
| 3   | blog                 | 1               |
| 4   | help                 | 1               |
| 5   | tdtds                | 1               |
'-----+----------------------+-----------------'
なんだかwktk。

ところでFriend一覧で出てきたアカウント「help」って...

twitter-help
ちょwww
Posted at by




#!/usr/bin/perl
use strict;
use Perl6::Say;

undef &Perl6::Say::say;
sub my_say {
    my $this = shift;
    print @_;
    $this;
}
*Perl6::Say::say = \&my_say;

STDERR->say('フォォーー!!')->say('セイ')->say('セイ')->say('セイ');

こういうのは、おとなしく package IO::HG4;
use base qw(IO::Handle);

sub say {
    my $this = shift;
    print @_;
    $this;
}

IO::HG4->new->say('フォォーー!!')->say('セイ')->say('セイ')->say('セイ');
するのがいいと思った。
Posted at by




いいなぁiPod touch欲しいなぁ。

Plaggerでニコニコ動画を一括ダウンロード&変換 Podcast を生成して iPod touch で見る - 2007年11月最新版
うちでは臨時予算案は可決されそうにありません。宝くじが当たるま携帯で我慢したいと思います。

今日は、ゆーすけべさんの所に置いてあるYAMLをちょこっと弄って、携帯向け変換をやろうかと思います。ちょっとだけオリジナリティを出そうとニコニコではなく、YouTubeから...
まず、こんなYAMLを用意します。

youtube_hatena_tagged.yaml
global:
  assets_path: /home/user/plagger/assets/
  timezone: Asia/Tokyo
  log:
    evel: info

plugins:

  - module: Subscription::Config
    config:
      feed:
          - url: http://b.hatena.ne.jp/t/youtube?mode=rss&amp;sort=hot&amp;threshold=3
          
  - module: Filter::FindEnclosures
  - module: Filter::FetchEnclosure
    config:
      dir: /home/user/plagger/out
  - module: Filter::FLVInfo
  
  - module: Filter::FFmpeg
    config:
      command: /usr/bin/ffmpeg
      ext: 3gp
      dir: /home/user/plagger/out
      encoding: utf-8
      options:
        video_codec:         mpeg4
        audio_codec:         libamr_nb
        audio_sampling_rate: 8000
        audio_bit_rate:      12.2k
        frame_size:          176x144
      extra_options: -ac 1 -f 3gp

  - module: Publish::Feed
    config:
      format: RSS
      dir: /home/user/plagger/out
      filename: youtube_hatena_tagged.xml
はてなで「youtube」タグが付いているフィードを取ってきて、Enclosureをフェッチ&解析。最後にFFmpegで変換というフェーズになります。Feed作成は意味なさそうですが、この後役に立ちます。
私の携帯SB810T向けには、映像コーデックにmpeg4、音声コーデックにamr-nb(libamr_nb)を指定します。メーカーの仕様によると、AACでも行けそうな事が書いてありますが、どうやら動画でない音声ファイルの場合のみAACが再生出来るようです。
また、サンプルレート8000はamr-nbの標準値で、オーディオチャネル(ac)は1にしないといけない様です。 もちろん
  • lib/Plagger/Plugin/Filter/FFmpeg.pm
  • lib/Plagger/Plugin/Filter/FLVInfo.pm
はCodeReposから取得して下さい。
殆んどyusukebeさん(yousukebeさんではありません。ここ大事)のと同じです。大概はffmpegのオプションでなんとか出来るかと思います。ただ現状Plaggerのtrunkに入ってるFind-Enclosuresのyoutube.plでは、jp.youtube.comドメインのリソースが対象外になっていますので、以下のパッチを当てます。
Index: assets/plugins/Filter-FindEnclosures/youtube.pl
===================================================================
--- assets/plugins/Filter-FindEnclosures/youtube.pl (revision 1988)
+++ assets/plugins/Filter-FindEnclosures/youtube.pl (working copy)
@@ -3,7 +3,7 @@
 
 sub handle {
     my ($self, $url) = @_;
-    $url =~ qr!http://(?:www.)?youtube.com/(?:watch(?:\.php)?)?\?v=.+!;
+    $url =~ qr!http://(?:www.|jp.)?youtube.com/(?:watch(?:\.php)?)?\?v=.+!;
 }
 
 sub find {
※パッチが当たった物はCodeReposに入れてあります。チンして食べてください。ただGData APIでないのでカッコよくはありませんが。
あとは # plagger -c youtube_hatena_tagged.yaml を実行すると、上のYAMLで指定している「out」フォルダにゴッチョリと3gpファイルが出来上がっている筈です。
携帯の場合はPodcast出来ませんし、動画のサイズが大きくなるとメールで送ったりHTTPでダウンロードする事も出来ません。結局はメモリ転送になりますが、Feed.pmにちょっとだけ手を加えて...
Index: lib/Plagger/Plugin/Publish/Feed.pm
===================================================================
--- lib/Plagger/Plugin/Publish/Feed.pm  (revision 1988)
+++ lib/Plagger/Plugin/Publish/Feed.pm  (working copy)
@@ -122,6 +122,15 @@
     open my $output, ">:utf8", $filepath or $context->error("$filepath: $!");
     print $output $xml;
     close $output;
+
+    if ($self->conf->{command_after}) {
+        my $command = $self->conf->{command_after};
+        my $dir = $self->conf->{dir};
+        $filepath =~ s!\\!/!g;
+        $command =~ s!\$\(filename\)!$filepath!g;
+        $command =~ s!\$\(dir\)!$dir!g;
+        system($command);
+    }
 }
 
 sub make_author {
こんなパッチを当てると、フィード出力時に「command_after」が実行されますので   - module: Publish::Feed
    config:
      format: RSS
      dir: /home/user/plagger/out
      filename: youtube_hatena_tagged.xml
      command_after: find $(dir) -name "*.3gp" -exec cp \{\} /media/usbdisk/private/myfolder/My Items/Video
こんなYAMLにしておけば、出来上がった3GPがUSB越しに携帯のmicroSDカードにズコーーーーンと転送されるって仕組です。
microSD-card
ズゴーーーーン
まぁ、Podcastと違って出来上がるまで、良い子ちゃんで待ってなきゃいけないですが...

追記
plagger流儀で言えば、Notify::Commandを使うべきですね。
Posted at by




とりあえず書き上げました。
またまた適当クオリティですが...

lang/perl/Net-Kotonoha - CodeRepos

使い方は...
use warnings;
use encoding 'utf-8';
use Net::Kotonoha;
use Data::Dumper;
use Encode;

#binmode(STDERR, ':encoding(shiftjis)');

my $kotonoha = Net::Kotonoha->new(
        mail     => 'xxxx@example.com',
        password => 'xxxxxxx',
    );

# 新着コト一覧
warn Dumper $kotonoha->newer_list;

# 最近のコト一覧
#warn Dumper $kotonoha->recent_list;

# コト番号120235を取得
my $koto = $kotonoha->get_koto(120235);

# タイトル表示
warn $koto->title;

# ○ユーザ一覧
warn Dumper $koto->yesman;

# ×ユーザ一覧
warn Dumper $koto->noman;

# 回答(0:未回答, 1: ○, 2:×, コメントは省略可能)
$koto->answer(1, 'けもたい');

# 自分の回答
warn Dumper $koto->answer;

※一覧系の戻りはハッシュの配列
こんな感じ...
まぁ、UIで遊ぶサービスですから、あまり使い道はないかもしれませんが...

追記
cpanに入れてありますので「cpan Net::Kotonoha」で入ります。
Posted at by




タレントスケジュールなんてサイトを見つけたので、さっそくスクレイピング。
ドキュメントに同じid属性が複数あるという、なんともダイナミックなHTMLにもめげず作り上げたのが以下 #!/usr/bin/perl

use encoding 'utf-8';
use strict;
use warnings;
use Encode qw(from_to);
use URI;
use URI::Escape qw(uri_escape_utf8);
use Web::Scraper;
use YAML;

if ($^O eq 'MSWin32') {
    binmode(STDERR, ':encoding(shift_jis)');
    Encode::from_to($ARGV[0], 'cp932', 'utf-8');
}
my $talent = shift || '小島よしお';

my $talent_schedule = scraper {
    process '//div[@class="find_bl"]/following-sibling::*[1]//td', day => 'TEXT';
    process '//div[@class="find_bl"]/following-sibling::*[1]//td/div',
        'schedule[]' => scraper {
            process 'div', media => sub { my $m = $_->attr('class'); $m =~ s/^icon_//g; $m };
            process '/div/a', url => '@href';
            process '/div/a', title => 'TEXT';
            process '/div/node()[1]', timespan => sub {
                my $s = $_->string_value;
                $s =~ s/ //;
                $s =~ s/(^|[^\d])(\d):(\d\d)/0$2:$3/g;
                my @span = split(/[^\d:]/, $s);
                \@span;
            };
        };
    result qw/day schedule/;
};
my $uri = URI->new('http://talent-schedule.jp/'.uri_escape_utf8($talent));
my $oppappi_schedule = $talent_schedule->scrape($uri);
warn Dump $oppappi_schedule;
ちょっと日付まわりで苦労してますが...

小島よしおって、結構番組出てますねぇ。

でもそんなの関係ry)
Posted at by




今日はもう寝ます。
ttyは明日ミマス。

typester++

sub handle {
    my ($self, $url) = @_;
    $url =~ qr!http://ttyshare.com/rec/\w+!;
}

sub find {
    my($self, $args) = @_;
    
    my $uri = $args->{url};
    my $response = LWP::UserAgent->new->post(
        $uri,
        ['download' => 1]);
    if($response->content =~ m/<div id="p-(.+?)" class="player">/) {
        my $enclosure = Plagger::Enclosure->new;
        $uri = sprintf('http://ttyshare.com/static/tty/%s/%s/%s/%s',
            substr($1, 0, 1), substr($1, 0, 2), substr($1, 0, 4), $1);
        $enclosure->url($uri);
        $enclosure->type('application/x-ttyrec');
        $enclosure->filename("$1.tty");
        return $enclosure;
    }

    return;
}
Posted at by




終わり無き戦い...
/lang/perl/plagger/lib/Plagger/Plugin/Publish/Magnolia.pm - CodeRepos::Share - Trac
Ma.gnolia.com...意外とUIが好き。

これで、私が同期しているソーシャルブックマークは
  • Publish::Delicious
  • Publish::LivedoorClip
  • Publish::Buzzurl
  • Publish::LivedoorCilp
  • Publish::Buzzurl
  • Publish::GooBookmark
  • Publish::NiftyClip
  • Publish::Pookmark
  • Publish::YahooBookmark
  • Publish::BlueDot という名の Pubilsh::Delicious
  • Publish::Magnolia
となった。
収拾がつかなくなってきた。
さて、明日は何作ろう

追記
otsuneさんのの方がよい。
CodeRepsに上げてあるMagnolia.pmは、plaggerのsvn/trunkが変わったタイミングで消します。
Posted at by




これまた適当に...
/lang/perl/plagger/lib/Plagger/Plugin/Publish/NiftyClip.pm - CodeRepos::Share - Trac
/lang/perl/plagger/lib/Plagger/Plugin/Publish/Pookmark.pm - CodeRepos::Share - Trac
NiftyClipは結構とUIが使いやすい気がした。
あと、POOKMARK Airlinesの方はjavascriptがonじゃないとログイン出来ないって事にハマりかけた。あとtwitterにポストする機能があるので、default_no_twitterという設定で無効に出来るようにした。

これで、私が同期しているソーシャルブックマークは
  • Publish::Delicious
  • Publish::LivedoorClip
  • Publish::Buzzurl
  • Publish::GooBookmark
  • Publish::NiftyClip
  • Publish::Pookmark
となった。
収拾がつかなくなってきた。
Posted at by




どなたかのはてなブックマークで、「コメント欄」なんてのを見つけたけど、タイトル見るとどうにも開き辛い...
皆さんそんな事を思った事はありませんか?
そんな時にはコレ!

WWW::CommentGetter
assetsにYAML置いて、URL指定すればコメントがゴッソリ!

/home/user/WWW-CommentGetter/assets/spalog-dotei.yaml
---
handle: http://spalog.net/dotei/.*
comments:
  body: div.dotei03-ct
こんなYAML置いて
/home/user/WWW-CommentGetter/spalog-dotei-comment.pl
use strict;
use warnings;
use Encode;
use WWW::CommentGetter;

my $assets_dir = '/home/user/WWW-CommentGetter/assets';
my $getter = WWW::CommentGetter->new($assets_dir);

use YAML;
binmode STDERR, ':encoding(shiftjis)' if $^O eq "MSWin32";
warn Dump $getter->get('http://spalog.net/dotei/ent_1922.php');
こんなコード書けばコメント欄がゴッソリ!
実行結果は各人で...

tokuhiromさん++
Posted at by




私は、Plaggerを使用して、はてなブックマークを別のソーシャルブックマークに同期していますが、その際ブックマークタグがどのように扱われるかについて以下説明します。
はてなブックマークのフィードは
RSS形式
http://b.hatena.ne.jp/[hatena account]/rss もしくは
AtomFeed形式
http://b.hatena.ne.jp/[hatena account]/atomfeed というURLで提供され、データには以下の様な物が含まれています。 ※以下の例はRSSの場合
<item rdf:about="http://coderepos.org/share/changeset/552">
    <title>Changeset 552 - CodeRepos::Share - Trac</title>
    <link>http://coderepos.org/share/changeset/552</link>
    <description>笑わせて頂きました</description>
    <content:encoded>
      &lt;blockquote cite="http://coderepos.org/share/changeset/552" title="Changeset 552 - CodeRepos::Share - Trac"&gt;
        
        &lt;cite&gt;&lt;a href="http://coderepos.org/share/changeset/552"&gt;Changeset 552 - CodeRepos::Share - Trac&lt;/a&gt; &lt;a href="http://b.hatena.ne.jp/entry/http://coderepos.org/share/changeset/552"&gt;&lt;img src="http://b.hatena.ne.jp/images/entry.gif" title="このエントリーを含むブックマーク" alt="このエントリーを含むブックマーク" border="0"&gt;&lt;/a&gt;&lt;/cite&gt;
      &lt;/blockquote&gt;
      &lt;p&gt;笑わせて頂きました&lt;/p&gt;
    </content:encoded>
    <dc:date>2007-10-19T21:19:44+09:00</dc:date>
    <dc:creator>mattn</dc:creator>
    <dc:subject>coderepos</dc:subject>
    <dc:subject>erogeek</dc:subject>
    <taxo:topics>
      <rdf:Bag>
      <rdf:li resource="http://b.hatena.ne.jp/t/coderepos" />
      <rdf:li resource="http://b.hatena.ne.jp/t/erogeek" />
      </rdf:Bag>
    </taxo:topics>
</item>
ブックマークした元リンクのtitle/linkに加え、ブックマークコメントが格納されたdescription、およびblockquote/citeタグを使用して引用元形式に表現されたcontent:encoded、さらにはブックマークタグを表現するdc:subjectが記述されています。
Plaggerの場合、descriptionよりもcontant:encodedを優先しており、コメントとしては冗長な引用部分が転送されてしまいます。これについては先日書いた「Plaggerで、はてなブックマークをdel.icio.usにミラーする時に、descriptionフィールドを衛生的に修正するフィルタプラグイン書いた」にある様にdescriptionをcontent:encodedに上書きしてやる事で対応出来ます。
先日この記事を書いた際、otsuneさんから「この目的であれば、 b.hatena.ne.jp/[hatena user]/atomを Filter::AtomLinkRelated すればOk」というブックマークコメント頂きました。
昨日、頂いたアドバイスの通りAtomFeedで試して見た所、複数設定した筈のブックマークタグが一つだけしか適応されないという現象が発生しました。
IRC(#plagger-ja)でotsuneさん、国内滞在説のあるmiyagawaさんに相談しながら原因を当たった所、昨日の夜にXML::Feedでのdc:subjectの扱い方に問題があるのではないかという事が分かりました。

ここで見て頂きたいのはdc:subjectというノード。dc:subjectは私の記憶ではAtom0.3では厳密に個数は規定されておらず、複数記述する事も出来てしまっています。結果、規定されていないことで色んな実装が表れてしまっています。
はてなの様に複数のdc:subjectを使って表現する物もあれば、del.icio.usの様に一つのdc:subject内に空白(スペース)等でセパレートしてタグを記述している物もあります。
以下、私が簡単に調べた各サービスのフィード出力状況と、そのフィード内のdc:subjectの扱われ方です。
サービス フィード形式 dc:subjectの扱い
はてなブックマーク RSS1.0
Atom0.3
タグ毎にdc:subject
del.icio.us RSS1.0 単一のdc:subjectを空白でセパレート
Livedoor Clip RSS2.0 タグ毎にdc:subject
Buzzurl RSS1.0 タグ毎にdc:subject
Goo Bookmark RSS1.0 出力されない
FC2 Bookmark RSS2.0
※1
出力されない
Pookmark Airlines RSS1.0 タグ毎にdc:subject
※2
Nifty Clip RSS1.0 タグ毎にdc:subject
※3
Blue Dot RSS2.0 単一のdc:subjectをカンマでセパレート
Digg RSS2.0 出力されない
※1 このフィードはちょっと頂けない
※2 入力UIは単一行だがダブルクオート記述出来る
※3 入力UIはjavascriptで追加形式(POSTは1個もしくは配列)
各サービス毎にdc:subjectの扱われ方はまちまちです。
これらの仕様をXML::Feedがどのように扱っているかが原因ではないかと思いました。
現状、XML-Feed-0.12のソースでは

lib/XML/Feed/Atom.pm(146): sub category {
    my $entry = shift;
    my $ns = XML::Atom::Namespace->new(dc => 'http://purl.org/dc/elements/1.1/');
    if (@_) {
        $entry->{entry}->add_category({ term => $_[0] });
    } else {
        my $category = $entry->{entry}->category;
        $category ? ($category->label || $category->term) : $entry->{entry}->get($ns, 'subject');
    }
}
となっていますが、上記"get"ではXML::Atomの"get"が呼ばれ、ARRAYの先頭しか返りません。
XML::Atomには"get"ではなく"getlist"も用意されており、こちらの方はARRAYを返してくれる仕様になっています。
(XML::Feed::RSSの方は元々categoryでARRAYを返す場合もある為、baseであるEntryは既にARRAYを返されても問題ない準備が出来ています)

dc:subjectが単一とは規定されていない事、XML::Atomで"getlist"が用意されている事を、XML::FeedのAUTHORであるBenjamin Trott氏にメールし、パッチも付けて送付しました。
どんな返事が返って来るか分かりませんが、これが正しい修正だとすればXML::Feedのアップグレードで直って来るかもしれません。

しばらくは、はてなブックマークからの同期はrssフィードを使いdescriptionからcontent:encodeを上書きするようなトリックを使うか、AtomFeedを使ってしかも上記の様な修正を入れて対応するかになります。
もしかしたら、Plagger側にcontent:encoded->summaryではなく、description->summaryとなるようなオプション入れても良いかも知れませんね。
それかtsupoさんのbookeyを使うってのもアリですね。
Posted at by




調べたら、BlueDotってdel.icio.us v1 API互換のAPIを公開してた。
ちゃんと調べるべきだなぁ...
ただ、Publish::Delciousではendpoint書き換えられないからパッチ書いた。
Index: lib/Plagger/Plugin/Publish/Delicious.pm
===================================================================
--- lib/Plagger/Plugin/Publish/Delicious.pm (revision 1981)
+++ lib/Plagger/Plugin/Publish/Delicious.pm (working copy)
@@ -18,10 +18,14 @@
 
 sub initialize {
     my ($self, $context, $args) = @_;
-    $self->{delicious} = Net::Delicious->new({
+    my $opt = {
         user => $self->conf->{username},
         pswd => $self->conf->{password},
-    });
+    };
+    for my $key (qw/ endpoint/) {
+        $opt->{$key} = $self->conf->{$key} if $self->conf->{$key};
+    }
+    $self->{delicious} = Net::Delicious->new($opt);
 }
 
 sub add_entry {
YAMLには - module: Publish::Delicious
  config:
    username: del.icio.us-username
    password: del.icio.us-password
    interval: 2
    post_body: 1
    endpoint: https://secure.bluedot.us/v1/
と書くと行ける!

これで、私が同期しているソーシャルブックマークは
  • Publish::Delicious
  • Publish::LivedoorClip
  • Publish::Buzzurl
  • Publish::LivedoorCilp
  • Publish::Buzzurl
  • Publish::GooBookmark
  • Publish::NiftyClip
  • Publish::Pookmark
  • Publish::YahooBookmark
  • Publish::BlueDot という名の Pubilsh::Delicious
となった。
収拾がつかなくなってきた。
こうなったらどこまでやれるか勝負だ
Posted at by




これまた適当に...
/lang/perl/plagger/lib/Plagger/Plugin/Publish/YahooBookmark.pm - CodeRepos::Share - Trac
Yahoo!ブックマーク...正直、今後ログインする機会なんてあるのか?

これで、私が同期しているソーシャルブックマークは
  • Publish::Delicious
  • Publish::LivedoorClip
  • Publish::Buzzurl
  • Publish::GooBookmark
  • Publish::NiftyClip
  • Publish::Pookmark
  • Publish::YahooBookmark
となった。
収拾がつかなくなってきた。
必要かどうかなんて、もうどうでもいい
Posted at by




えーと...。
ただ今、私の環境では
  • Opera 9.50 Alpha
  • Firefox 2.0.0.7
どちらを使ってでもFC2Bookmarkにログイン出来ません。
User Agent Switcher使っても駄目。IE6でログインするとマイブックマーク一覧でjavascriptエラーが多発。登録画面表示しただけで20秒ほどハング。
もうね...ワーキングなんちゃらとか色んな事、言わせんといて下さい。

で、本題。Publish::LivedoorClipをパクらせて頂き、Publish::FC2Bookmarkを書きました。CodeReposに上げときます。
それに合わせて、blosxomのbookmarksプラグインに、FC2Bookmarkの被ブックマーク数画像を追加しています。(こちらはモバイル対応していません)

現状、まともにFC2Bookmarkに登録する方法がPlaggerでしか無いなんて...

「それPla」どころか「それPlaしか」だよ。

Posted at by




Publish::Twitterコピって、Publish::Jaikuをでっちあげた。
一応動いてる。TwitterからJaikuへポストした結果
※テストでは1件だけポストした。
※apikeyはココから

twitter2jaiku.yaml
global:
  assets_path: /home/user/plagger/assets
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://twitter.com/statuses/user_timeline/[twitter user].rss

  - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1

  - module: Publish::Jaiku
    config:
      username: [user name]
      userkey: [your api key]

Plagger/Plugin/Publish/Jaiku.pm
package Plagger::Plugin::Publish::Jaiku;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use Net::Jaiku;
use Time::HiRes qw(sleep);

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry' => \&publish_entry,
        'plugin.init'   => \&initialize,
    );
}

sub initialize {
    my($self, $context) = @_;
    my %opt = (
        username => $self->conf->{username},
        userkey => $self->conf->{userkey},
    );
    $self->{jaiku} = Net::Jaiku->new(%opt);
}

sub publish_entry {
    my($self, $context, $args) = @_;

    my $body = $self->templatize('jaiku.tt', $args);
    # TODO: FIX when Summary configurable.
    if ( length($body) > 159 ) {
        $body = substr($body, 0, 159);
    }
    $context->log(info => "Updating Jaiku status to '$body'");
    $self->{jaiku}->setPresence( message => encode_utf8($body) ) or $context->error("Can't update jaiku status");

    my $sleeping_time = $self->conf->{interval} || 15;
    $context->log(info => "sleep $sleeping_time.");
    sleep( $sleeping_time );
}

1;
__END__

=head1 NAME

Plagger::Plugin::Publish::Jaiku - Update your status with feeds

=head1 SYNOPSIS

  - module: Publish::Jaiku
    config:
      username: jaiku-id
      userkey: jaiku-apikey

=head1 DESCRIPTION

This plugin sends feed entries summary to your jaiku account status.

=head1 CONFIG

=over 4

=item username

Jaiku username. Required.

=item userkey

Jaiku apikey. Required.

=item interval

Optional.

=item timeout

Optional.

=back

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Net::Jaiku>

=cut
assets/plugins/Publish-Jaiku/jaiku.tt
[% IF entry.body %][% entry.body_text %][% ELSE %][% entry.title_text %][% END %] [% entry.permalink %]

どこに納品するかが分かりません。

追記 CodeReposにcommitしました。
Posted at by




otsune nowa - Publish::GooBookmarkを書くためにHTMLソースとか見てるけど」を見ると、otsuneさんが既に書きかけているかも知れないけど...
私の適当なので良ければ...
package Plagger::Plugin::Publish::GooBookmark;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use Time::HiRes qw(sleep);
use URI;
use Plagger::Mechanize;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry' => \&add_entry,
        'publish.init'  => \&initialize,
    );
}

sub initialize {
    my $self = shift;
    unless ($self->{mech}) {
        my $mech = Plagger::Mechanize->new;
        $mech->agent_alias('Windows IE 6');
        $mech->quiet(1);
        $self->{mech} = $mech;
    }
    $self->login_goo_bookmark;
}


sub add_entry {
    my ($self, $context, $args) = @_;

    my @tags = @{$args->{entry}->tags};
    my $tag_string = @tags ? join(',', @tags) : '';

    my $summary;
    if ($self->conf->{post_body}) {
        $summary = encode('utf-8', $args->{entry}->body_text); # xxx should be summary
    }

    my $uri = URI->new('http://bookmark.goo.ne.jp/add/detail/');
    $uri->query_form(
        url  => $args->{entry}->link,
    );

    my $res = eval { $self->{mech}->get($uri->as_string) };
    if ($res && $res->is_success) {
        eval {
            my $button = $self->{mech}->form_name('boomarkEdit')->find_input('addDetail') || 'editEdit';
            $self->{mech}->submit_form(
                form_name => 'boomarkEdit',
                fields => {
                    title       => encode('utf-8', $args->{entry}->title),
                    keywordlist => encode('utf-8', $tag_string),
                    comment     => $summary,
                    publicno    => 0,
                    point       => $self->conf->{rate} || 1,
                },
                button => $button
            )
        };
        if ($@) {
           $context->log(info => "can't submit: " . $@);
        } else {
            $context->log(info => "Post entry success.");
        }
    } else {
       $context->log(info => "fail to bookmark HTTP Status: " . $res->code);
    }
 
    my $sleeping_time = $self->conf->{interval} || 3;
    $context->log(info => "sleep $sleeping_time.");
    sleep( $sleeping_time );
}

sub login_goo_bookmark {
    my $self = shift;
    unless ($self->conf->{username} && $self->conf->{password}) {
        Plagger->context->log(error => 'set your username and password before login.');
    }
    my $res = $self->{mech}->get('https://login.mail.goo.ne.jp/certify-cgi/login.cgi?site=bookmark.goo.ne.jp');
    $self->{mech}->submit_form(
        form_name => 'f1',
        fields => {
            uname => $self->conf->{username},
            pass  => $self->conf->{password},
        },
    );
}

1;

__END__

=head1 NAME

Plagger::Plugin::Publish::GooBookmark - Post to goo bookmark automatically

=head1 SYNOPSIS

  - module: Publish::GooBookmark
    config:
      username: your-username
      password: your-password
      interval: 2
      post_body: 1
      #rate: 3

=head1 DESCRIPTION

This plugin automatically posts feed updates to goo bookmark
L<http://bookmark.goo.ne.jp/>. It supports automatic tagging as well. It
might be handy for synchronizing delicious feeds into goo bookmark.

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Plagger::Plugin::Publish::LivedoorClip>, L<Plagger::Mechanize>

=cut
レート(GooBookmarkでいうpoint)を設定出来るようにした。
それと、Publish::LivedoorClipで重複登録の際に、エラーが出ていたので、パッチを書いた。こちらもレートを変えられるようにした。
Index: LivedoorClip.pm
===================================================================
--- LivedoorClip.pm (revision 1976)
+++ LivedoorClip.pm (working copy)
@@ -46,12 +46,17 @@
         tags  => encode('utf-8', $tag_string),
         title => encode('utf-8', $args->{entry}->title),
         notes => $summary,
+        rate  => $self->conf->{rate} || 1,
     );
 
     my $add_url = $uri->as_string;
     my $res = eval { $self->{mech}->get($add_url) };
     if ($res && $res->is_success) {
-        eval { $self->{mech}->submit_form(form_name => 'clip') };
+        eval {
+            my $form_name = 'clip';
+            $form_name = 'edit_form' if $self->{mech}->form_name($form_name);
+            $self->{mech}->submit_form(form_name => $form_name)
+        };
         if ($@) {
            $context->log(info => "can't submit: " . $args->{entry}->link);
         } else {
otsuneさんと、Publish::LivedoorClipのAUTHORさんがOKならば、それぞれCodeReposに上げる予定です。

しかしまぁ、SBM同期用YAMLがエライ事になってきた。
global:
  assets_path: /home/user/plagger/assets/
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/[hatena user]/atomfeed

  - module: Filter::AtomLinkRelated

  - module: Filter::Rule
    rule:
      module: Deduped
      path: /tmp/syncsbm.db

  - module: Publish::Delicious
    config:
      username: xxxx
      password: xxxx
      interval: 2
      post_body: 1
  - module: Publish::LivedoorClip
    config:
      livedoor_id: xxxx
      password: xxxx
      interval: 2
      post_body: 1
      rate: 3
  - module: Publish::Buzzurl
    config:
      usermail: xxxx
      password: xxxx
      interval: 2
      post_body: 1
  - module: Publish::GooBookmark
    config:
      username: xxxx
      password: xxxx
      interval: 2
      post_body: 1
      rate: 3
追記1
otsuneさんのいうWWW::Mechanizeで書いてしまった...
追記2
otsuneさんからツッコミの有難い頂いたので、修正後にCodeReposにアップします。otsuneさんありがとうございました。
追記3
さらにotsuneさんからツッコミの有難い頂いたので、今後は慎重に行きます。苦笑
Posted at by




先日書いた「個人的ソーシャルブックマークサービスの歩き方」という記事にもある通り、私は個人的な資料をdel.icio.us、ソーシャルなものをはてなブックマークに...と使い分けています。
ただし、携帯からはdel.icio.usが使えない為、はてなブックマークを使ってお気に入りユーザのブクマから必要な物だけを自分のブックマークとしてエントリしています。その後、資料として必要な物をdel.icio.usに手作業で転送しています。ただし量が多い場合にはPlaggerを使うこともあります。
ただし、ここで一つ問題が発生していました。
はてなブックマークのフィードにはブクマコメントがitem/descriptionフィールドに格納されています。ただしPublish::Delicousを含むほぼ全てのSBM系プラグインではsummaryではなくbody(body_text)をコメント部として扱う仕様になっています。ですので
http://b.hatena.ne.jp/mattn/rss <description>おぉ。thx>miyagawa</description>
とdescriptionフィールドに格納されている文字列そのままが欲しいにも関わらず <content:encoded>
  &lt;blockquote cite="http://www.ac.cyberhome.ne.jp/~mattn/cgi-bin/blosxom.cgi/software/lang/perl/20071015162834.htm" title="Big Sky :: Publish::Wassrをでっちあげた"&gt;
    
    &lt;cite&gt;&lt;a href="http://mattn.kaoriya.net/software/lang/perl/20071015162834.htm"&gt;Big Sky :: Publish::Wassrをでっちあげた&lt;/a&gt; &lt;a href="http://b.hatena.ne.jp/entry/http://www.ac.cyberhome.ne.jp/~mattn/cgi-bin/blosxom.cgi/software/lang/perl/20071015162834.htm"&gt;&lt;img src="http://b.hatena.ne.jp/images/entry.gif" title="このエントリーを含むブックマーク" alt="このエントリーを含むブックマーク" border="0"&gt;&lt;/a&gt;&lt;/cite&gt;

  &lt;/blockquote&gt;
  &lt;p&gt;おぉ。thx>miyagawa&lt;/p&gt;
</content:encoded>
という元記事の引用文が含まれたbodyで配信されてしまいます。はじめはPublish::XXXでpost_bodyしているSBM系のプラグインを全て直そうかと(use_summaryみたいなオプションで)思いましたが面倒。いっそAggregator::SimpleのXML::Feed::RSSを操作している部分にオプション付けて強制的にcontentでなくsummaryを使わせるように修正しようかとも思いました。ただ、よく考えたらsummaryをbodyに上書きしてやるプラグインを書いた方が便利だし汎用的だと思い以下のプラグインを作りました。
Plagger/Plugin/Filter/SummaryToBody.pm
package Plagger::Plugin::Filter::SummaryToBody;
use strict;
use base qw( Plagger::Plugin );

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'update.entry.fixup' => \&filter,
    );
}

sub filter {
    my($self, $context, $args) = @_;
    $args->{entry}->body($args->{entry}->summary);
}

1;

__END__

=head1 NAME

Plagger::Plugin::Filter::SummaryToBody - copy summary field to body field.

=head1 SYNOPSIS

  - module: Filter::SummaryToBody

=head1 DESCRIPTION

This plugin copy summary field to body field. This is helpful to sanitize
description field. ex) Hatena bookmark field include <blockquote> tag for
quote.

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Plagger::Plugin::Filter::SummaryToBody>

=cut
使い方はmodule定義だけ。以下は私がはてブからdel.icio.usの転送につかっているYAML
hatebu2delicous.yaml
global:
  assets_path: /home/user/plagger/assets/
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/[hatena user]/rss

  - module: Filter::SummaryToBody

  - module: Filter::Rule
    rule:
      module: Deduped
      path: /tmp/hatebu2delicious.db

  - module: Publish::Delicious
    config:
      username: [delicious username]
      password: [delicious password]
      interval: 2
      post_body: 1
どっちかっていうとBreakXXX系のプラグインで、しかも個人用途でしかありませんが一応公開しておきます。
後でCodeReposにも置いておきます。

追記
もしかしたら空繰再繰さんの「Plagger::Plugin::Filter::ExtractBody」を使ってXPathで「p」とする事でも同じ結果になるかもしれませんね。
こちらは後日試します。
Posted at by




Journal of miyagawa (1653)
TEXTや@srcといったショートカット結果に対して任意のフィルタをかませる事が出来るようになったようです。
これまでのように process "span.entry-content", comment => 'TEXT';
と指定していた部分を process "span.entry-content", comment => [ 'TEXT', 'MyFilter' ];
と記述出来るようになったのです。
MyFilterは「Web::Scraper::Filter::MyFilter」というパッケージで定義され、filterプロシージャが呼び出されます。

さっそく、twitterの発言では70%近くが英語のmiyagawaさんの発言をスクレイピングし、エキサイト翻訳で日本語にフィルタするサンプルを作って見ました。
package Web::Scraper::Filter::EnglishToJapanese;
use base qw( Web::Scraper::Filter );
use warnings;
use LWP::UserAgent;
use HTTP::Request::Common qw(POST);

sub filter {
    my($self, $value) = @_;
    my $req = POST( 'http://www.excite.co.jp/world/english/',
        [before => $value, wb_lp => 'ENJA'] );
    my $data = $Web::Scraper::UserAgent->request($req)->content;
    $data =~ s!\x0D|\x0A!!g;
    $data =~ s/^.*?<textarea[^>]*name="after"[^>]*>(.*?)<\/textarea>.*?$/$1/;
    return $data;
}

1;

use URI;
use Web::Scraper;

my $twitter = scraper {
    process 'td.content',
        'comments[]' => scraper {
            process "span.entry-content", comment => [ 'TEXT', 'EnglishToJapanese' ];
        };
    result 'comments';
};
my $comments = $twitter->scrape( URI->new("http://twitter.com/miyagawa/") );
use YAML;
warn Dump $comments;
で、結果
---
- comment: ' ウェブログを作られた http://tinyurl.com/2xldch '
- comment: ' ウェブを出荷します:、:フィルタサポートがある削り器0.21_01。 バージョン番号が言うようにこれがdevリリースであるのに注意してください。'
- comment: ' 見ます。'
- comment: ' ダッシュボード懺悔室、Yellowcard、少年は少女が好きです: 多くの誘惑が今月のSF warfieldで http://www.ticketmaster.com.. を見せます。 ... '
- comment: ' 100のコメント. diggの上のトップページングのためのtakesako、おめでとう、ワオ490、diggs、 http://tinyurl.com/255ht7 '
- comment: ' スクリーンからStreoパート2までNFGを聞くのがあります。 輝かしいアルバム'
- comment: ' @hanekomu、うん、それはしゃぶられます。 そこでは、日本で同じです。 請求先の住所が米国にある状態で、運よく私はcreditcardsを持っています。'
- comment: ' Dashboard Confessionalの新しいアルバム http://www.amazon.com/gp/pr.. を購入した、アマゾン'
- comment: ' より古いリンクの取り逃がすことは一時的であるように思えました。私がfriendfeedされることへのスイッチかJaikuに好きでないので、 http://tinyurl.com/2gq4uj は少し救いました。'
- comment: ' したがって、さえずりの丁付けはいつまでも、行きましたか? そうだとすれば、私は、確実にさえずりを使用するのを止めるつもりです。'
- comment: ' http://subtech.g.hatena.ne... のウェブログを作りました。 Yappo++typester++Plagger++'
- comment: ' @Yappo++'
- comment: ' IT Crowd s02e05を見ます。'
- comment: ' ep1を見ます。'
- comment: ' 12ドルでmonoprice.comからの外でコンポーネントケーブルと結合器を私のPSPビデオに購入しました。 すさまじい値'
- comment: ' 作成されて、playstationのための削り器は、給送 http://tinyurl.com/yqbtjb plagger++ウェブを格納して、発行しています:、:削り器++'
- comment: ' http://feeds.feedburner.com.. に加入しました。'
- comment: ' 私の2週間のabsenseでは、私はWaMu、Master、およびCapitalOneから10の+クレジットカード申し出を受けました。 ため息をついてください。'
- comment: ' ビールを飲みます。'
エキサイト翻訳の部分をスクレイピングするんが筋ちゃうんかいな!というツッコミは無しでお願いします。
Posted at by




Publish::Twitterコピって、Publish::Wassrをでっちあげた。
一応動いてる。TwitterからWassrへポストした結果
※テストでは1件だけポストした。

twitter2wassr.yaml
global:
  assets_path: /home/user/plagger/assets
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://twitter.com/statuses/user_timeline/[twitter user].rss

  - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1

  - module: Publish::Wassr
    config:
      username: [user name]
      password: [pass word]

Plagger/Plugin/Publish/Wassr.pm
package Plagger::Plugin::Publish::Wassr;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use Net::Wassr;
use Time::HiRes qw(sleep);

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry' => \&publish_entry,
        'plugin.init'   => \&initialize,
    );
}

sub initialize {
    my($self, $context) = @_;
    my %opt = (
        user => $self->conf->{username},
        passwd => $self->conf->{password},
    );
    for my $key (qw/ apihost apiurl apirealm/) {
        $opt{$key} = $self->conf->{$key} if $self->conf->{$key};
    }
    $self->{wassr} = Net::Wassr->new(%opt);
}

sub publish_entry {
    my($self, $context, $args) = @_;

    my $body = $self->templatize('wassr.tt', $args);
    # TODO: FIX when Summary configurable.
    if ( length($body) > 159 ) {
        $body = substr($body, 0, 159);
    }
    $context->log(info => "Updating Wassr status to '$body'");
    $self->{wassr}->update( {status => encode_utf8($body)} ) or $context->error("Can't update wassr status");

    my $sleeping_time = $self->conf->{interval} || 15;
    $context->log(info => "sleep $sleeping_time.");
    sleep( $sleeping_time );
}

1;
__END__

=head1 NAME

Plagger::Plugin::Publish::Wassr - Update your status with feeds

=head1 SYNOPSIS

  - module: Publish::Wassr
    config:
      username: wassr-id
      password: wassr-password

=head1 DESCRIPTION

This plugin sends feed entries summary to your Wassr account status.

=head1 CONFIG

=over 4

=item username

Wassr username. Required.

=item password

Wassr password. Required.

=item interval

Optional.

=item apiurl

OPTIONAL. The URL of the API for wassr.jp. This defaults to "http://wassr.jp/user/xxx/statuses" if not set.

=item apihost

=item apirealm

Optional.
If you do point to a different URL, you will also need to set "apihost" and "apirealm" so that the internal LWP can authenticate.

    "apihost" defaults to "api.wassr.jp:80".
    "apirealm" defaults to "API Authentication".

=back

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Net::Wassr>

=cut
assets/plugins/Publish-Wassr/wassr.tt
[% IF entry.body %][% entry.body_text %][% ELSE %][% entry.title_text %][% END %] [% entry.permalink %]
deps/Publish-Wassr.yaml
name: Publish::Wassr
author: Yasuhiro Matsumoto
depends:
  Net::Wassr: 0

Net::Wassrは[Perl]Net::Wassr - Hatena::Diary::Neko::kak 500 Internal Server Errorを使用。
Posted at by




Web::Scraper 0.15とcisco_scraper.pl
問題が一つ。添削してくださったパッチだと process '//li/node()[4]', 'title' => sub {$_->string_value;};
となっているのですが、4番目とは限らないんです。
たとえば、
http://www.cisco-records.co.jp/html/item/004/010/item393180.html
は何曲か試聴サンプルがないために、この処理だと取得できないです。
おろろ...
これはtext()でTextNodeを参照するしかないですね。
ただ、text()では改行等のゴミまで拾ってしまうので、以下のようにnormalize-space()で空文字ノードを省いています。
もしかすると、node()[2]も同じように修正した方がいいかもしれませんね。
#!/usr/bin/perl

use strict;
use warnings;

use Web::Scraper;
use URI;
use YAML;
use Data::Dumper;

my $uri = shift;

my %scraper;

$scraper{'link'} = scraper {
    process 'a', 'name' => 'TEXT';
    process 'a', 'uri'  => '@href';
    result qw/name uri/;
};

$scraper{'genre'} = scraper {
    process '//a[1]', 'top'   => $scraper{link};
    process '//a[2]', 'style' => $scraper{link};
    result qw/top style/;
};

$scraper{'track'} = scraper {
    process '//li/text()[normalize-space(.)!=""]', 'title' => sub {
        my $s = $_->as_XML;
        $s =~ s/\s+$//;
        return $s;
    };
    process 'li>a', 'uri' => '@href';
    result qw/title uri/;
};

$scraper{'item'} = scraper {
    process 'td.de_title',      'title'  => 'TEXT';
    process 'td.de_artist',     'artist' => 'TEXT';
    process 'td.nm_jacket>img', 'image'  => '@src';
    process 'td.de_price',              'price'   => 'TEXT';
    process 'td.de_label>a',            'label'   => $scraper{link};
    process 'td.de_genre',              'genre'   => $scraper{genre};
    process 'td[headers="de_format"]',  'format'  => 'TEXT';
    process 'td[headers="de_release"]', 'release' => 'TEXT';
    process 'td[headers="de_country"]', 'country' => 'TEXT';
    process 'td[headers="de_sheet"]',   'sheet'   => 'TEXT';
    process 'td[headers="de_arrival"]', 'arrival' => 'TEXT';
    process 'td[headers="de_nomber"]',  'number'  => 'TEXT';
    process '//p[@class="de_star"]/node()[2]', 'star' => 'TEXT';
    process 'ul[id="de_sound"]>li', 'tracks[]' => $scraper{track};
    result
        qw/title artist image price label genre format release release country sheet arrival number star tracks/;
};

my $item = $scraper{'item'}->scrape( URI->new($uri) );
warn Dump $item;
あと、ブックマークコメント
コールバック渡しだと相対URLの展開がされないのは僕だけ?
との事ですが...少し調べてみた所Web::Scraper側でパッチが必要かもしれません。
以下svn/trunk(rev2351)からの差分です。
Index: lib/Web/Scraper.pm
===================================================================
--- lib/Web/Scraper.pm  (revision 2351)
+++ lib/Web/Scraper.pm  (working copy)
@@ -152,12 +152,12 @@
         local $_ = $node;
         return $val->($node);
     } elsif (blessed($val) && $val->isa('Web::Scraper')) {
-        return $val->scrape($node);
+        return $val->scrape($node, $uri);
     } elsif ($val =~ s!^@!!) {
         my $value =  $node->attr($val);
         if ($uri && is_link_element($node, $val)) {
             require URI;
-            $value = URI->new_abs($value, $uri);
+            $value = URI->new_abs($value, $uri)->as_string;
         }
         return $value;
     } elsif (lc($val) eq 'content' || lc($val) eq 'text') {
Posted at by




Exuberant ctags

ctags 5.7 improves Perl support

  • Added support for 'package' keyword
  • Added support for multi-line subroutine, package, and constant definitions
  • Added support for optional subroutine declarations
  • Added support for formats
  • Ignore comments mixed into definitions and declarations
  • Fixed detecting labels with whitespace after label name
  • Fixed misidentification of fully qualified function calls as labels
これ凄いす。もう朝からお腹いっぱいです。
さっそくWin32版試してみました。
改行されたメソッドにもジャンプ出来るし、余計なコメントにもヒットしないし、使いやすいです。
vimで開発する方のpluginフォルダには必ずと言って良いほど入っているtaglist.vimを使うとパッケージ名称も一覧されます。
パッケージやサブルーチンがキレイに一覧されます。
さらに今回「package」に対応したので、ちょっと時間は掛かりますが C:\Perl\site\lib>ctags -R -h ".pm"
こんな事して...
set tags=./tags,tags,../tags,c:/perl/site/lib/tags
こんな事しておくと...


ctags_beforejump
こんな状態でビジュアル選択しておいて、"C-]"を押す事で
ctags_afterjump
こんな感じにタグジャンプします。ウマーーー
複数含むモジュールだと、taglist.vimのTagListには複数のpackageが表示されます。

すばらしす...
Posted at by




どっちかっていうと、今日の出来事みたいな記事になります。
題名の件をやろうとまず、以下のようなYAMLを書いた。
plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/t/shibuya.pm?mode=rss

  - module: Publish::OPML
    config:
      title: Shibuya.pm
      filename: shibuya-pm.opml
で実行したけれどOPMLが空っぽ。
ソースを追ってBreakEntriesToFeedsが使えそうだったので以下の行を足した。   - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1
でも駄目。で、miyagawaさんにメールした。なぜか英語で...
余談 : なぜ英語か
以前、Web::Scraperについて質問メールを送った。でも反応が無かったので物は試しと英語で書いた。そしたら返事が返って来た。
→ 以後英語... orz
miyagawaさんから、「use BreakEntriesToFeeds」と返事が来たけど、「BreakEntriesToFeeds」は1 entryを1 feedに変換する為のプラグインで、subscriptionを変更するものでは無かった。
で、書きあげたのが以下のプラグイン

BreakEntriesToSubscriptions.pm
package Plagger::Plugin::Filter::BreakEntriesToSubscriptions;
use strict;
use base qw( Plagger::Plugin );

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'update.feed.fixup' => \&break,
    );
}

sub break {
    my($self, $context, $args) = @_;

    for my $entry ($args->{feed}->entries) {
        my $feed = $args->{feed}->clone;
        $feed->clear_entries;

        $feed->add_entry($entry);
        $feed->link($entry->link);
        $feed->url($entry->link);
        eval {
            use HTML::TokeParser;
            my $agent = Plagger::UserAgent->new;
            my $res = $agent->fetch($entry->link, $self);
            my $parser = HTML::TokeParser->new(\$res->content);
            while (my $token = $parser->get_tag("link")) {
                my $attr = $token->[1];
                if ($attr->{rel} eq 'alternate'
                        && ($attr->{type} eq 'application/rss+xml'
                         or $attr->{type} eq 'application/atom+xml')) {
                    $feed->url(URI->new_abs($attr->{href}, $entry->link)-> as_string);
                    last;
                }
            }
        } if $self->conf->{use_auto_discovery};
        $feed->title($entry->title)
            if $self->conf->{use_entry_title};
        $context->subscription->add($feed);
    }

    $context->subscription->delete_feed($args->{feed});
}

1;

__END__

=head1 NAME

Plagger::Plugin::Filter::BreakEntriesToSubscriptions - some entry = 1 subscription

=head1 SYNOPSIS

  - module: Filter::BreakEntriesToSubscriptions

=head1 DESCRIPTION

This plugin breaks all the subscription entries into a single feed. This is
a fairly hackish plugin but it's helpful for make OPML from feeds.

=head1 CONFIG

=over 4

=item use_entry_title

Use subscription's title as a newly generated feed title. Defaults to 0.

=back

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 THANKS

Tatsuhiko Miyagawa

=head1 SEE ALSO

L<Plagger>

=cut
ドキュメントにも書いた通り、ちょっと(かなり?)hackishなプラグインです。
このプラグインを使って

shibuya-pm2opml.yaml
plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/t/shibuya.pm?mode=rss

  - module: Filter::BreakEntriesToSubscriptions
    config:
      use_entry_title: 1
      use_auto_discovery: 1

  - module: Publish::OPML
    config:
      title: Shibuya.pm
      filename: shibuya-pm.opml
こんなYAMLを用意すれば
「Shibuya.pm」のタグが付いている「はてなブックマーク」からオートディスカバリでフィードを取得したOPML
こんなOPMLファイルが出来上がります。

miyagawaさんに感謝

おしまい

※もしかしたらオートディスカバリ出来なかったURL(例えばPDFとか)はOPMLに含めないようにするオプションがいるかも...
Posted at by




こういう使い方もあるね。
で、どうする...って訳でもないけど
※そういうの、「使い道ない」っていうんだよね。そうだよね。
#!/usr/bin/perl

use strict;
use warnings;

use Web::Scraper;
use URI;
use YAML;

my $airlines_accident_scraper = scraper {
  process '//div[@class="entry-content"]//table/tr',
    'airlines[]' => scraper {
      process '//td[1]', title => 'TEXT';
      process '//td[2]', last_accident => 'TEXT';
      process '//td[3]', flight_count => 'TEXT';
      process '//td[4]', death_accident => 'TEXT';
      process '//td[5]', death_rate => 'TEXT';
      process '//td[6]', accident_incidence => 'TEXT';
      process '//td[7]', total_rank => 'TEXT';
    };
  result 'airlines';
};

my $list = $airlines_accident_scraper->scrape(URI->new('http://www.manji.com/jp/2007/08/post_22.html'));
use YAML;
warn Dump $list;
リストは、マスコミが報じない危険な航空会社リストから拝借。

余談ですが...
Web::Scraper 0.16あたりから、@参照するとstringでなく、URIか返ってくるようになってるので、「認証付きのページで@srcを拾い上げて、認証無しでは参照出来ない画像を落とす」なんて事に使えるようになったみたいです。
Posted at by




これでFilterも作りやすくなるのかな...
例えば、はてなブックマークのフィードからShibuya.pmタグが付いてる物のOPMLを作るとか?(自身無さげ)
でもこれ、MIMEパターンをconfig.yamlに上手くめり込ませる方法ってないのかな...
指定する場合、「このURLに対しては変則的なxxxなMIMEで取りたい」って使いたいんだよね。

Index: lib/Plagger/Plugin/Subscription/Feed.pm
===================================================================
--- lib/Plagger/Plugin/Subscription/Feed.pm (revision 1959)
+++ lib/Plagger/Plugin/Subscription/Feed.pm (working copy)
@@ -17,7 +17,6 @@
 sub load {
     my ( $self, $context ) = @_;
 
-    # TODO: Auto-Discovery, XML::Liberal
     my $uri = URI->new( $self->conf->{url} )
       or $context->error("config 'url' is missing");
 
@@ -30,6 +29,20 @@
     my $content = Plagger::Util::load_uri($uri);
     my $feed = eval { Plagger::FeedParser->parse(\$content) };
 
+    if unless($feed) {
+        use HTML::TokeParser;
+        my $parser = HTML::TokeParser->new(\$content);
+        while (my $token = $parser->get_tag("link")) {
+            my $attr = $token->[1];
+            if ($attr->{rel} eq 'alternate'
+                    && ($attr->{type} eq 'application/rss+xml'
+                     or $attr->{type} eq 'application/atom+xml') {
+                $uri = $attr->{href};
+                $feed = eval { Plagger::FeedParser->parse(\$content) };
+                last;
+            }
+        }
+    }
     unless ($feed) {
         $context->log( error => "Error loading feed $uri: $@" );
         return;
Posted at by




Web::Scraperの0.15がリリースされたので、色々試してみてます。
ほんのりと変わった所を調べてみます。

UserAgentが置き換えられるようになった

uaのスコープを変えて頂けたので、先日の「WWW::MechanizeとWeb::Scraperでtwitterのfriendsを全部取ってみる」でやった以下の様な無理やりなUA置き換えでなく undef &Web::Scraper::__ua;
*Web::Scraper::__ua = sub {
    $mech;
};
以下のようにキレイな置き換えが出来るようになります。 $Web::Scraper::UserAgent = $mech;
スコープもourになってますから、別のパッケージ作るときにも便利かもしれませんね。


ショートカットとしてRAWが使えるようになった

これはインラインのjavascriptをそのままスクレイピングするのに使えますね。
#!/usr/bin/perl

use strict;
use warnings;
use Web::Scraper;
use URI;
use YAML;
use Data::Dumper;

my $my_script = scraper {
    process '//script[2]', 'code' => 'raw';
    result 'code';
};
my $item = $my_script->scrape( URI->new("http://mattn.kaoriya.net") );
warn Dump $item;
この例では、はてなスターのトークン設定部が取得出来ます。 --- |-
<!--
Hatena.Star.Token = '43aa5d7954c8fc062faae4eaaa864913599c277b';
-->
※htmlというショートカットでも使えます。


相対URLから絶対URLを自前で生成しなくてもよくなった

お腹が空いてきたのでWeb::Scraperでモスバーガーのメニューをスクレイピング」でやったように、今までは process '.', url => sub {URI->new_abs($_->attr('href'), $uri)->as_string;};
というコードが必要でしたが、「@」による属性参照でリンクエレメントであるようならば、自動的に絶対URLを生成してくれます。
上のようなコードも process '.', url => '@href';
と楽になりますね。
以上が私が0.13と0.15のdiffをざーーーーと見た感じの変更点です。


おまけ

今日はこれに付け加え、一つtipsを...
Web::ScraperでCISCO RECORDSをスクレーピングより
たとえば <li><span>1</span>Track 1</li>
というHTMLから"Track1"だけを抽出しようにも process 'li', 'title' => 'TEXT';
だと 1Track1 なんて結果になるのでそれを回避するために process 'li', 'title' => sub {
    my $elem = shift;
    $elem->find_by_tag_name('span')->delete;
    return $elem->as_text;
};
なんてことをしてるのですが、もっといい方法があるはず。
treeを壊さずやるとすれば、TextNodeを参照するのがいいかと思います。
例えば、XPathのnode()を使い、番号指定で取得します。だた現状のWeb::ScraperではTextNodeはショートカットで参照出来ませんので、以下のようにstring_valueを返すように手を加えると上手く行きます。
※相対URLの修正も含んでいます。
--- cisco_scraper.pl    Tue Sep 18 13:30:20 2007
+++ cisco_scraper2.pl   Tue Sep 18 14:12:49 2007
@@ -14,9 +14,7 @@
 
 $scraper{'link'} = scraper {
     process 'a', 'name' => 'TEXT';
-    process 'a', 'uri'  => sub {
-        return URI->new_abs( $_->attr('href'), $uri )->as_string;
-    };
+    process 'a', 'uri'  => '@href';
     result qw/name uri/;
 };
 
@@ -27,23 +25,15 @@
 };
 
 $scraper{'track'} = scraper {
-    process 'li', 'title' => sub {
-        my $elem = shift;
-        $elem->find_by_tag_name('span')->delete;
-        return $elem->as_text;
-    };
-    process 'li>a', 'uri' => sub {
-        return URI->new_abs( $_->attr('href'), $uri )->as_string;
-    };
+   process '//li/node()[4]', 'title' => sub {$_->string_value;};
+    process 'li>a', 'uri' => '@href';
     result qw/title uri/;
 };
 
 $scraper{'item'} = scraper {
     process 'td.de_title',      'title'  => 'TEXT';
     process 'td.de_artist',     'artist' => 'TEXT';
-    process 'td.nm_jacket>img', 'image'  => sub {
-        return URI->new_abs( $_->attr('src'), $uri )->as_string;
-    };
+    process 'td.nm_jacket>img', 'image'  => '@src';
     process 'td.de_price',              'price'   => 'TEXT';
     process 'td.de_label>a',            'label'   => $scraper{link};
     process 'td.de_genre',              'genre'   => $scraper{genre};
@@ -53,12 +43,7 @@
     process 'td[headers="de_sheet"]',   'sheet'   => 'TEXT';
     process 'td[headers="de_arrival"]', 'arrival' => 'TEXT';
     process 'td[headers="de_nomber"]',  'number'  => 'TEXT';
-    process 'p.de_star',                'star'    => sub {
-        my $elem = shift;
-        $elem->find_by_tag_name('span')->delete;
-        return $elem->as_text;
-
-    };
+    process '//p[@class="de_star"]/node()[2]', 'star' => sub {$_->string_value;};
     process 'ul[id="de_sound"]>li', 'tracks[]' => $scraper{track};
     result
         qw/title artist image price label genre format release release country sheet arrival number star tracks/;
このTextNode参照の需要があるならショートカットでも良いと思うんですが、いまんところ無さそうですね。

追記
otsune氏より「diff -uじゃないと...」という指摘で修正。
Posted at by




twitterのAPIでfriendsが100件しか取れなくなって久しいですが...
WWW::MechanizeとXPathでtwitterの全friendsを取得するサンプル作ってみました。 あまりやり過ぎると、オフィシャル側に怒られそうな気もしますが...
後の使い方は、適当で... #!/usr/local/bin/perl

use warnings;
use strict;
use LWP::Simple;
use XML::Simple;
use WWW::Mechanize;
use HTML::TreeBuilder::XPath;
use HTML::Selector::XPath qw(selector_to_xpath);
use Data::Dumper;

my $username = 'your_username';
my $password = 'your_password';

my $m = WWW::Mechanize->new(timeout => 10);
$m->get('http://twitter.com/login');
$m->submit_form(
    form_number => 1,
    fields    => {
        username_or_email  => $username,
        password           => $password,
    },
    button    => 'commit',
);

my $xpath = selector_to_xpath('tr.vcard');
my @friends;

my $num_page = 1;
while (1) {
    my $res = $m->get("http://twitter.com/friends/?page=$num_page");
    my $encoding = $res->header('Content-Encoding');
    my $content = $res->content;
    $content = Compress::Zlib::memGunzip($content) if $encoding =~ /gzip/i;
    $content = Compress::Zlib::uncompress($content) if $encoding =~ /deflate/i;

    my $tree = HTML::TreeBuilder::XPath->new;
    $tree->parse($content);
    $tree->eof;
    my @nodes = $tree->findnodes($xpath);
    for my $tr (@nodes) {
        push(@friends, {
                nick => $tr->findnodes('td/strong/a')->[0]->as_text,
                image => $tr->findvalue('td[@class="thumb"]//img/@src')->as_string,
                name => $tr->findvalue('td[@class="thumb"]//img/@alt')->as_string,
                description => $tr->findvalue('td/strong/a/@title')->as_string,
                url => $tr->findvalue('td[@class="thumb"]/a/@href')->as_string,
            });
    }
    $tree->delete;
    @nodes or last;
    $num_page++;
}

print Dumper @friends;

最近遊んでる物、ほとんどmiyagawa氏のものばっかだな...
Posted at by




muumoo.jpPlaggerで取得したGoogleブックマークのフィードを整えるFilter:GoogleBookmarksFeedを書いたけど日本語消えちゃう (管理人日記)という記事より。
喜んだのもつかの間、日本語の文字を含むタグやコメントを書くと、その文字が消えてしまうようです。Plaggerではありがちな問題なような気がしますが、このPluginでも起きてしまいました。
確かに、ブラウザ上からだと日本語は見えるんですが、どうやらGoogleさんはUser-Agentを見て勝手にencodingをISO-8859-1に変えておられるようです。
# curl -L 'https://www.google.com/bookmarks/?output=rss' -u username:password
<?xml version="1.0" encoding="ISO-8859-1"?><rss vers
...
config.yamlの先頭に global:
  timezone: Asia/Tokyo
  user_agent:
    agent: Mozilla/5.0
を入れたら取得出来ました。
ブクマコメントで書こうかと思いましたが、記事が半月程前のものなので管理人さんも見てないかと思い、記事にしました。

それよりも...LivedoorClip.pmで Plagger [info] plugin Plagger::Plugin::Subscription::Config loaded.
Plagger [info] plugin Plagger::Plugin::UserAgent::AuthenRequest loaded.
Plagger [info] plugin Plagger::Plugin::Filter::GoogleBookmarksFeed loaded.
Plagger [info] plugin Plagger::Plugin::Publish::LivedoorClip loaded.
Plagger [info] plugin Plagger::Plugin::Bundle::Defaults loaded.
Plagger [info] plugin Plagger::Plugin::Aggregator::Simple loaded.
Plagger [info] plugin Plagger::Plugin::Summary::Auto loaded.
Plagger [info] plugin Plagger::Plugin::Summary::Simple loaded.
Plagger [info] plugin Plagger::Plugin::Namespace::HatenaFotolife loaded.
Plagger [info] plugin Plagger::Plugin::Namespace::MediaRSS loaded.
Plagger [info] plugin Plagger::Plugin::Namespace::ApplePhotocast loaded.
Plagger::Plugin::Aggregator::Simple [info] Fetch https://www.google.com/bookmarks/?output=rss
Plagger::Plugin::UserAgent::AuthenRequest [info] Adding credential to Google Search History at www.google.com:443
Plagger::Cache [debug] Cache HIT: Aggregator-Simple|https://www.google.com/bookmarks/?output=rss
Plagger::Plugin::Aggregator::Simple [debug] 200: https://www.google.com/bookmarks/?output=rss
Plagger::Plugin::Aggregator::Simple [info] Aggregate https://www.google.com/bookmarks/?output=rss success: 15 entries.
Died at C:/Perl/site/lib/WWW/Mechanize.pm line 1705.
なエラーが出る。なんぞ?
とりあえずcpan upgrade行ってきます。

追記1
GoogleBookmarksFeedで、tagsは1個でも配列で返ってきてそうだったので以下のように修正してます。もしかしたら間違ってるかも *** GoogleBookmarksFeed.pm.orig Tue Sep 04 11:39:49 2007
--- GoogleBookmarksFeed.pm  Tue Sep 04 11:40:15 2007
***************
*** 22,28 ****
              $args->{entry}->body($orig_body);
              $context->log(info => "Parsing Google Bookmarks title " . $args->{entry}->permalink);
          }
!         if (my @orig_tags = @{$args->{orig_entry}->{entry}->{$ns}->{bkmk_label}}) {
              $args->{entry}->tags(@orig_tags);
          }
      }
--- 22,28 ----
              $args->{entry}->body($orig_body);
              $context->log(info => "Parsing Google Bookmarks title " . $args->{entry}->permalink);
          }
!         if (my @orig_tags = $args->{orig_entry}->{entry}->{$ns}->{bkmk_label}) {
              $args->{entry}->tags(@orig_tags);
          }
      }
追記2
大嘘ついてました。tagsは1つの場合は文字、2つ以上の場合は配列で戻るみたいです。 *** GoogleBookmarksFeed.pm.orig Tue Sep 04 11:39:49 2007
--- GoogleBookmarksFeed.pm  Tue Sep 04 14:54:17 2007
***************
*** 22,29 ****
              $args->{entry}->body($orig_body);
              $context->log(info => "Parsing Google Bookmarks title " . $args->{entry}->permalink);
          }
!         if (my @orig_tags = @{$args->{orig_entry}->{entry}->{$ns}->{bkmk_label}}) {
!             $args->{entry}->tags(@orig_tags);
          }
      }
  }
--- 22,33 ----
              $args->{entry}->body($orig_body);
              $context->log(info => "Parsing Google Bookmarks title " . $args->{entry}->permalink);
          }
!         if (my $orig_tags = $args->{orig_entry}->{entry}->{$ns}->{bkmk_label}) {
!           if (ref($orig_tags) eq "ARRAY") {
!               $args->{entry}->tags($orig_tags);
!           } else {
!               $args->{entry}->tags([$orig_tags]);
!           }
          }
      }
  }
Posted at by




こんな夜中に何やってんだか...
お腹が空いてきたので、モスバーガーのホームページからメニューをスクレイピングしてみる。
別に買いに行く訳じゃないけど...

思った以上に苦戦。苦戦の理由は「HTMLにIDやCLASSが殆んど振られておらず、XPathで抽出出来るパタンがない」こと。しょうがないのでまた無茶ぶりを発揮して、ノード階層をパタンとして使い、最小マッチのノードから欲しいノードへ上昇するというドロ臭いXPathを書いた。
パタンは、td要素を2つ持つtr要素で、かつそのtd要素内にはhref属性に"/menu/"という文字列を含んだa要素、しかもそのa要素は"pdf"という文字列を含んでいない。
結果、CSSセレクタは全く使わなかった(使えなかった?)。これじゃ、Web::Scraperのスライド資料の悪い例のままだ...
ま、取れたので良しとしよう。


mosburger-scraper.pl #!/usr/local/bin/perl

use warnings;
use strict;
use Web::Scraper;
use YAML;
use URI;

my $uri = URI->new("http://www.mos.co.jp/menu/index.html");
my $mosburger = scraper {
    process '//tr[count(td)=2]/td/a[contains(@href,"/menu/") and not(contains(@href,".pdf"))]/img/../../..',
        'menus[]' => scraper {
            process '/tr/td[1]/a',     url => sub {URI->new_abs($_->attr('href'), $uri)->as_string;};
            process '/tr/td[1]/a/img', title => '@alt';
            process '/tr/td[1]/a/img', image => sub {URI->new_abs($_->attr('src'), $uri)->as_string;};
            process '/tr/td[2]/a',
                'perk' => scraper {
                    process '.', url => sub {URI->new_abs($_->attr('href'), $uri)->as_string;};
                    process 'img', title => '@alt';
                };
        };
    result 'menus';
};

my $burgers = $mosburger->scrape($uri);
warn Dump $burgers;

---
- image: http://www.mos.co.jp/menu/img/ph_hamburger18.jpg
  perk:
    title: サウザン野菜バーガー ¥300
    url: http://www.mos.co.jp/menu/hamburger/thousand/
  title: サウザン野菜バーガー
  url: http://www.mos.co.jp/menu/hamburger/thousand/
- image: http://www.mos.co.jp/menu/img/ph_hamburger19.jpg
  perk:
    title: [期間限定 10月中旬まで] シーザーサラダバーガー ¥300
    url: http://www.mos.co.jp/menu/hamburger/seasar/
  title: シーザーサラダバーガー
  url: http://www.mos.co.jp/menu/hamburger/seasar/
...
補足情報(場合によってはセットメニュー)も一緒に取得出来ます。

あー。はらへった。
Posted at by




via twitter/plaggerのポスト

FrontPage - habu Wiki @ SF.jp

habuは、プラグインを組み合わせることでRSSを加工して再配信するためのソフトウェアです。簡単に言っちゃえば,Plaggerもどきです。

plaggerも好きだし、pythonも好きなら試さない訳には行きません。インストールはマイコミジャーナルが詳しいかと思います。私は、いきなりsvn/trunk取得して必要な物はeasy_installしました。

まぁ題名の件で言えば、iTunesで聞いている曲をTwitterにPostするPythonのスクリプト ? TRIVIAL TECHNOLOGIES 2.0を使えば動きそうですが、とりあえず...

habuではpluginはクラスで定義され、実行時にexecuteというメソッドが呼び出されます。気をつけなければならないのがexecuteはperl版と異なりentry単位にhookされるのではなく、全てのcontentsを持って呼び出されます。
※開発中なのかbaseとなるpluginクラスといった物はありません。
またhabuにはまだtemplateが実装されていません。ですのでポスト形式は現状プラグイン本体に記述する事になります。(いずれ改良されるかもしれません)
あと、これはいたし方ないですがsite-packageにあるxxx.pyを使うpublisher/xxx.pyでimport xxx出来ません。
今回のtwitterポストプラグインも「twitterPost.py」という名前にしてあります。
webutilsクラスやlogクラス等で、おおよそperl版と同じような事が出来ます。perlよりもpythonの方が可読性がありますし、pytnonにも負けない程のライブラリ郡もありますから、色んな事が出来るかと思います。Djangoなんかと組み合わせても面白そうです。
あと、面白いなと思ったのがコマンドライン用インタフェース「runhabu.py」に"--download-module"というオプションがあり、svnサーバからダウンロード出来る仕組みがあるようです。こちらは追々試します。
pythonが好きでplaggerも触りたい人は、こちらから初めてみてみるのも良いかもしれませんね。
で、最後に適当に作ったプラグインがコレ
habu/publisher/twitterPost.py
# -*- coding: utf-8 -*-
from twitter import Api
import habu.log as log

class TwitterPublisher(object):
  def __init__(self, config, environ):
    self.username = config.get("username", "twitter username")
    self.password = config.get("password", "twitter password")

  def execute(self, content):
    try:
      api = Api(self.username, self.password)
      for entry in content["entries"]:
        message = entry["title"] + ":"
        if len(entry["summary"]):
          message += entry["summary"]
        else:
          message += entry["description"]
        message += " " + entry["link"]
        if len(message) > 159:
          message = message[0: 159] + "..."
        api.PostUpdate(message)
    except Exception, e:
      log.error()
    else:
      log.info("twitterPost : commit")

def create(config, environ):
  return TwitterPublisher(config, environ)
あと、用意するYAMLはこんな感じ
global:
  timezone: Asia/Tokyo
  log: stdout

pipeline:
  rss_fetcher:
    - module: subscription.config
      config:
        feed:
          - http://b.hatena.ne.jp/[hatena user]/rss
    - module: filter.join
    - module: filter.sort
      config:
        reverse: True
    - module: publisher.twitterPost
      config:
        username: [twitter username]
        password: [twitter password]
deps相当、Crypt相当の物はまだ実装されていませんのでご利用は計画的に。
Posted at by




昨日書いた「pythonで動作するPlagger「habu」でtwitterにポストするプラグイン書いた」を作者の方がご覧になられ、野良プラグイン置き場のcommit権限を頂きました。
さっそく、昨日の「twitterPost.py」もcommitさせて頂きました。
で、今日はperl版のPublish::Gmailを移植。
おおよそPublish::Gmailを取り込めてますが、viaをサポート出来ていません。
ソースは適当に(いつもながら)こんな感じ...

# -*- coding: utf-8 -*-
__author__ = 'mattn.jp@gmail.com'
__version__ = '0.1'

import smtplib
import urllib
import urlparse
import re
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
from email.MIMEImage import MIMEImage
from email.Header import Header
from email.Utils import formatdate
from HTMLParser import HTMLParser
import habu.log as log

class _EnclosureParser(HTMLParser):
  def __init__(self):
    HTMLParser.__init__(self)
    self.base = ""
    self.files = []

  def set_base(self, url):
    if url.endswith("/"):
      self.base = url + "index.html"
    else:
      self.base = url

  def handle_starttag(self, tag, attrs):
    if tag.lower() == "img":
      for attr in attrs:
        if attr[0] == "src":
          self.files.append(urlparse.urljoin(self.base, attr[1]))
          break

class MailPublisher(object):
  def __init__(self, config, environ):
    self.mailto = config.get("mailto", "mail to")
    self.mailfrom = config.get("mailfrom", "mail from")
    self.mailroute = config.get("mailroute", "mail route")
    self.encoding = config.get("encoding", "utf-8")

  def execute(self, content):
    try:
      if self.mailroute.has_key("host"):
        s = smtplib.SMTP(self.mailroute["host"])
      else:
        s = smtplib.SMTP()
      for entry in content["entries"]:
        html = """<html>
<body>
""" + entry["description"] + """
</body>
</html>"""
        msg = MIMEMultipart()
        msg['Subject'] = Header(entry["title"], self.encoding)
        msg['From'] = self.mailfrom
        msg['To'] = self.mailto
        msg['Date'] = formatdate()
        msg['X-Mailer'] = "pyhabu::mailPost %s" % __version__
        related = MIMEMultipart('related')
        alt = MIMEMultipart('alternative')
        related.attach(alt)
        ep = _EnclosureParser()
        try:
          ep.set_base(entry["link"])
          ep.feed(html)
        except Exception, e:
          pass
        finally:
          ep.close()
        cnt = 1
        for filename in ep.files:
          fp = urllib.urlopen(filename)
          headers = fp.info()
          if headers.has_key("content-type"):
            mimetype = headers["content-type"].replace("image/", "")
            name = "img%d.%s" % (cnt, mimetype)
            img = MIMEImage(fp.read(), mimetype, name=name)
            img['Content-ID'] = '<%s>' % name
            related.attach(img)
            html = re.compile(r"(<img.*>)").sub(
              lambda x:x.group(1).replace(filename, name), html)
            cnt += 1
        content = MIMEText(html.encode(self.encoding, "replace"), 'html', self.encoding)
        alt.attach(content)
        msg.attach(related)

        s.sendmail(self.mailfrom, self.mailto, msg.as_string())
        log.info("mailPost : commit")
    except Exception, e:
      log.error()
    finally:
      s.close()

def create(config, environ):
  return MailPublisher(config, environ)

※おっ!enclosureもサポートしてんじゃん...
で、用意するYAMLはこんな感じ...
global:
  timezone: Asia/Tokyo
  log: stdout

pipeline:
  rss_fetcher:
    - module: subscription.config
      config:
        feed:
          - http://www.example.com/index.rss
    - module: filter.join
    - module: filter.sort
      config:
        reverse: True
    - module: publisher.mailPost
      config:
        mailto: yourname@gmail.com
        mailfrom: yourname@gmail.com
        mailroute:
          host: mail.example.com
        #encoding: iso-2022-jp

結論:pythonもperlも楽しい。
Posted at by




Powered by Vim 会社でちらりと覗いたデスクトップにvimを見つけました。少しずつですが何故か最近vimユーザが増えている気がしています。
vimは開発の場で見かけるとカッコよく映りますね。vimユーザでない方から見ると「何だか分かんない内にソースコードが出来上がって行く」と神懸り的な物に見えるようです。
今日はこれまでもvimを使って来たけどなかなか上達しない方、またはこれからvimを覚えようとされているvimビギナーの方に送る、vimの使い方を上達させる5つのオキテ。

キーボードは見るな

まずは基本。要はブラインドタッチしろという意味ですが、なぜvimのオキテなのかというと...
vimは頭で考えるテキストエディタです。ブラインドタッチが出来ていないと、vimに命令を与える際に思考を止めてしまい学習出来なくなります。以下のオキテをブラインドタッチよりも先に学習してしまう事は、ある意味無駄な学習ともなり得ます。

マウスを触るな

vimに限らずU*IX系のテキストエディタでは、マウスを使わず全ての編集が出来る様になっています。
マウスを探す事で思考を止めてしまい、せっかく思いついたかもしれない素晴らしいアイデアを自ら消し去ってしまうかも知れません。
海外の方がvimを触っている動画を幾らか見たことがありますが、彼らは常に喋るようにタイプしコタツの上にあるテレビのチャンネルを探す様な感覚でテキストを検索しています。
vimを上達させる為には見たかったテレビ番組の内容を喋りながらもチャンネルを変えられる...くらいの当たり前さをテキスト編集スキルとして身に着けなければなりません。

脳内で英文を作れ

vimは基本的にカウント、モーション、オブジェクトという3つの要素を用いてvimに命令することで編集を行います。
例えば行を3行消すのに、SHIFTキーを押しながら3回矢印キーを押してDELキーを押す...という通常のテキストエディタとは違い、「消す 3 下」と言った少し英語に似た脳内文章を自分で表現しvimに命令しなくてはなりません。この脳内文章を如何に効率良く作れるかがvim上達への近道となるのです。
現在のカーソル位置続く単語3つを消して別の物に置き換えたい場合、vim使いならば脳内で「変える 3 単語」という文章が出来上がり「c3w」とタイプされるのです。

短いショートカットキーを知れ

良く出来たテキストエディタでは、良く登場する入力方法を予め決められたキーボードショートカットとして割り当てられています。
例えばプログラムのソースコードを編集中に、カーソルがある次の行から新たな行として入力を開始したい場合、通常のテキストエディタならば<End>を押して行末まで移動し<Enter>を押して入力を開始しなければなりませんが、vimだとノーマルモードから「o」だけ。
この辺りの気配りさが如何にもコード書き専用エディタと言われる由縁なのかもしれません。

人のテクニックを盗め

これはテキストエディタに限らず、何にでも言える話ですがオープンソース界隈では自らの設定ファイルまでも公開したがる人がワンサカいます。
まぁ私もその一人ですが...
vimは一人で「:help」から勉強するには多すぎる程のノウハウが詰まったテキストエディタです。自分で思い付かなかった素晴らしいテクニックは、ありがたくら盗んでしまいましょう。

vimはどれ程使い込んでも満足する事が出来ないテキストエディタです。
どんなに上達しても時に人の設定ファイルや編集方法に驚かされたりします。
頭で考えるテキストエディタだからこそ、人それぞれの編集方法が生まれるのでしょうね。
vimを使い始めてみようと思われている方、このどっぷりと深い世界にのめり込んで見ませんか?
Posted at by




意外と知られていないんですね。ビジュアル選択って
vimで選択範囲を置換
うわーん。これやりかたかったんだよー!知らなかったよー!
Powered by Vim 同じネタを説明しても面白くないので、今日はビジュアル選択後に行うアクションについて...
「'<'>」の後には、「s(substitute)」だけでなく「g(global)」や「v(vglobal)」を書く事もでき、行単位でのビジュアル選択(正式にはlinewise-visual選択)を行った行に対して絞込みを行い、その上で置換を行う事も出来ます。
例えば 問題
※以下の阿藤について間違っている物に×を入れよ
(  ) 俺は阿藤会だ
(  ) 俺こそ阿藤下位だ
(  ) 僕も阿藤回だ
(  ) リッチに阿藤買いだ
(  ) 実は私の従兄弟が阿藤快だ
(  ) 叔父が阿藤飼いだ
(  ) 海で阿藤貝を拾った
(  ) お前、阿藤甲斐性あるな
こんなテキストならば、「(  )」が付いている行を選択して
:'<,'>v/阿藤快/s/(  )/(×)/g
でおしまい。
※「:」を押した時点で「'<,'>」は補完されます。
意味は、ビジュアル選択している部分から「v」で「阿藤快」の含まれない行を抜き出し、その結果に対して「s」で「(  )」を「(×)」に置換するという物です。

また、例えばテキストファイルに書かれた以下の様なスケジュール一覧があったとします。
予定表
1. 09:00 出社
2. 10:00 会議(午前の部)
3. 12:00 昼休憩
4. 13:00 会議(午後の部)
     ここで仕様を煮詰める
5. 16:00 内部ミーティング
6. 16:30 資料作成
7. 17:30 客先にて打ち合わせ
昼休憩の後に項目番号4として「13:30 来客予定」を入れたくなったらどうしますか?
4から7までを一つずつ足して行きますか?
vimなら4で始まる行から7で始まる行までを選択して
:'<,'>g/^\d/exec "normal 0\<c-a>" とすれば4以降が1個ずつずれるので、5の上から4で書き始めればよいのです。
vimではノーマルモード時、数値の上で<c-a>を押すと数値がインクリメントされる(<c-x>でデクリメント)という機能があるので、これを利用して先頭行の数字に対して<c-a>キーを送信しています。

さらに会議(午後の部)の開始が1時間が遅れるとなった場合、「13:00」を含む行から「17:30」を含む行まで選択して
:'<,'>g/^\d/s/\(\d\d\):/\=printf("%02d:", submatch(1)+1)/ でおしまい。
先頭が数字で始まる行に対して「s」で「数値+数値+":"」を検索し値に1足し、printfでゼロ付き文字にして置換しています。
※printf()はvim7でしか動きません。
少し工夫すれば30分足して60分になった物は1時間繰り上げる...なんて事も出来るでしょうね。

vimってパズルみたいで面白いですよね。方法はこれだけでなく、人によっては私よりも手数の少ない方法を使われる方もいます。
凝ると色んな事が出来ますので、皆さん凄いの見つけたら教えて下さいね。
Posted at by




今まで誰にも見せた事無かったですが...
出しちゃいます。

/dotfiles/vim/mattn-vimrc - CodeRepos::Share - Trac
/dotfiles/vim/mattn-gvimrc - CodeRepos::Share - Trac
正直デカイです。

もう随分昔からあるvimrcなので使ってない機能もあれば、忘れてしまったノウハウもいっぱい...
あまり参考にならないかもしれませんが、どうぞ。

mattn the vimmer!

追記
マルチプラットフォーム用です。
Posted at by




色んな人のvimrcを見ていて意外とmapされていないのがこの技。
元はと言うとKoRoNさんから教えて貰った技で、先日公開した私のvimrcにも入っています。
" expand path
cmap <c-x> <c-r>=expand('%:p:h')<cr>/
" expand file (not ext)
cmap <c-z> <c-r>=expand('%:p:r')<cr>
これを使って例えば # vi /path/to/file/file.txt
type some text...
:pwd
/path/to/file
な状態で、ファイルのある場所にカレントディレクトリを移したいならば :cd <C-X>  <= Ctrl押しながらX とコマンドモードで<C-X>をタイプすれば :cd /path/to/file/ と補完されます。あとはENTERで移動。
結構便利だったりします。またコマンドモードで<C-Z>とすれば編集中のファイルへの絶対パスに補完されますので、例えば編集中のHTMLファイルをFirefoxで確認したいならば :!firefox-remote <C-Z>ENTER で簡単に確認出来ます。書きはじめたjavascriptを試すにはいいですね。
※Windowsの場合はvimrcで「set shellslash」してあり、かつfirefoxまでのパスが通っている状態ならば「:!firefox <C-Z>」で行けるかもしれません。(確認してません)

応用として、例えばまだインストールしていないGreaseMonkeyスクリプトをvimで閲覧/編集中に「:!firefox <C-Z>」すれば簡単にインストール出来てしまいます。UN*XユーザでいちいちファイラからFirefoxにjsファイルをドロップ...なんてメンドクサイですよね。特にcoderepos等のCVS/SVNリポジトリでグリースモンキーを見つけ中身をvimで確認してすぐさまインストールしたい!なんて時には大活躍です。
おそらく他にも使い道は沢山あるかと思います。
皆さんもvimrcに加えてみては如何でしょうか。
Posted at by




vimを使っていて開いているバッファ全てからあるキーワードを検索したい場合、「vimgrep」コマンドを使っています。開いているファイルの拡張子を指定して :vimgrep /sometext/ *.c *.h
と実行したり、実際開いているファイル名を羅列したりする事があります。検索結果の一覧もクイックフィックス「clist」で確認出来ます。
でもこれだと保存していないと結果に現れませんし、幾ら拡張子で絞っても余計なファイルがマッチしてしまう可能性があります。

今日のtipsはvim-dev MLに流れたスレッドからご紹介

「vimgrep」を実行するとクイックフィックスが作成されますが、このクイックフィックスに検索結果を追加する「vimgrepadd」というコマンドがあります。
これを全てのバッファに対して実行するように「bufdo」コマンドを絡めます。
:bufdo vimgrepadd /sometext/ %
なるほどね...

ちなみにスレッドの中で「クイックフィックスをクリアするには単にcexpr ""でいける」とBram Moolenaar氏が書いてます。

見えてるバッファだけで実行する場合は「bufdo」の代わりに「windo」が使えますね。
Posted at by




http://www.hsbt.org/diary/20071022.html#p02
だれか indent たのむ - HsbtDiary (2007-10-22)
頼まれた!
って訳ではないですが...

vimは数多くのファイルフォーマットに対応しており、通常扱うファイルであれば、ほぼ全てサポートしているんじゃないかという位、多くのファイルフォーマットに対応出来ています。
どれくらい凄いかと言うと...
gvimの「シンタックス(S)」メニューを展開すると画面がいっぱいになるくらいです。
vim_ff

さて、VBでのインデントですが、例えばVB6の以下の様なソース Private Sub Class_Initialize()
Debug.Print "あめんぼ赤いな"
        If Me.strValue = "" Then
    Debug.Print "エラー!"
End If
End Sub
Private Sub Class_Terminate()
Debug.Print "あいうえお"
End Sub
こんなに崩れたファイルでも、vimなら「gg=G」でおしまい。 Private Sub Class_Initialize()
    Debug.Print "あめんぼ赤いな"
    If Me.strValue = "" Then
        Debug.Print "エラー!"
    End If
End Sub
Private Sub Class_Terminate()
    Debug.Print "あいうえお"
End Sub
例えば、以下の様なJavaのソースファイルでも public class Test {
            public Test() {
                System.out.println("コンストラクタ");
            }
public void doPhpSpot() {
//ここでは何もしない
}
}
上のコマンドで public class Test {
    public Test() {
        System.out.println("コンストラクタ");
    }
    public void doPhpSpot() {
        //ここでは何もしない
    }
}
あらキレイ!
例えばファイル全体でなく、一部分だけインデントを修正したい場合にはビジュアル選択(V押下後にjk移動)した後で「=」とすれば、部分的にインデントし直されます。

どうですか!vim使いたくなりませんか!

mattn often say sales hype.
Posted at by




昨日の記事「Publish::Jaikuをでっちあげた」でご紹介したソースコードは、最終的にはCodeReposにcommitする事にしました。
で、その際CodeReposのトップページで読んだコミットルール
Commit messege rule
svn ci -m "lang/LanguageName/BigProjectName: Commit messege."
svn ci -m "lang/LanguageName/misc/ScriptName: Commit messege."
svn ci -m "dotfiles/SoftwareName/Username-Filename: Commit messege."
を守ってcommit時にファイル名を一覧しました。
で、悩んだのが複数のファイルをcommitする場合。
色々な人のcommit logを見てたら、皆さん lang/LanguageName/BigProjectName,
lang/LanguageName/misc/ScriptName,
dotfiles/SoftwareName/Username-Filename:
  Added.
といった書き方をされていました。
でもこれってU*NIXなら"pwd"して、コピれば簡単ですが、Windowsの場合は"¥"になったり、部分的にチェックアウトしている場合には"pwd"で取得出来るものはなかったりと不便だったりします。
で、なんか楽出来ないかなと思いまして今回はこのcommit対象ファイル一覧を"svn commit"時のコメントとして出力してくれるvimscriptをご紹介します。
※というか、さっき作りました。

仕組みとしては、"svn info"を実行すると出力される
URL : http://host/root/path/to/dir
Repository Root : http://host/root
という部分の2行の差、「path/to/dir」を取得し、svn commit実行時にエディタに表示されている
--This line, and those below, will be ignored--

A    docs
A    docs/file1.txt
A    docs/file2.txt
A    docs/file3.txt
のファイル名部分の先頭に上記フォルダ「path/to/dir」を付与し path/to/dir/docs,
path/to/dir/docs/file1.txt,
path/to/dir/docs/file2.txt,
path/to/dir/docs/file3.txt:
というコメントを生成します。
あとはこれをFileTypeがsvnの場合に動作するようにautocmdを作ればsvn commit時に
path/to/dir/docs,
path/to/dir/docs/file1.txt,
path/to/dir/docs/file2.txt,
path/to/dir/docs/file3.txt:
 
--This line, and those below, will be ignored--

A    docs
A    docs/file1.txt
A    docs/file2.txt
A    docs/file3.txt
という画面が現れます。カーソルも":"の次の行に移動しますので、そこからコメントを書くと事が出来ます。
ツールは横着から生まれる物ですね!

vimscriptのコードは以下の通り
"=============================================================================
" File: svn_file_comment.vim
" Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
" Last Change: Fri, 12 Oct 2007
" Version: 0.1
"-----------------------------------------------------------------------------
" when editing comment for 'svn commit',
"  it append svn comment like following
"
"   root/path/to/dir/docs,
"   root/path/to/dir/docs/file1.txt,
"   root/path/to/dir/docs/file2.txt,
"   root/path/to/dir/docs/file3.txt:
"   <= cursor
"   --This line, and those below, will be ignored--
"   A    docs
"   A    docs/file1.txt    
"   A    docs/file2.txt    
"   A    docs/file3.txt    
"-----------------------------------------------------------------------------

function! AppendCommitFiles()
  let lstart = search("^--", "n")
  let lend = line("$")
  if line(".") > 1 || lstart != 2
    return
  endif
  let oldlang=$LANG
  let $LANG="C"
  let lines=system("svn info")
  let $LANG=oldlang
  let url=substitute(lines, '.*\nURL: \([^\x0A]*\).*', '\1', '')
  let root=substitute(lines, '.*\nRepository Root: \([^\x0A]*\).*', '\1', '')
  if match(url, root) != 0
    return
  endif
  let basedir=substitute(strpart(url, strlen(root)), '^\/*', '', '')
  let lcur = lstart
  let lines = ""
  let mx = '^\s*[A-Z]\s\+\([^$]\+\)$'
  while lcur <= lend
    let line = getline(lcur)
    if line =~ mx
      let lines .= basedir."/".substitute(line, mx, '\1', '')."\<NL>"
    endif
    let lcur = lcur + 1
  endwhile
  let lines = substitute(lines, '\n.', ',&', 'g')
  let lines = substitute(lines, '\n$', ':&', '')
  call cursor(0)
  let value = getreg("a")
  let type = getregtype("a")
  call setreg("a", lines, "c")
  execute 'normal! "ap'
  call setreg("a", value, type)
  silent! /^$
endfunction
autocmd FileType svn call AppendCommitFiles()
例によって、このソースもCodeReposのコノ辺に置く予定です。

追記
ちょこっと修正
Posted at by




最近Plaggerにハマってるからって、やる事メチャメチャやな...

Plaggerのフィードをvimの「--remote-send」を使って転送するPlaggerプラグインを作りました。
vimには、リモートサーバと言う機能があり以下のオプション/コマンドで別に起動しているvimへコマンド/式/ファイルを送信する事が出来ます。

--remote [+{cmd}] {file} ...
別のサーバへファイルを送信します。
+{cmd}により開く際にコマンドを併用出来ます。
--remote-silent [+{cmd}] {file} ...
--remote と同等ですが、エラー等が発生してもメッセージ出力されません。
--remote-wait [+{cmd}] {file} ...
--remote と同等ですが、送信したvimが終了するまで待機します。
vimで編集した結果をどこかに送るシェルスクリプト等では使えるかもしれません。
--remote-wait-silent [+{cmd}] {file} ...
--remote-wait と同等ですが、エラー等が発生してもメッセージ出力されません。
--remote-tab [+{cmd}] {file} ...
--remote と似ていますが、指定されたファイルを全て別タブで開きます。
--remote-tab-silent [+{cmd}] {file} ...
--remote-tab と同等ですが、エラー等が発生してもメッセージ出力されません。
--remote-tab-wait [+{cmd}] {file} ...
--remote-tab と同等ですが、送信したvimが終了するまで待機します。
--remote-tab-wait-silent [+{cmd}] {file} ...
--remote-tab-wait と同等ですが、エラー等が発生してもメッセージ出力されません。
--servername {name}
リモートサーバを名称で指定します。リモートサーバ一覧を取得するには --serverlist を使用します。
--remote-send {keys}
リモートサーバにキーを送ります。基本的にnormalコマンドで与える引数と同等です。
--remote-expr {expr}
リモートサーバに式を送り、その結果を出力します。
--serverlist
リモートサーバ一覧を出力します。

その他、スクリプトから使える
remote_expr({server}, {string} [, {idvar}])
{server}に対して{string}という式を評価して貰います。
:echo remote_expr("gvim", "2+2") と書くと4が表示されます。
remote_foreground({server})
{server}をフォアグラウンドにします。 remote_expr({server}, "foreground()") と同等の機能ですね。
等がありますので、vimを起動しなくても色々な事が出来ます。

今日は、はてなブックマークから「vim」タグが付いているエントリをvimに転送するPlaggerプラグインを作りました。
確認はWindowsでしかしてませんが、X Windowが動いている環境や、Mac OS X等でも動くかと思います。
※あと、フィードタイトルにエスケープ文字や「<ESC>」等といったvimのキー識別ぽい物があると動かない可能性があります。 package Plagger::Plugin::Publish::Vim;
use strict;
use base qw( Plagger::Plugin );

our $VERSION = '0.01';

use Encode;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'plugin.init' => \&initialize,
        'publish.entry' => \&add_entry,
    );
}

sub initialize {
    my($self,$context) = @_;

    $self->{vim} = $self->conf->{vim};
    $self->{vim} = "vim" if (!$self->{vim});
    $self->{server} = $self->conf->{server};
    if (!$self->{server}) {
        open(IN, "vim --serverlist|");
        $self->{server} = <IN>;
        close(IN);
        chomp $self->{server};
    }
    open(IN, sprintf("%s --servername %s --remote-expr \"&encoding\"|",
        $self->{vim}, $self->{server}));
    $self->{encoding} = <IN>;
    print $self->{encoding}."\n";
    close(IN);
    my $command = sprintf("%s --servername %s --remote-send \"<C-\\\><C-N>:new<CR>i\"",
        $self->{vim}, $self->{server});
    system($command);
}

sub add_entry {
    my($self, $context, $args) = @_;

    $context->log(info => $self->{server});
    my $command = sprintf("%s --servername %s --remote-send \"%s\n\t%s\n<C-W>\"",
        $self->{vim}, $self->{server},
        encode($self->{encoding} || 'utf8', $args->{entry}->{title}),
        encode($self->{encoding} || 'utf8', $args->{entry}->{link}),
    );
    system($command);
}

1;
で、YAMLはこんな感じ
global:
  assets_path: /home/user/plagger/assets
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/t/vim?mode=rss

  - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1

  - module: Publish::Vim
    config:
      #vim: vim7
      #server: GVIM1
実行結果は
vim_plagger

またくだらんもん作ってしもた...
Posted at by




Googleが提供している開発用tagツールGoogle GTags(gtags)をWindowsで動かす手順です。
以下、パッチを上げてますが、接続タイムアウトのalarmを無効化しています。必要だと思われる人は、SIGALRMを別の方法で実装してください。
まず、サイトからsvnで最新ソースを取得します。
C:\TEMP> svn checkout http://google-gtags.googlecode.com/svn/trunk/ google-gtags 次に、以下のパッチを当てます。
Index: regexp.h
===================================================================
--- regexp.h    (revision 57)
+++ regexp.h    (working copy)
@@ -4,7 +4,9 @@
 #ifndef TOOLS_TAGS_REGEXP_H__
 #define TOOLS_TAGS_REGEXP_H__
 
+extern "C" {
 #include <regex.h>
+}
 #include "tagsutil.h"
 
 class RegExp {
Index: configure
===================================================================
--- configure   (revision 57)
+++ configure   (working copy)
@@ -10,7 +10,7 @@
 if [ ! -e "scons/scons.py" ]; then
     pushd scons > /dev/null
     echo "Unpacking scons..."
-    tar xzvf scons-local.tar.gz > /dev/null
+    gzip -dc scons-local.tar.gz | tar xv > /dev/null
     if [[ "$?" == 0 ]]; then
    echo "Done"
     else
Index: gtags.cc
===================================================================
--- gtags.cc    (revision 57)
+++ gtags.cc    (working copy)
@@ -49,6 +49,10 @@
 #include "tagsoptionparser.h"
 #include "tagsrequesthandler.h"
 
+#ifdef WIN32
+# include <winsock2.h>
+#endif
+
 DEFINE_STRING(tags_file, "", "The file containing the tags information.");
 
 
@@ -78,6 +82,11 @@
     return -1;
   }
 
+#ifdef _WIN32
+  WSAData wsadata;
+  WSAStartup(MAKEWORD(2,0), &wsadata);
+#endif
+
   logger = new StdErrLogger();
 
   tags_request_handler = new TagsRequestHandler(GET_FLAG(tags_file),
@@ -90,4 +99,9 @@
 
   delete tags_request_handler;
   delete logger;
+
+
+#ifdef _WIN32
+  WSACleanup();
+#endif
 }
Index: gtags.py
===================================================================
--- gtags.py    (revision 57)
+++ gtags.py    (working copy)
@@ -175,11 +175,11 @@
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      address = socket.getaddrinfo(host, port, socket.AF_INET,
                                socket.SOCK_STREAM)
-     signal.signal(signal.SIGALRM, alarm_handler)
-     signal.alarm(CONNECT_TIMEOUT)
+     #signal.signal(signal.SIGALRM, alarm_handler)
+     #signal.alarm(CONNECT_TIMEOUT)
      s.connect(address[0][4])
-     signal.alarm(0)
-     signal.alarm(DATA_TIMEOUT)
+     #signal.alarm(0)
+     #signal.alarm(DATA_TIMEOUT)
 
      # need \r\n to match telnet protocol
      s.sendall(command + '\r\n')
@@ -191,7 +191,7 @@
      while data:
        buf.write(data)
        data = s.recv(1024)
-     signal.alarm(0)
+     #signal.alarm(0)
      return buf.getvalue()
 
 # Instance of connection_manager that forwards client requests to gtags server
Index: tags_logger.h
===================================================================
--- tags_logger.h   (revision 57)
+++ tags_logger.h   (working copy)
@@ -67,6 +67,7 @@
   }
 };
 
+#undef ERROR
 const int INFO = 0, WARNING = 1, ERROR = 2, FATAL = 3, NUM_SEVERITIES = 4;
 
 // uncomment out the standard google logger
Index: socket_server.cc
===================================================================
--- socket_server.cc    (revision 57)
+++ socket_server.cc    (working copy)
@@ -18,10 +18,14 @@
 
 #include <assert.h>
 #include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <unistd.h>
+#ifdef WIN32
+# include <winsock2.h>
+#else
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
+# include <unistd.h>
+#endif
 #include <string>
 
 #include "tagsprofiler.h"
@@ -29,6 +33,12 @@
 #include "tagsrequesthandler.h"
 #include "socket_server.h"
 
+#ifdef WIN32
+typedef int socklen_t;
+#define write(x, y, z) send(x, y, z, 0)
+#define close(x) closesocket(x)
+#endif
+
 extern GtagsLogger* logger;
 
 DEFINE_INT32(tags_port, 2222, "port to tags server");
次にgoogle-gtagsのソースルートにregex for win32を解凍します。これで、gnu_regex_distというフォルダが出来ます。
zsh for win32等をお持ちの方ならば、そのまま sh configure
お持ちでない方でもconfigureの中身を見ると大体検討が付きます。
Makefile.w32は以下の通り。mingw32-makeでビルドします。
all: gtags.exe

gtags.exe : filename.cc gtags.cc sexpression.cc strutil.cc symboltable.cc tagsoptionparser.cc tagsprofiler.cc tagsrequesthandler.cc tagstable.cc socket_server.cc gnu_regex_dist/regex.c
    gcc -DHAVE_STRING_H -c -I. gnu_regex_dist/regex.c
    gcc -I. -Ignu_regex_dist -o gtags.exe filename.cc gtags.cc sexpression.cc strutil.cc symboltable.cc tagsoptionparser.cc tagsprofiler.cc tagsrequesthandler.cc tagstable.cc socket_server.cc regex.o -lstdc++ -lws2_32
あとはspiritlooseのはてなダイアリー - [Vim]Google Tags(GTags)を試してみた(with Vim)を参考に lang_call_to_server = {
  "c++" : { "definition" : [("localhost", 2222)],
            "callgraph" : [] },
  "java" : { "definition" : [],
             "callgraph" : [] },
  "python" : { "definition" : [],
               "callgraph" : [] } }
等と設定して、vimrcに exec "set runtimepath+=".escape(globpath(&runtimepath, 'gtags'), ' ')
nmap <C-]> :call Gtag(expand('<cword>'))<CR>
を追加します。
これで設定はOKです。次に以下の手順でソースツリーでtagsファイルを生成します。
python c:/temp/google-gtags/gentags.py --etags=c:/emacs/bin/etags.exe --rtags=c:/temp/google-gtags/rtags.py --etags_to_tags=c:/temp/google-gtags/etags_to_tags.py ここではetagsとしてemacsに含まれるバイナリを使用しましたが、ctags.exeをetags.exeにリネームしても同様に使えます。
これで「cpp.tags.gz」というファイルが生成されますので、あとはサーバを起動します。
c:/temp/gtags.exe --tags_file ./cpp.tags.gz --tags_port 2222 --gunzip 起動したらvim(gvim)を起動して、タグジャンプしたい部分でC-]します。
spiritlooseさんの言うように確かに速いですね。共同開発等では便利かもしれませんね。

オフトピですが、このライブラリに含まれるSconsというビルドツールについて今度調べてみようかと思います。
Posted at by




vimにはexplore.vimというスクリプト(現在はnetrw.vimに統合)が付属しており # vim /usr/include 等と実行すると、vimがファイラとして起動します。同様にコマンドラインから :e /usr/include と実行しても同じ結果になります。コマンド単体としてもExploreとして起動出来ます。
このExplore実は結構よく出来ていて、以前ご紹介した「男は黙ってvimでリモート編集」の応用として :e ftp://ftp.vim.org/pub/vim/
Enter username: anonymous
Enter Password: *********
でフォルダ閲覧出来ます。(*********はanonymous) " ============================================================================
" Netrw Directory Listing                                        (netrw v109)
"   ftp://ftp.vim.org/pub/vim/
"   Sorted by      name
"   Sort sequence: [\/]$,\.h$,\.c$,\.cpp$,\.[a-np-z]$,*,\.info$,\.swp$,\.o$\.obj
"   Quick Help: <F1>:help  -:go up dir  D:delete  R:rename  s:sort-by  x:exec
" ============================================================================
../
./
MIRRORS
README
amiga
atari
be
beanie.gif
doc
extra
faq.html
farsi
green_ball.gif
index.html
ftp://ftp.vim.org/pub/vim/ [RO]                                       1,1     2%

また、Exploreではファイル名にカーソルを合わせて「x」をタイプすると拡張子に合わせてアプリケーションが起動します。
例えばWindowsでファイル名が「勤務表.xls」であればExcelが起動します。

この「x」で外部アプリケーションが起動する機能、現状はWindows、GNOME、KDEをサポートしています。
ちょっとソースを見たところ、netrw.vimには元となったexplore.vimに昔々に私が入れ込んだ「explFileHandler」が別名「netrwFileHandlers」として取り込まれてました。
ただ、「netrwFileHandlers」は私が元々想定していた単一のユーザ関数ではなく「netrwFileHandlers#Invoke」という関数でファイル種別毎に分別され、ファイル種別毎のスクリプト関数が実装されていました。
これをグローバルで宣言すれば自分独自の設定も出来るという仕組みです。 let g:netrw_browsex_viewer='-'
" エディタであるvimから秀丸起動して、何やってんだか...
function! NFH_txt(file)
    " netrwFileHandlers.vimの不具合回避?
    let f = substitute(a:file, '^\([A-Z]\)COLON', '\1:', '')

    exe "silent !start c:/progra~1/hidemaru/hidemaru.exe \"".f."\""
    return 1
endfunction

こんな感じのユーザ関数を作れば例えば.plや.shでperlやbashを起動したりする事も出来ます。
コード内にある「netrw_browsex_viewer」ですが、"-"に設定すると上記のようなユーザ/スクリプト関数を呼び出す機能として動作しますが、実行可能なコマンドを設定するとそのまま起動してくれるようにもなっています。
これを使用すれば、現状Windows、GNOME、KDEしかサポートしていないnetrw.vimでも、Mac OS Xに対応する事が出来ます。
私はMac OS Xを持っていないので確認出来ませんが、MacWiki - OSXの固有コマンドを見ると、Mac OS Xではコマンドラインからファイルを開く「open」コマンドがあるらしいので let g:netrw_browsex_viewer = 'open'
とvimrcに設定しておけば、Exploreから「x」をタイプする事でファイル種別に応じたアプリケーションが起動出来るかと思います。
※どなたか動作報告頂ければ、オフィシャルにマージして貰えるかもしれません。

その他、netrw.vimが使用するftp/sshのコマンドライン等の設定は :NetrwSettings
とすれば、設定画面が表示されますので、色々カスタマイズして見ると面白いかもしれませんね。

mattn the vim explorer
Posted at by




vimを使っていらっしゃる半数以上は開発者の方かと思います。
今日は、そんな開発者の方に便利なtipsをご紹介。

Makefile編集中のファイル名補完

一般的なファイルシステムでは、ファイル名に「=」という文字を使うことが出来ます。ただ、実際に使っているかどうかといえば、めったに使われる事はありません。なのに TOP_DIR=/usr/inc
まで打って<c-x><c-f>とタイプしてもファイル名が補完されないのは、悲しかったりします。 こんなときには以下の設定をvimrcに入れておくと便利です。
autocmd FileType Makefile setlocal isfname-== isfname+=32
通常、フォルダ名やファイル名に「=」やスペースを使う事はまずありませんし、*unix系の開発を経験された事がある方ならば、スペースが混じってしまう事は一大事になる事もご存知かと思います。
この設定だと、Makefileにだけ適応されますし補完もサクサク出来ますね。
ちなみにWindowsで「C:/Program Files/hogehoge」を補完されたいならば TOP_DIR="C:/Progra
と「"」を先頭にしておく事で補完出来ます。
もしかすると、*unixな方は「I」や「L」もisfnameから消してしまう事で CFLAGS=-I/usr/inc
とか LDFLAGS=-L/usr/li
でも補完出来て幸せかもしれません。

長いブロックや複数ブロックがある言語の開発

jsp(JavaServerPages)やASP(ActiveServerPage)、php等で開発される場合には、開始タグで各言語の処理を開始します。例えばphpであれば <?php
$f = fopen("test.dat", "r");
と「<?php」が開始タグとなります。
vimでは各言語に合わせてシンタックスハイライト表示する機能を持っていますが、この開始/終了タグを辿って色付けをしています。この開始/終了タグの検索は、最悪ファイル全体を検索してしまい重たくなってしまう可能性がある為、vimでは閾値範囲内しか検索しないようになっています。
ただ、全ての言語でこの閾値が相応しいものとは限らず、まれに長いブロックが存在するとシンタックスハイライトが効かなくなってしまう事もあります。ここではこの閾値の変更方法を説明します。
閾値は、「syntax sync 」コマンドの「minlines」という値で設定でき、以下のようにautocmdで設定します。
autocmd FileType jsp,asp,php,xml,perl syntax sync minlines=500 maxlines=1000
私は上記の設定を使っています。

pythonのインデントをタブではなくスペースにする

これはvimユーザでpythonの開発をされる方であれば、これは必須ですね。
以下の設定はタブ幅4で、expandtabにしています。
autocmd FileType python setlocal ts=4 sw=4 sta et sts ai
ただ、まれにスペース2つの人とかもいますから、人のソースを修正する際には向かないかもしれませんね。

Emacsユーザに送るキーバインド

これはおまけ。
nmap <m-x> :
nmap <silent> <c-x>1 :only<cr>
nmap <silent> <c-x>2 :sp<cr>
nmap <c-x><c-w><c-w>
cmap <m-x> <nop>
もっとこだわりたい方は、vimacsの方をお勧めします。

追記1:リンクが間違ってました
追記2:KoRoN氏の指摘で「BufEnter」から「FileType」に修正(コピペ元がマズってました)
追記3:さらにKoRoN氏の指摘で「FileType」のpatternが間違ってるとの指摘で修正(もしかしたらBufEnter使ってたのは拡張子使いたいからだったのかな?覚えてないや)

mattn the Onsen Ikitai!
Posted at by




vimscriptの発祥から考えると、vimで扱えるオブジェクトはあくまで数値、文字列レベルの物でしかないと思われがちですが、Dictionaryとfunction()を使えば、それとなく継承ぽい事が出来ます。
まぁ、javascriptに近い言語仕様というのもありますからね。
以下のコードでは、簡単なクラスとオブジェクトを定義しています。
function! Class_Prototype() dict
  return self
endfunction

function! Class_Override(...) dict
  if a:0 == 0|throw "Invalid Parameter"|endif
  let class = copy(self)
  let class.__NAME__ = a:1
  if type(a:2) == type(class.New)
    let class.New = a:2
  else
    let class.New = self.New
  endif
  let class.Super = self
  return class
endfunction

function! Class_New(...) dict
  let instance = copy(self)
  call remove(instance, "New")
  call remove(instance, "Override")
  let instance.Super = self
  return instance
endfunction

function! Class_ToString() dict
    return self.__NAME__
endfunction

let Object = {
 \ "__NAME__" : "Object",
 \ "Prototype"function("Class_Prototype"),
 \ "Override"function("Class_Override"),
 \ "Super"{},
 \ "New"function("Class_New"),
 \ "ToString"function("Class_ToString")}
この状態で if exists("object")|unlet object|endif
let object = Object.New()
echo object.ToString() . ":..."
とすると Object:... と表示されます。

ここで function! Human_Sing() dict
  return self.perfix . "は" . self.name . "。" . self.title
endfunction
function! Human_New(...) dict
  let instance = copy(self)
  let instance.perfix = a:1
  let instance.name = a:2
  let instance.title = a:3
  let instance.Sing = function("Human_Sing")
  return instance
endfunction
let Human = Object.Override("Human", function("Human_New"))
とすると、Objectクラスを継承するHumanクラスを定義する事が出来ます。
さらにこの状態で if exists("human")|unlet human|endif
let human = Human.New("私", "人間", "一般人")
echo human.ToString() . ":" . human.Sing()
とすると Human:私は人間。一般人 と表示されます。
つまり、Newメソッドをオーバーライドし、Singメソッドを追加した事になります。ToStringメソッドはObjectクラスのメソッドとなります。
vimscriptはJavascriptのように、メンバを動的に生成出来ますので
function! Gian_Boxing(who) dict
  return a:who . "のくせに生意気だぞ!!!"
endfunction
let Gian = Human.Override("Gian", {})
let Gian.Boxing = function("Gian_Boxing")
if exists("gian")|unlet gian|endif
let gian = Gian.New("俺", "ジャイアン", "ガキ大将")
echo gian.ToString() . ":" . gian.Sing()
echo gian.Boxing("のび太")
とすると Gian:俺はジャイアン。ガキ大将
のび太のくせに生意気だぞ!!!
とジャイアンが生成出来ます。
意外とやれるもんですね。

ただ、せっかくローカルスコープ、スクリプトスコープ、グローバルスコープ等、名前空間は既にしっかり存在してるんだから、もう少し他のオブジェクト指向言語(JavaやJavaString、C#やVB.NET)のように既定メソッドとかデフォルトコンストラクタみたいな概念が欲しいなぁ...
あと関数名は先頭大文字強制ってのは痛い。
ま、その辺はまた今度...

mattn the vimscripter
Posted at by




vimのtipsに何かいいのがないか探してたら...
:help scroll-smooth なんてのを発見。

中身みたら...
==============================================================================
Smooth scrolling                                        *scroll-smooth*

If you like the scrolling to go a bit smoother, you can use these mappings: >
        :map <C-U> <C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y>
        :map <C-D> <C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E>

(Type this literally, make sure the '<' flag is not in 'cpoptions').

==============================================================================
と...

まぁスムースですわな。
書いた奴出て来い
Posted at by




コマンドモードで :help! 結果↓ E478: 慌てないでください!
tipsというよりイースターエッグかな...
Posted at by




vim_html 普段vimを使っていらっしゃる方は、ソースコードや/etcにある設定ファイル等をブログに書きたいと思う事が多いかと思います。
私のブログでもほぼ7割方ソースコードが含まれるブログ記事となっています。

ソースコードをブログにアップする際、やはりテキストエディタの様にシンタックスに色が付いた常態だと分かりやすいですよね。
世の中には色んな方法があるようです。

ただ、私としてはやはり静的ファイルでHTMLを出力したいし、なるべく多くのフォーマットで、かつマルチバイトに対応していてほしい。
私は普段テキストエディタとしてvimを使っていますが、vimのソースコードハイライト機能はタダモノではなく、かつ対応しているファイルフォーマットの数も :echo len(split(globpath(&rtp, "syntax/*.vim"),"\n"))
とするだけでも500個以上のファイルフォーマットに対応している事が分かります。
さらにvimのオフィシャルサイトに行けば世の中に存在するプログラミング言語のほぼ全てのsyntaxファイルが揃うでしょう。
vimにも、HTML出力の機能が無いわけではありません。「tohtml.vim」を使えば現在ハイライトされているバッファの中身をHTMLファイルにしてくれる機能があります。
#include <stdio.h>

int main(int argc, char* argv[]) {
    printf("Hello, World\n");
    return 0;
}
こんなファイルであれば :TOHtml
とする事で <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>C:/temp/helloworld.c.html</title>
<meta name="Generator" content="Vim/7.1">
</head>
<body bgcolor="#000000" text="#c0c0c0"><font face="monospace">
<font color="#ff6060">#include </font><font color="#ff40ff">&lt;stdio.h&gt;</font><br>
<br>
<font color="#00ff00">int</font>&nbsp;main(<font color="#00ff00">int</font>&nbsp;argc, <font color="#00ff00">char</font>* argv[]) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;printf(<font color="#ff40ff">&quot;Hello, World</font><font color="#008080">\n</font><font color="#ff40ff">&quot;</font>);<br>
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#00ffff">return</font>&nbsp;<font color="#ff40ff">0</font>;<br>
}<br>
</font></body>
</html>
こんなHTMLを出力してくれます。
ただ...<font>はいただけませんね。

最近のHTMLでは御法度です。HTML Validatorも許しません。HTMLに直接色を埋め込むなんて最近ではタブー化しつつあります。(このサイトには沢山ありますが...)

今回は、「tohtml.vim」から実行される「2html.vim」を使用してブログに最適なHTMLを出力出来るコツを説明して行きます。
「2html.vim」は基本動作を変えられるように複数のオプションを持っています。以下そのオプションについて説明します。

use_xhtml

出力するHTMLをXHTMLに強制します。たとえば<br>は<br />と出力されます。
また<DOCTYPE>も合わせて変更されます。

html_number_lines

HTMLを出力する際に行番号を出力してくれます。ただし同一要素内に出力されますのでマウスで選択すると行番号まで選択されてしまいます。

html_font

HTMLのフォントを強制出来ます。通常はmonospaceが使われますが、これよりも優先したいフォントがある場合に使用します。

html_use_css

コードのシンタックスを<font>ではなく<span>で出力します。
class属性は上記「Comment」や「Statement」といった識別が使用されます。

html_use_encoding

metaタグでcharset指定できます。但し現状の「2html.vim」にはencodingが「cp932」の場合の処理がありませんので、以下のパッチを「2html.vim」に当てる必要があります。
*** 2html.vim   Fri Aug 03 13:23:20 2007
--- 2html.vim.org   Tue Jul 31 09:48:00 2007
***************
*** 162,168 ****
      let s:html_encoding = 'iso-8859-1'
    elseif s:vim_encoding =~ "^cp12"
      let s:html_encoding = substitute(s:vim_encoding, 'cp', 'windows-', '')
!   elseif s:vim_encoding == 'sjis' || s:vim_encoding == 'cp932'
      let s:html_encoding = 'Shift_JIS'
    elseif s:vim_encoding == 'big5'
      let s:html_encoding = "Big5"
--- 162,168 ----
      let s:html_encoding = 'iso-8859-1'
    elseif s:vim_encoding =~ "^cp12"
      let s:html_encoding = substitute(s:vim_encoding, 'cp', 'windows-', '')
!   elseif s:vim_encoding == 'sjis'
      let s:html_encoding = 'Shift_JIS'
    elseif s:vim_encoding == 'big5'
      let s:html_encoding = "Big5"
これでWindowsを使っていらっしゃる人でも「charset=Shift_JIS」と出力されます。

html_no_pre

<pre>を使わずHTML出力する際に使用します。最近はRSSで全文配信する風潮もあるみたいで、その際に<![CDATA[...]]>使いたくない人には有用かもしれません。

html_start_line

ソースからHTMLを生成する際の開始行番号を指定します。

html_end_ilne

ソースからHTMLを生成する際の終了行番号を指定します。

html_ignore_folding

ソースコードがfoldingされていても内部をHTML出力するように設定します。
設定せずにfoldingされているソースをHTML出力すると以下のようなHTMLが出力されます。
#include <stdio.h>

int main(int argc, char* argv[]) {
+---  2 行:printf("Hello, World\n");
}

html_whole_filler

foldingモード時に、追加された行を意味する行「... inserted lines」を表示したく無い場合に設定します。

以上が「2html.vim」で使用できるオプションです。
通常は何も設定されていませんが、以下ではブログ等に貼り付ける際の私なりのコツを示します。
  • スタイルシートを定義する
  • CSSファイルやHTMLファイルの<style>部に以下のコードを追加しておきます。
    .code {
     border-bottom    : 1px solid #777777;
     border-left      : 5px solid #777777;
     border-right     : 1px solid #777777;
     border-top       : 1px solid #777777;
     background       : #555555;
     color            : #ffffff;
     overflow-x       : auto;
     white-space      : nowrap;
     font-family      : monospace;
    }

    /* source code */
    .code>.Comment {
     color: #aaaaaa;
    }
    .code>.Constant,
    .code>.String,
    .code>.Character,
    .code>.Number,
    .code>.Boolean,
    .code>.Float {
      color: #aa7777;
    }
    .code>.Identifier,
    .code>.Function {
     color: #77aa77;
    }
    .code>.Statement,
    .code>.Conditional,
    .code>.Repeat,
    .code>.Label,
    .code>.Operator,
    .code>.Keyword,
    .code>.Exception {
     color: #77aaaa;
    }
    .code>.PreProc,
    .code>.Include,
    .code>.Define,
    .code>.Macro,
    .code>.PreCondit {
     color: #aaffff;
    }
    .code>.Type,
    .code>.StorageClass,
    .code>.Structure,
    .code>.Typedef {
     color: #aaaa55;
    }
    .code>.Special,
    .code>.SpecialChar,
    .code>.Tag,
    .code>.Delimiter,
    .code>.SpecialComment,
    .code>.Debug {
     color: #777777;
    }
    .code>.Underlined {
     color: #00ff00;
     text-decoration: underline;
    }
    .code>.Ignore {
     color: #777777;
    }
    .code>.Error {
     color: #ff0000;
    }
    .code>.Todo {
     color: #0000ff;
    }
    .code>.Folded {
     color: #aaffff;
     background-color: #aaaaaa;
    }
    Internet Explorer6ではセレクタが正しく動作しないかと思いますので修正が必要です。
  • オプションをXHTML使用,CSS使用,PRE未使用モードに設定する
  • ~/_vimrc(un*xでは~/.vimrc)に以下を追加します。
    let g:use_xhtml = 1
    let g:html_use_css = 1
    let g:html_no_pre = 1
  • ソースコードをHTML化する
  • 全体をHTML出力するならば何も選択せず、また部分的にHTML出力するならば行選択(linewise-visual)して
    :TOHtml
    と実行します。
    HTMLには<html>や<body>も含まれますので、実際には<body>から</body>を切り取るといいでしょう。

これで色んなソースコードを皆に見てもらうことが出来るようになります。この方法はあくまでmattnが良いと思った方法ですので、もしかしたらより便利な方法があるかもしれません。
一度CSSさえ設定してあれば、上記の手順1つで簡単にHTML出力出来ます。ぜひコードを晒け出してみて下さい。

mattn the OSS fan.
Posted at by




vim.orgを物色してたら
Vimpress : Manage wordpress blog posts from Vim
というvimからWordPressに記事投稿出来るスクリプトを発見。まぁ、だいたい中身も想像出来ましたが物は試しと随分前にアカウントを取ってて全然使ってないWordPress.comのサイトにポストしてみました。 で、予定通りマルチバイトで文字化けしたので、改造してみました。
基本的な修正は文字コード周りですが、カテゴリの取得方法をMTからmetaWeblogに変えて、BXR: Blosxom XML-RPC Interfaceにも対応してみました。
ただここへのポストはまだ試してないです。苦笑
オリジナルがGPL2ライセンスでしたので、今回はpatchで配布します。
ダウンロード:
どうぞ、楽しいWordPress(+vim)ライフをお楽しみ下さい。

追記
使い方はblog.vimの中で宣言しているblog_usernameとblog_passwordを設定し、blog_urlにXMLRPCのエンドポイントを設定します。
あとは :BlogList でリスト取得(ENTERで開く) :BlogNew で新規ブログ、その状態から :BlogSend で投稿です。(リストからENTERで開いた場合は更新です)
Posted at by




まきこみ計画のcozymaxさんに、「blosxom bookmarks plugin」を紹介頂きました。
【Blosxom】bookmarksプラグインを導入

いやぁ我ながら「適当クオリティ」とは恐いものです。コラ
どんどん修正して良い物にしてあげてください。
そしてフィードバックだけはお忘れなく。コラ

御指摘は、ありがたく頂き、速攻でbookmarksプラグインを修正致します。
普段もコマンドプロンプトで生きている程un*x生活が浸透してしまっているので、GUIなftpツールでダウンロードして、修正してアップして...なんて事しませんよ。
vimmerならば、リモート直編集っす。

:e ftp://user@server/path/to/file/file.txt
Enter Password:

ちなみにvim、ftp以外にもscp、http、webdav、rsync、sftp等が使えます。ファイルへのパスが仮想でない分、webdav(URLはdav://)なんか、かなり便利です。
工夫すればsmbclient使って「smb://」にも対応出来るんじゃないですかね。

しかしながら最近、vimのオフィシャルへは一つもパッチを送っていないというありさま。 かろうじてチュートリアルの翻訳を進めているくらいです。
:help mbyte
で出てくるメアドも既に使えません。

シャキっとします。シャキっと。はい。
Posted at by




意外と便利なvimscript、なんとなく今日は底に眠ってそうで実は便利なスクリプトをご紹介。
vimで翻訳出来たら便利かな?なんて思ったりしませんか?
出来ます。

香り屋testdirというフォルダに、excitetranslate.vimというファイルがあります。
これを取ってきて、pluginsディレクトリに放り込むだけ。
ただし作られてから結構古く、exciteのURLも変更されていますので、以下のパッチを当てる必要があります。

*** excitetranslate.vim.orig    Mon Jul 23 16:30:34 2007
--- excitetranslate.vim Mon Jul 23 16:28:52 2007
***************
*** 4,16 ****
  "
  " Maintainer: MURAOKA Taro <koron@tka.att.ne.jp>
  " Author: Yasuhiro Matsumoto <mattn_jp@hotmail.com>
! " Last Change:25-Dec-2021.
  
  if !exists('g:excitetranslate_options')
    let g:excitetranslate_options = 'register,buffer'
  endif
  
! let s:excite_web = 'http://www.excite.co.jp/world/text/'
  
  function! s:CheckEorJ(word)
    let all = strlen(a:word)
--- 4,16 ----
  "
  " Maintainer: MURAOKA Taro <koron@tka.att.ne.jp>
  " Author: Yasuhiro Matsumoto <mattn_jp@hotmail.com>
! " Last Change:25-Dec-2021.
  
  if !exists('g:excitetranslate_options')
    let g:excitetranslate_options = 'register,buffer'
  endif
  
! let s:excite_web = 'http://www.excite.co.jp/world/english/'
  
  function! s:CheckEorJ(word)
    let all = strlen(a:word)
***************
*** 38,43 ****
--- 38,44 ----
    silent! %v/^<textarea /d _
    silent! %v/name="after"/d _
    silent! %s/<[^>]*>//g
+   silent! %s/\r//g
    let line = getline(1)
    silent bw!
    " Remove temporary files
注意)本プラグインを動作させる為には、「Chalice」に含まれる「aliceライブラリ」が必要です。「aliceライブラリ」の最新版は、svnリポジトリから取得出来ます。

あとは、vimのバッファで「This is a pen!」と70年代生まれ小学校1年生レベルの英語を打ち込み :ExciteTranslate とすれば これはペンです! と70年代生まれ小学校2年生レベルの答えが返ってきます。
またexcitetranslate.vimは、内部が英語ぽいか日本語ぽいかで逆の動作もしますので「魔女の宅急便」を訳すと Kiki's Delivery Service と戸田奈津子並みの答えが返ってきます。
Posted at by




前のエントリで気づいたのですが、実はJSONとvimって愛称がよいのでは?と気づきました。
確かに複雑なJSON(例えばif文等を含んだりしているもの)は扱えませんが、JSONPが実行出来るならばメソッド引数にデータ本体が渡る為、簡単なものなら解釈出来る事が分かりました。

例えば、以下のメソッドを定義します。
scriptencoding utf-8
function! JsonHandler(data)
  return a:data
endfunction
function! GetJsonP(url)
  let ret = system("curl -s \"" . a:url . "?callback=JsonHandler\"")
  let org = &enc
  let &enc = "utf-8"
  let ret = substitute(ret, '\\u\([0-9a-zA-Z]\{4\}\)', '\=nr2char("0x".submatch(1))', 'g')
  exe "let val = " . iconv(ret, "utf-8", org)
  let &enc = org
  return val
endfunction
function! GetJson(url)
  let ret = system("curl -s \"" . a:url . "\"")
  let org = &enc
  let &enc = "utf-8"
  let ret = substitute(ret, '\\u\([0-9a-zA-Z]\{4\}\)', '\=nr2char("0x".submatch(1))', 'g')
  exe "let val = " . iconv(ret, "utf-8", org)
  let &enc = org
  return val
endfunction

GetJsonメソッドはURLを指定してJSONオブジェクトを取得する関数です。
はてブのコメント一覧であれば、以下のような処理で取得出来ます。
if exists("json")
  unlet json
endif
if exists("jvar")
  unlet jvar
endif
let json = GetJson("http://b.hatena.ne.jp/entry/json/?url=http://mattn.kaoriya.net/web/hatena/20070726110753.htm")
for jvar in json["bookmarks"]
  echo jvar["user"] . " - " . jvar["comment"]
endfor
実行結果は以下の通り(参考:はてブでアーーーーッ!!!)
yheld - アーーーーッ!!!フヒヒ、サーセンwwww
parkbench -
ku0522 - アーーッ!!
tyoro1210 - アーーーーッ!!!
mattn - 自分でアーーーーッ!!!
また、del.icio.usの場合は通常のJSONにデフォルトのコールバックが含まれてしまいますので、vimでは解釈出来ません。しかしJSONPならばメソッドを指定する事で左記javascriptが消えてくれます。専用にGetJsonPという関数を用意しました。
はてブと同様に if exists("json")
  unlet json
endif
if exists("jvar")
  unlet jvar
endif
let json = GetJsonP("http://del.icio.us/feeds/json/mattn.jp")
for jvar in json
  echo jvar["d"] . " - " . jvar["u"]
endfor
とする事で(参考:mattn.jp in del.icio.us) FlashVillage.com - FREE Flash Templates - http://www.flashvillage.com/
その対応に意味はあるのか (Yak blog) - http://www.greenspace.info/mt/2007/07/31/post_38.html
Top Rated - Free Stock Photos - http://public-domain-photos.com/
Niconorati (ニコノラティ) - ブログで今話題のニコニコ動画 - http://pulpsite.net/niconorati/
SourceForge、「優秀なオープンメ[スプロジェクト」の投票結果を発・- ZDNet Japan - http://japan.zdnet.com/oss/story/0,3800075264,20353786,00.htm
IT戦記 - style.cssText の使い処に関する考察 - http://d.hatena.ne.jp/amachang/20070730/1185788557
void GraphicWizardsLair( void ); // Ustream.tvの凄いところは、Flashで実用的なIRCクライアントを作っちゃったところ - http://www.otsune.com/diary/2007/07/30/1.html#200707301
カイ氏伝: Wikipediaの「RSSリーダー」がとんでもない件 - http://blogging.from.tv/archives/000564.html
美輪明宏のチンコの有無を返すAPI作った | dzfl::blog - http://dzfl.jp/blog/2007/07/29/miwa-mojo-api/
美輪明宏のチンコの有無を配信するRSS作った - PRESS RELEASE on VOX - http://asada.vox.com/library/post/%E7%BE%8E%E8%BC%AA%E6%98%8E%E5%AE%8F%E3%81%AE%E3%83%81%E3%83%B3%E3%82%B3%E3%81%AE%E6%9C%89%E7%84%A1%E3%82%92%E9%85%8D%E4%BF%A1%E3%81%99%E3%82%8Brss%E4%BD%9C%E3%81%A3%E3%81%9F.html
Cookie Manager | Javascript Code | All Things Webby - http://insin.woaf.net/code/javascript/cookiemanager.html
[N] 無料で使える18のアプリケーション&ウェブサービス - http://netafull.net/lifehack/021313.html
flivpee - Google Code - http://code.google.com/p/flivpee/
MOONGIFT: ≫ オープンメ[スのFlashムービープレーヤ「Flivpee」:オープンメ[スを毎日紹介 - http://www.moongift.jp/2007/07/flivpee/
TechCrunch Japanese アーカイブ ≫ Pownceが、ついにAPIを公開 - http://jp.techcrunch.com/archives/pownce-moving-to-open-api-eventually/
といった結果が得られます。
本来ならcallbackもvimscriptを呼ばせるべきかもしれませんね。
vimを使ってマッシュアップアプリを作ってみたい人には、便利なtipsかも知れません。
私はライブラリ化するつもりはありませんが、してみたい人がいるならばぜひvimscriptsに登録してみて下さい。
まぁ、「意外と知られていない」という割には自分も知らなかった訳ですが...

the half of mattn is composed of fondness.
Posted at by



2008/01/30


それは...

チミに足りないものカード
Vimの足りないものカード
by ふりーむ! 無料ゲーム/フリーゲーム
Posted at by