oEmbed + Pipes callback

現状のoEmbed仕様だとcallbackに関する記述がないのと、サンプルとして使える状態になってるflickrのoEmbedレスポンスは改行がエスケープされていないのでそのままではJSONPには使えない。PipesJSON出力は&_callback=your_call_back_nameパラメタを追加するとyour_call_back_name(JSON)を返してくれるので特別なサーバを用意しなくてもこの問題を解決できる。以下は多くのサービスに対応しているoohembed.comにリクエストするpipes.

oohembed filter

urlistでflickrの各写真のページurl(!=画像ファイルの直url)を指定するとそのページの写真を表示できるようになった。
http://urlist.buffr.org/list/22

Monthly activity report: June, 2008

greasemonkey

Twitter - kATakOto SPEAKER
LDR insert HatenaDiary daily image
PipesFullFeed - Insert feed url to Wedata item entry
tako3 search redirect

カタコトスピーカーはちょっとおもしろかった。Twitter - SPEAKERsatoru.net文字音声変換API使ってる部分をYahoo! 形態素解析API+カタカナ辞書+TTS API横取りで置き換えてるだけ。エンドポイントの異なる複数のAPIをシーケンシャルに叩くので初めてJSDeferred使ってみたけど、単体で使えてすっきり書けるのがいいですね。

PipesFullFeedはplaggerとか使えないけど楽して(=全文取得に特別な操作しない)feed読みたい人向けに作ったもの。wedataで必要なパラメタを共有。同じpipesでfeedを作るとfeedタイトルも同じになってしまうのはイマイチな点。

mdtd

http://buffr.org/mdtd
gears + jsだけで動くtodo管理ソフト。一年くらい前に仕事の優先順位をすばやく直観的に決定する方法 - GIGAZINEを見て原型は作ったんだけど使い勝手がイマイチで放置していたものにフィルタ入れたら使う気になれるレベルになった。各タスクのタイトル先頭にタグっぽくlifeとかプロジェクト名とか入れておいて、タスクが多くて嫌なときは見たいタグだけ絞込んで使ってる。
細かく管理するならRTMとかのほうがいいと思う。優先順位を決定するのにふたつのフラグのon/offだけ決めればいいのは楽。
gearsのdbに保存してるからgears入ってないと動かないかわりにオフラインでも動きます。

urlist

http://urlist.buffr.org/
ブックマークとは別に、playlistみたいな少し編集意識があるurlのリストを管理したいと思って作ったもの。sbmとかtumblrとかtwitterの操作に比べると、リスト順序変更やメモ欄の画面が分かれてて入力は面倒なんだけど、内容について少しは考えさせたいのでそうしてる。ブログとか日記までいかないけど、まとめ意識のあるもの。ちょっと面倒なかわりログイン不要で誰でもすぐに使えるようにしてる。自分が作ったリストをurlistでは管理できないけどそういうのは既存のサービス(SBMとか)でできるからやってない。gyazoの手軽さ、既存サービスに無い機能の補助的な使われ方なんかを意識してる。

各種サービス側のエントリURLからoEmbedでリッチなコンテンツ取得するのが広まれば、こういうurlのリストだけでもいろいろできるんじゃないかという狙いもある。現状でもgreasemonkeyの力を借りれば色々できる。

リスト中のurlが全て画像、全てmp3、全てyoutubeの場合はそれぞれのメディアに応じたプレイヤを表示。画像のカタログ表示はIndexhibitを意識してる。特定メディア種別で統一されてない場合のかっこいい表示方法を思いついてないのでそういうのは普通のリスト表示になる。

minibuffer用コマンドもあるからyoutubeで検索してピンして:pinned-link | urlist "hoge"するとurlistのリスト作成画面にまとめて送れる。

あと、リストがたまると新規リスト作成時点で近いものがrelated listとして表示されるはず。入力が検索になる。

黄色はindexhibitのポスターから。

YAPC::Asia 2008

聞いたセッションで気になったものメモ。

  • about Perl5.10(tokuhirom)

dorだけでも使いたい

  • Javascript Love for Perl Hackers(ingy)
    • pQuery::DOMは単体で使える.HTML::Elementを継承
  • Step 3: Prophet - A peer to peer replicated property database(jesse)
    • 小さなDB向き
    • p2p
    • 変更履歴、同期、エラー修正
  • Running Perlish Small Business with Perl(clkao)
    • aiink.com、デザインできるピンバッチや簡単な判子販売
    • OpenID対応
    • ピンバッチのエッジに合わせた文字曲げ加工はCairoのモジュール
    • コンビニ決済用の印刷画面
    • mozilla2PS(pdf), xulrunner, Xvfb
    • RTで帳簿
    • バーコード画像生成モジュール(Business::TW::*)

best practicalの面々は小さいビジネスを効率よくやる、というのに注力している印象。フルスタックフレームワーク作ったり小さなビジネスっていう点が37 signalsに似てる。

  • You're Doing OO Wrong(Schwern)
    • isa -> x, hasa -> o
    • inheritance -> x, Roles -> o

Moose!

  • Perl Loves Javascript Hackers(ingy)
    • Makefileでホームページをつくる
    • configはYAMLに。静的ファイルに埋め込むデータとjsonで使うデータ。それぞれperlで変換(TT::process & JSON::Dump)
    • jemplateはTemplateToolkitで使ってるテンプレートそのまま使える
    • textarea埋め込み系(TrimPath等)と違ってjemplateはajaxでテンプレート取得すればブラウザキャッシュされる
  • Improving your Catalyst application(jrockway)
    • DBICx::TestDatabase
    • ロジック単体テスト, schemeとresultsetを引数で渡す
    • Catalyst:Model::Factory::PerRequest, $c
    • 似たようなコントローラはuse base "Catalyst::Controller"したクラスにまとめる
    • MapMaker(entity-linktable-tag) じきCPANにアップする
    • App::TemplateServer
  • Gungho and cloud computing, a scalable crawling and processing framework(jeff kim)
    • POE:CoPo::MessageQueue(STOMP)
    • Swarmage
    • Amazon EC2 + S3, ec2-s3間の転送はタダ.
    • 伝統的なクロウラー書くよりもGungho使ったほうがコスト安い
  • Perl Is unDead(Schwern)
    • 昔(mosaicの頃)はperlしかなかったけど今は他にも選択肢(php, python, ruby..)がある
    • fork programming. fork musicみたいに自分で手を動かして全部できるもの
    • 昔は簡単だったが今のperlCPAN依存は初心者に難しい。PHPjavascriptは始めようと思えば10分でできる。
    • perlは死んだと思われてる
    • 一般的なプロトコルperl以外とも話そう
    • 他のコミュニティに参加して今のperlのことを教えよう
    • BarCampとか

上に書いてないけどスパムちゃんぷるー、POEとerlang絡みの話も面白かった。仕事の都合なんかで今までperlのイベント行けなくて今回が初めてだったけど楽しかった!

perlからwedataにアクセスするWebService::Wedata

http://wedata.net/
http://search.cpan.org/dist/WebService-Wedata/ (2008-06-04 追記)
http://buffr.org/tmp/wedata/WebService-Wedata-v0.0.4.tar.gz
APIキーが必要なのでインストール時のテストは動かないようにしてますがt/01*.api_basic*.t.skipの内容のようにwedataに対して一通りCRUDできます。単純にAutopagerizeのSITECONFIG移動先として見られがちですが、OpenID使えるような人に編集してもらいたい既存データがある場合にも、wedataはAPIがあるので簡単に移動/編集できていいと思います。
以下はsynopsisから新規データベース/アイテムの追加と更新の抜粋。

use WebService::Wedata;

my $wedata = WebService::Wedata->new('YOUR_API_KEY');
my $database = $wedata->create_database(
    name => 'database_name',
    required_keys => [qw/foo bar baz/],
    optional_keys => [qw/hoge fuga/],
    permit_other_keys => 'true,'
);

my $item = $database->create_item(
    name => 'item_name',
    data => {
        foo => 'foo_value',
        bar => 'bar_value',
        baz => 'baz_value',
    }
);
$item->update(
    foo => 'foo_updated_value',
    bar => 'bar_updated_value',
    baz => 'baz_updated_value',
);

$item->delete;
$database->delete;

muxtapeからテープをダウンロードしてiTunesにプレイリストとして追加する

koyachi2008-03-30


以下のスクリプトをdlmuxtape.plとして保存し、

$ perl dlmuxtape.pl tape_name

などとするとiTunesを起動して"muxtape / tape_name"のような新規プレイリストを作成してhttp://tape_name.muxtape.comからダウンロードしたテープを追加します。

#!/usr/bin/env perl
use strict;
use warnings;
use LWP::UserAgent;
use List::MoreUtils qw/each_array/;
use Mac::iTunes;

my $tape_id = $ARGV[0] || "justin";
my $muxtape_url = "http://$tape_id.muxtape.com";
my $download_dir = $ARGV[1] || "./download";

unless (-d $download_dir) {
    mkdir $download_dir;
}
unless (-d "$download_dir/$tape_id") {
    mkdir "$download_dir/$tape_id";
}

my $itunes = Mac::iTunes->controller;
$itunes->activate;

my $playlist_name = "muxtape / $tape_id";
if ($itunes->playlist_exists($playlist_name)) {
    die "$playlist_name already exist";
}
$itunes->add_playlist($playlist_name);

my $ua = LWP::UserAgent->new;
$ua->agent('Firefox');
my $response = $ua->get($muxtape_url);
if ($response->is_success) {
    my $data = $response->content;
    $data =~ s!.*var muxtape = new Kettle\((.*?)\);.*!$1!s;

    my $ids = $data;
    $ids=~ s/^\[(.*)\],.*$/$1/;
    my @ids = map { s/'(.*?)'/$1/; $_; } split /,/, $ids;

    my $sigs = $data;
    $sigs =~ s/^\[.*?\],\[(.*?)\]$/$1/;
    my @sigs = map { s/'(.*?)'/$1/; $_; } split /,/, $sigs;

    my $count = 1;
    my $total = scalar @ids;
    my $ea = each_array(@ids, @sigs);
    while (my($id, $sig) = $ea->()) {
        my $song_url = song_url($id, $sig);
        print "[$count / $total] downloading... $song_url\n";
        $response = $ua->get($song_url);
        if ($response->is_success) {
            my $file_name = sprintf "%s/%s/%02d_%s.mp3", $download_dir, $tape_id, $count, $id;
            open FH, "> $file_name";
            binmode FH;
            print FH $response->content;
            close FH;

            $itunes->add_track($file_name, $playlist_name);
            $count++;
        }
        else {
            die "Can't get $song_url : " . $response->status;
        }
    }
}
else {
    die "Can't get $muxtape_url : " . $response->status;
}

sub song_url {
    my($hex, $sig) = @_;
    my $song_url = join "",
        "http://muxtape.s3.amazonaws.com/songs/",
        $hex,
        "?PLEASE=DO_NOT_STEAL_MUSIC&",
        $sig
    ;
    $song_url;
}

指定prefixで始まる複数の関数をExternalInterfaceとして簡単に公開する

actionscriptじゃないとできないような処理を細かい単位でたくさん作って各処理の制御タイミングはjavascriptにまかせるとExternalInterfaceでas->js公開する関数が増えてきてExternalInterface.addCallbacl5回くらいまでは我慢できるけどそれ以上になるとなんか考えたほうがいいなーと思って考えた。
最初は指定namespaceをExternalInterface.addCallbackしようとしてたんだけどどうもdescribeTypeではnamespace情報取得できないらしいので、関数prefixを使う。

jsへの公開を簡単にする関数作って、

package org.buffr.util {
    import flash.utils.describeType;
    import flash.external.ExternalInterface;

    public class Exporter {
        private static var funcHash:Object = {};
        private static var self:*;

        public static function export(_self:*, prefix:String = 'ext_') : void {
            self = _self;
            var desc:XML = describeType(self);
            var re:RegExp = new RegExp('^' + prefix + '(.*)$');
            for each (var m:XML in desc.method) {
                var result:Array;
                if (m.@declaredBy == desc.@name &&
                    (result = re.exec(m.@name))) {
                    var methodName:String = String(m.@name);
                    var jsMethodName:String = result[1];
                    funcHash[jsMethodName] = self[methodName];
                }
            }
            ExternalInterface.addCallback("exec", function(methodName:String, args:Array):* {
                return Exporter.funcHash[methodName].apply(Exporter.self, args);
            });
            ExternalInterface.addCallback("list", function() : Array {
                var list:Array = [];
                for (var m:String in Exporter.funcHash) {
                    list.push(m);
                }
                return list;
            });
        }
    }
}

Test01クラスのext_で始まる関数がjsへの公開関数

package {
    import flash.display.Sprite;
    import flash.external.ExternalInterface;
    import org.buffr.util.Exporter;

    public class Test01 extends Sprite {
        public function Test01() {
            Exporter.export(this);
            ExternalInterface.call('log', "[AS]init end");
        }

        public function a() : void {
        }
        private function b() : void {
        }

        public function ext_a() : void {
            ExternalInterface.call('log', "[AS]ext_a");
        }
        public function ext_b(a:uint, b:uint) : uint {
            ExternalInterface.call('log', "[AS]ext_b(" + a + ',' + b + ')');
            return a + b;
        }
        public function ext_c(a:String, b:String) : String {
            ExternalInterface.call('log', "[AS]ext_c(" + a + ',' + b + ')');
            return a + b;
        }
    }
}

でjsからは以下のようなかんじ

function ASBridge(id) {
  this.id = id;
}
ASBridge.prototype = {
  _exec: function(methodName, args) {
    log(methodName);
    return swf(this.id).exec(methodName, args);
  }
  ,
  _list: function() {
    return swf(this.id).list();
  }
};

function Test01() {
  this.installSWF();
  this.bridge = new ASBridge('test01');
  var self = this;
  setTimeout(function(){
    var asFuncs = self.bridge._list();
    for (var i=0,length=asFuncs.length; i < length; i++) {
      var func = asFuncs[i];
      self[func] = partial(function(func) {
        var args = [];
        for (var j=1,length=arguments.length; j < length; j++) {
          args.push(arguments[j]);
        }
        var result = self.bridge._exec(func, args);
        log([func, args, result].join(":"));
      }, func);
    }
  }, 1000);
}
Test01.prototype = {
  installSWF: function() {
    swfobject.embedSWF("Test01.swf",
                       "test01", "1", "1", "9.0.0", "expressInstall.swf", 
                       {}, {id:'test01'}, {id:'test01'});
  }
  ,
  d: function() {
    log("d");
  }
};

var test01 = new Test01();

var param = {
  a: [],
  b: [1, 2],
  c: ['1', '2'],
  d: []
};
'a b c d'.split(' ').forEach(function(id){
  var elm = document.getElementById(id);
  elm.addEventListener('click', function(e) {
    test01[id].apply(test01, param[id]);
  }, false);
});

aidy3, Firefoxの検索窓からアクセスできるようにした & Songbirdから1キーでダウンロード対応

Firefoxの検索窓はOpenSearch descriptionファイルを設置してsearch.htmlに飛ぶようにしてるだけ。Songbirdの検索窓からも同様にアクセスできる。

それとSongbird専用キーアサインとして、検索画面の通常再生(aキーかマウスクリック)の後dキーで最後に再生したファイルをSongbirdライブラリにダウンロード可能にした。

Songbirdにはweb apiが用意されていて、通常のweb page内のjavascriptからアクセスできるグローバル領域にsongbirdオブジェクトが存在して、Songbirdに対してアクションさせることができる。音源へのstaticなリンクを持つ普通のページだとsongbirdがリンクを解析してページに含まれる音源のリストを画面下領域にリスト表示してくれるんだけど、aidy3の検索はjsで動的に書き換えてるので何もしないとこの領域に表示されない。songbirdが用意してるweb apiを使うとこの音源リスト領域に任意アイテム追加などができる。とりあえず検索ごとに再生したものをリスト領域に表示するようにしてみた。検索キーワードに対する再生履歴みたいな感じになっている。