2008年3月アーカイブ

なんか、一部の人たちでpolipoって言うproxyが流行っているらしい。
うちは、ルータであるFreeBSDにsquidを入れて、透過proxyとして使っているのだけれど、どんなもんかと思って入れてみる。
まずは、portsからwww/polipoをインストール。本家サイトを見てみたけど、1.0.4で最新版らしい。
インストールすると、
To complete the polipo installation you should:

1. copy the configuration file example and modify it if desired
   cd /usr/local/etc/polipo/
   cp config.sample config
   vi config

2. enable automatic polipo startup with entry in /etc/rc.conf.local or /etc/rc.conf
   polipo_enable="YES"

3. start polipo
   /usr/local/etc/rc.d/polipo start

4. point your browser to the new proxy server, the default port is 8123
なんて言われる。デフォルトのポートが8123でsquidと違うので、とりあえず両方動かしておいて動作確認ができそうだ。
手順に従って、まずは/usr/local/etc/polipo/configを編集する。
デフォルトと変えたところのみ記述。
proxyAddress = "192.168.0.1"    # polipoがlistenするIPアドレス。ルータの家庭内LAN向けのIPを指定。
allowedClients = "192.168.0.0/24" # 接続を許可するIPアドレス。家庭内LANからのみ許可。
parentProxy = "127.0.0.1:3128" # 親proxy。とりあえず、今まで使っていたsquid。意味ないかな?
uncachableFile = /usr/local/etc/polipo/uncachable
※ diskcacheは、portsから入れた場合デフォルトで有効(/var/cache/polipo)
※ config.sampleには、forbiddenFile = の行は存在しなかったが、forbidden.sampleと言うファイルは存在した。良くわからん。
/usr/local/etc/polipo/uncachableを作成。(どこかにテンプレートないかな。)
http://.*megalodon.jp/
そして、/etc/rc.confにpolipo_enable="YES"を追加。
polipoを起動。
ブラウザのproxyを、ルータのIP:8123に変更。
う~ん、早くなった・・・かな?
今のところRSSを見るのにFirefoxの拡張のSageと言うのを使っている。
ところが、こいつはBookmark Synchronizerと相性が悪い。具体的には、常にmergeしてあげないといけない。
常にmergeだと、新しいbookmarkやrssの追加は良いけど、移動や削除ができない。
で、最近LDRizeを入れたことで livedoor Readerに興味を持ったので、アカウントを取ってみた。
なかなか良さそうな感じなんだけど、Google Readerと一緒で認証がかかってるRSSが取れない。(あたりまえ)
これは結構致命的なのだけれど、調べていたらPlaggerLDRとか言うものがあるらしい。
こいつは、フロントエンドはlivedoor Readerで、バックエンドをCatalyst/Sqlite3にするものらしい。
さらに、plaggerのStore::DBICと組み合わせれば、plaggerでなんでも食わせられるという優れもの。
早速試してみようとしたけど、LWP::UserAgentが2.003じゃないと動かないよ、と言うエラーが出た。
portsのp5-libwwwのバージョンが古いらしい。
cpanから入れてしまおうかどうか悩んでいたら、今度はFastladderのオープンソース版があると言うのを見つけた。
Fastladderと言うのはlivedoor Readerの英語版で、それのオープンソース版があるらしい。
こちらは、rails/mysql(デフォルトはsqlite3)で、mongrelで動きます。PlaggerLDRと同じように、plaggerのStore::Fastladderでなんでも入れられるらしい。
早速入れて・・・と思ったけど、INSTALL.txtはかなり不親切です。
portsのrailsだと古いようだったので、gemsから必要パッケージをインストール。
mysqlの設定をして、サーバを起動。ブラウザからアクセスすると、ユーザ登録画面になる。
ユーザを登録してログインすると、livedoor Readerそっくりの画面(ただし英語)。
が、なんかエラーが出る。
で、サーバの他にcrawlerも起動しろとか書いてあるので、起動しようとしたらfreeimageがないとか言われる。
あれ~?portsからgraphics/freeimage入れたんだけどな~?と思って調べてみたら、portsに間違いがあってまともにライブラリが入らないらしい。
さっそくfreeimageを入れなおしてみたら、無事に動いたようだ。
と言ったところで今日は時間切れ。
Store::Fastladderはまたplaggerの別のbranchesらしいので、ちょっと面倒だ。
本格的に運用しようと思ったら、外部向けのポートでLISTENしないようにして、apacheからproxyするようにして、SSLのクライアント認証入れて、といろいろやることはありそうだ。しかし、railsのことを全然知らないので、ハードル高いな・・・。
と、言うわけでオープンソース版のfastladderのインストール。
まだうまく行っていないんだけど、書いておけば誰か教えてくれるかも知れないので。
  1. portsから、databases/mysql50-server をインストール。(既に入っていた。今は5.1もあるらしい)
    ※ 既に入っていたので設定系は割愛。
  2. portsから、lang/ruby18, devel/ruby-gems, databases/ruby-mysqlをインストール。(既に入っていた)
  3. portsから、graphics/freeimageをインストール。ただし、現時点のものでは正しくライブラリが入らないので、ここを参考にライブラリを入れなおす。
  4. fastladderのソースを持ってくる
    % svn co http://fastladder.googlecode.com/svn/trunk/fastladder
    
    ※ 以降は、fastladder/INSTALL.txtに従ってやるんだけど、DBをSqlite3じゃなくてmysqlを使う場合は手順が微妙に違う。
  5. まずは、gem関係をインストール。(portsから入れると、railsのバージョンが古いので、INSTALL.txt通りに手でやる必要がある。また、gemsのバージョンが古いとrails2.0.2が入らなかったので、portupgrade ruby18-gemsしておく)
    # gem install rails --version 2.0.2 -y
    # gem install rfeedfinder
    # gem install feed-normalizer
    # gem install opml
    # gem install mongrel
    
    mysqlを使うのでsqlite3-rubyは必要ない。
  6. config/database.yml を修正
    # cp config/database.yml.mysql config/database.yml
    # vi config/database.yml
    
    production: の所を適宜修正
    production:
      adapter: mysql
      encoding: utf8
      database: fastladder_production
      username: root
      password: mysqlのrootのパスワード
      socket: /tmp/mysql.sock ←FreeBSDのportsからmysqlを入れるとここ
    
  7. databaseを作る
    # mysqladmin -p create fastladder_production
    # RAILS_ENV=production rake db:migrate ←cshだとこの書き方できない
    
ここまでで、とりあえず本体のインストールは完了。
# script/server -d -e production
でサーバが起動するので、http://localhost:3000/ とかでアクセスできる。初回はアカウントの作成画面なので、作っておく。
INSTALL.txtだと、ここでcrawlerも起動しろと書いてあるけど、crawlerは使わないでPlaggerのStore::Fastladderを使う予定なので無視。

本格運用のためには、mongrelの3000直アクセスではなく、apacheのhttps経由にしたい。
apache2系であれば、mod_proxyを使ってmongrelにリクエストが飛ばせるようだ。
しかし、ここで問題が発生。
  • mongrel上のrailsアプリは、/ で動くことを期待している。
  • mongrel上のrailsアプリは、http:// で動くことを期待している。
ようなのだ。
2番目の問題については、apacheの設定に
RequestHeader set X_FORWARDED_PROTO 'https'
を入れることで対応できることがわかった。
しかし、1番目の問題の対応がわからない。調べてみたところ、mongrelの起動時に --prefix をつけると、変えられるらしいことがわかったが、fastladder/script/server は --prefixを解釈してくれない。
ただし、script/serverを実行すると、中ではmongrelを起動しているだけのようなので、直接
/usr/local/bin/ruby18 /usr/local/bin/mongrel_rails start -p 3000 -a 127.0.0.1 -e production -P /var/www/fastladder/tmp/pids/mongrel.pid -d --prefix /fastladder
のように起動してやると、--prefixがつけられた。 apacheの設定ファイルのsslの部分に、以下を追加。
    ProxyRequests off
    <Location /fastladder>
        SSLVerifyClient require ←httpsのクライアント認証を要求
        ProxyPass http://127.0.0.1:3000/fastladder
        ProxyPassReverse http://127.0.0.1:3000/fastladder
        RequestHeader set X_FORWARDED_PROTO 'https'
    </Location>
これで、https://・・・/fastladder がmongrelに行くようになる。
んだけど、fastladder がこいつをちゃんと認識してくれない。
  1. ブラウザから、https://・・・/fastladder にアクセス。
  2. railsがリクエストを受け取り、reader コントローラの welcome アクションを実行。
  3. https://・・・/login にリダイレクト(https://・・・/fastladder/login を期待している)。
  4. そんなページはないのでブラウザに404。
うーん、mongrelの--prefixだけじゃ駄目で、config/route.rb もいじらないといけないのかな。
と言う所ではまってしまって進みません。railsが全然わからないので、自分でいくつかサンプルでも作ってみないと駄目かな。

ちなみに、/fastladderとかつけなければ動くので、今までのwebサーバと喧嘩しないように、新しいホスト名をdnsでつけてあげて、virtualhostを作ればうまく動きます。
この場合、一つのIPで二つのSSL証明書が使えないので、fastladderの方は「証明書とホスト名が合わないんだけど」的なエラーがブラウザに出ます。
どうせオレオレ証明書だし、気にしなければ良いのですが、やはり気持ち悪い。
結局、portsからwww/mod_proxy_htmlを入れてみた。
最初、何故かapache2.0がインストールされてはまる。
apache2.0をアンインストールすると、apxsとか上書きされてたapache2.2のファイルが消されてしまいさらにはまる。
apache2.2を再インストールしてなんとか元に戻る。(この過程でapache-2.2.6からapache-2.2.8にバージョンアップされて、aprをアンインストールする必要があったりした)
すると、再度mod_proxy_htmlを入れてもちゃんとapache2.2のディレクトリに入った。
で、いろいろ設定をいじってみたけど、結局思惑通りには動かず。と言うか、ログを見てみてもmod_proxy_htmlが効いているように見えない。なんでだろう。

あきらめて、fastladder は別名のVirtualHostで動かすことにして、今度はStore::Fastladder に取り掛かる。
% svn co http://svn.bulknews.net/repos/plagger/branches/fastladder-crawler/plagger/ fastladder-crawler
でStore::Fastladder を持ってくる。
面倒なので、portsのPlaggerとか、Plaggerのtrunkにリンク張ったりとかせずに、とりあえずチェックアウトしてきたディレクトリで動かしてみることに。
# ってゆーか、Plaggerってまだかなりな勢いで開発されているので、portsとかでシステムに入れるよりも、plaggerを動かす個人ユーザのディレクトリにsvnで持ってきた方が良いかも知れない。
で、以下のようなfastladder.yamlを書いて、
global:
  timezone: Asia/Tokyo

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: file://var/www/docs/mt/pc/feed.xml ←とりあえず内部のrss
  - module: Store::Fastladder
    config:
      connect_info:
        - dbi:mysql:fastladder_production
        - root
        - mysqlのrootのパスワード
        - on_connect_do:
            - SET NAMES utf8
      member_id: 1
以下のコマンドで実行
% fastladder-crawler/plagger -c fastladder.yaml
すると、
Base class package "DBIx::Class::Schema::Loader" is empty.
なんて言われたので、portsから databases/p5-DBIx-Class-Schema-Loader をインストール。なんか設定画面が出てきたので、とりあえずSqlite3とmysqlとpostgresqlを選んだ。
依存関係でかなりたくさんのパッケージが入ったもよう。
もう一度実行してみたら、今度は
DBIx::Class::InflateColumn::set_inflated_column(): Couldn't load DateTime::Format::MySQL
とか言われるので、devel/p5-DateTime-Format-MySQL をインストールする。
すると、無事にplaggerの実行が終わった。
どきどきしながらfastladderのページでReloadを押してみたら、出てきてます!
でも、日本語が化けてーら。
さて、何が悪いのか切り分けないといけません。
とりあえず、
% mysql -p fastladder_production
mysql> \s
Server characterset:    latin1
Db     characterset:    latin1
Client characterset:    latin1
Conn.  characterset:    latin1
どうも、データベースがlatin1になっているようです。
mysqladmin dropして、mysqladmin create --default-character-set=utf8 とかやってみましたが、やっぱりlatin1のまま。
mysqlわかんねーよとか思いつつ、/var/db/mysql/my.cnfを修正します。[mysqld]セクションに以下の行を追加。
default-character-set = utf8
で、データベースを作り直して、rake db:migrateしたら無事に日本語も化けなくなりました。
これで、fastladder.yamlに好きなfeedを追加して、cronとかに仕込めばブラウザさえあればいつでもどこでもfeedが読めちゃいます。
さよなら、Sage、さよなら、livedoor Reader。
gmailに送ってるCustomFeed::MixiScraperもfastladderに入れちゃおうかしらん。

さて、浮かれてばかりいないで今後の課題です。
  • mongrelがrootで動いているのは気持ち悪いので、fastladder用のアカウントを作ってそちらで動かすようにする。
  • mysqlのDBをmysqlのrootでアクセスするのは気持ち悪いので、fastladder用のmysqlアカウントを作ってそちらを使うようにする。
  • せっかくFastladderの画面でsubscribeできるのに、それが効かないのは悲しいので、fastladder.yamlに反映する方法を考える。(これが一番ハードル高いな)
本格運用に向けて、もうちょっとセキュリティとか考えた設定にする。
  1. fastladderを実行するOSユーザを作る
    # adduser
    Username: fastladder
    Full name: fastladder
    Uid (Leave empty for default):
    Login group [fastladder]:
    Login group is fastladder. Invite fastladder into other groups? []:
    Login class [default]:
    Shell (sh csh tcsh nologin) [sh]:
    Home directory [/home/fastladder]: /var/www/fastladder
    Use password-based authentication? [yes]: no
    Lock out the account after creation? [no]:
    Username   : fastladder
    Password   : 
    Full Name  : fastladder
    Uid        : 3134
    Class      :
    Groups     : fastladder
    Home       : /var/www/fastladder
    Shell      : /bin/sh
    Locked     : no
    OK? (yes/no): y
    adduser: INFO: Successfully added (fastladder) to the user database.
    Add another user? (yes/no): no
    Goodbye!
    
  2. mysqlのユーザを作る(fastladder_productionデータベースは(1)で作った)
    # mysql -p
    mysql> grant all on fastladder_production.* to fastladder@localhost identified by 'パスワード';
    
  3. fastladderディレクトリのオーナーをfastladderユーザにする。
    # chown -R fastladder:fastladder /var/www/fastladder
    
  4. fastladder/config/database.yaml のusernameとpasswordをrootからfastladderのものに変える。
  5. /usr/local/etc/rc.d/fastladder を以下の内容で作成する。(postgresqlを参考に作った)
    #!/bin/sh
    
    # PROVIDE: fastladder
    # REQUIRE: LOGIN
    # KEYWORD: shutdown
    #
    # Add the following line to /etc/rc.conf to enable fastladder:
    #
    #  fastladder_enable="YES"
    #  # optional
    #  fastladder_flags="-b 127.0.0.1 -e production -d"
    #
    # This scripts takes one of the following commands:
    #
    #   start stop
    
    prefix=/var/www/fastladder
    command=${prefix}/script/server
    
    . /etc/rc.subr
    
    load_rc_config fastladder
    
    # set defaults
    fastladder_enable=${fastladder_enable:-"NO"}
    fastladder_user=fastladder
    fastladder_flags=${fastladder_flags:-"-b 127.0.0.1 -e production -d"}
    
    name=fastladder
    rcvar=`set_rcvar`
    command_args="${fastladder_flags}"
    
    start_cmd='su -l ${fastladder_user} -c "exec ${command} ${command_args}"'
    stop_cmd='kill `cat /var/www/fastladder/tmp/pids/mongrel.pid`'
    
    run_rc_command "$1"
    
    /etc/rc.confでfastladder_enable="YES"して、/usr/local/etc/rc.d/fastladder start で起動する。
  6. plaggerのfastladder.yamlのdatabaseの設定も、fastladderユーザに書き換える。
  7. plaggerの関連ファイルもfastladderオーナーに変更して、fastladderユーザのcrontabに仕込む。
さっそくMixiの新着とかもfastladderに流し込む設定にして、cronに仕込んで一晩経ちました。
で、fastladderの画面を見てみると、新着のfeedが並んでます。
でも、未読の数が最大でも10件。
あれ~?そんなはずは?と思ってmysqlでデータベースを覗きます。
feedsと言うテーブルにfeedsの情報が、itemsと言うテーブルにエントリの情報が、subscriptionsと言うテーブルにmemberとfeedsの関連付けがあるようです。
で、subscriptions.has_unreadを見て未読があるかどうかを判断しているようです。
items自身には既読の情報がないので、subscriptions.viewed_onとitems.created_on(?)を比べて未読かどうかを判断しているようです。
で、画面から一度見てしまうと、viewed_onとhas_unreadが更新されてしまうため、未読は全部既読になります。
mongrelのproduction.logを見ながらReloadボタンを押してみると、apiコントローラのsubsアクションを呼んでいます。
なので、api_controller.rb を読むと、subsの中でDBから引いた未読件数(unread_count)と定数MAX_UNREAD_COUNTのうちの小さい方を件数として採用していることがわかりました。
後は、MAX_UNREAD_COUNTを定義しているところを探したら、fastladder/config/initializers/constants.rb にありました。
案の定10となっているので、適当に200とかに変えてmongrelを再起動したら、無事に10件よりも多い未読が表示されるようになりました。
# どうせなら、feed単位じゃなくてitem単位で未読管理がしたいところですが、それは大改造ですかね。
もう一つ気になったのが、fastladderの画面でEdit Feedsの画面。
Last Updateが全て 465 monthes ago になっています。
これは、feedsテーブルのmodified_onがNULLになっているためで、Store::Fastladder の問題のようです。
Fastladder.pm の sub store で、$feed->modified_onを適切に設定してあげれば良いのだと思いますが、Plaggerが良くわかっていないので $args->{feed}の何を入れれば良いのかがわかりません。(とりあえずupdated_onと同じようにしておきましたが)
いや~、ソースがあるって言うのは素晴らしいですね。
fastladder で各種RSSとMixiの新着を見るようになって、うはうはなのですが、うはうはなりに気になるところが出てきます。
そこで、fastladder-crawler の CustomFeed::Mixi に手を入れて、トピック単位にfeedができるようにしてみました。
Index: lib/Plagger/Plugin/Store/Fastladder.pm
===================================================================
--- lib/Plagger/Plugin/Store/Fastladder.pm	(リビジョン 2024)
+++ lib/Plagger/Plugin/Store/Fastladder.pm	(作業コピー)
@@ -57,6 +57,7 @@
         title       => $args->{feed}->title || '',
         description => $args->{feed}->description || '',
     });
+    $feed->modified_on( $args->{feed}->updated || $now );
     $feed->updated_on( $args->{feed}->updated || $now );
     $feed->created_on( $now ) unless $feed->in_storage;
     $feed->insert_or_update;
Index: lib/Plagger/Plugin/CustomFeed/MixiScraper.pm
===================================================================
--- lib/Plagger/Plugin/CustomFeed/MixiScraper.pm	(リビジョン 2024)
+++ lib/Plagger/Plugin/CustomFeed/MixiScraper.pm	(作業コピー)
@@ -85,7 +85,11 @@
     my($self, $context, $args) = @_;
     for my $type (@{$self->conf->{feed_type} || ['FriendDiary']}) {
         $context->error("$type not found") unless $MAP->{$type};
-        $self->aggregate_feed($context, $type, $args);
+	if ($type eq 'BBS') {
+	    $self->aggregate_feed_bbs($context, $type, $args);
+	} else {
+	    $self->aggregate_feed($context, $type, $args);
+	}
     }
 }
 sub aggregate_feed {
@@ -115,7 +119,9 @@
         $entry->link($msg->{link});
         $entry->author($msg->{name});
         $entry->date( Plagger::Date->parse($format, $msg->{time}) );
-
+	if ($entry->date) {
+	    $entry->date->set_time_zone('Asia/Tokyo');
+	}
         if ($self->conf->{show_icon} && !$blocked && defined $MAP->{$type}->{icon}) {
             my $owner_id = $msg->{link}->query_param($MAP->{$type}->{icon});
             $context->log(info => "Fetch icon of id=$owner_id");
@@ -161,16 +167,16 @@
                 my $body = $item->{description};
                    $body =~ s!(\r\n?|\n)!<br />!g;
                 for my $image (@{ $item->{images} || [] }) {
-                    $body .= qq(<div><a href="$image->{link}"><img src="$image->{thumb_link}" style="border:0" /></a></div>);
-                    my $enclosure = Plagger::Enclosure->new;
-                    $enclosure->url($image->{thumb_link});
-                    $enclosure->auto_set_type;
-                    $enclosure->is_inline(1);
-                    $entry->add_enclosure($enclosure);
+		    my $imagelink = $image->{link};
+		    $imagelink =~ s!.*(show_.*?_picture\.pl.*?)'.*!http://mixi.jp/$1!;
+                    $body .= qq(<a href="$imagelink"><object type="text/html" data="$imagelink" >$imagelink</object></a>);
                 }
                 $entry->body($body);
 
                 $entry->date( Plagger::Date->parse($format, $item->{time}) );
+		if ($entry->date) {
+		    $entry->date->set_time_zone('Asia/Tokyo');
+		}
                 if ($self->conf->{fetch_comment}) {
                   for my $comment (@{ $item->{comments} || [] }) {
                       my $c = Plagger::Entry->new;
@@ -179,6 +185,9 @@
                          $c->link($comment->{link});
                          $c->author($comment->{name});
                          $c->date( Plagger::Date->parse($format, $comment->{time}) );
+		      if ($c->date) {
+			  $c->date->set_time_zone('Asia/Tokyo');
+		      }
                       push @comments, $c;
                   }
                 }
@@ -197,6 +206,124 @@
     $context->update->add($feed);
 }
 
+sub aggregate_feed_bbs {
+    my($self, $context, $type, $args) = @_;
+
+
+    my $format = DateTime::Format::Strptime->new(pattern => '%Y-%m-%d %H:%M');
+
+    my $meth = $MAP->{$type}->{get_list};
+    my @msgs = $self->{mixi}->$meth->parse;
+    my $items = $self->conf->{fetch_items} || 20;
+    $self->log(info => 'fetch ' . scalar(@msgs) . ' entries');
+
+
+    my $i = 0;
+    my $blocked = 0;
+    for my $msg (@msgs) {
+        last if $i++ >= $items;
+
+	my $feed = Plagger::Feed->new;
+	$feed->type('mixi');
+	my $subject = $msg->{subject};
+	$subject =~ s/\([0-9]*\)$//;
+	$feed->title($subject);
+	$feed->description('Mixi: ' . $msg->{name});
+	my $link = $msg->{link};
+	$link =~ s/&comment_count=[0-9]*//;
+	$feed->link($link);
+
+        my $entry = Plagger::Entry->new;
+        $entry->title($msg->{subject});
+        $entry->link($msg->{link});
+        $entry->author($msg->{name});
+        $entry->date( Plagger::Date->parse($format, $msg->{time}) );
+	if ($entry->date) {
+	    $entry->date->set_time_zone('Asia/Tokyo');
+	}
+        if ($self->conf->{show_icon} && !$blocked && defined $MAP->{$type}->{icon}) {
+            my $owner_id = $msg->{link}->query_param($MAP->{$type}->{icon});
+            $context->log(info => "Fetch icon of id=$owner_id");
+
+            my $item = $self->cache->get_callback(
+                "outline-$owner_id",
+                sub {
+                    Time::HiRes::sleep( $self->conf->{fetch_body_interval} || 1.5 );
+                    my $item = $self->{mixi}->show_friend->parse(id => $owner_id)->{outline};
+                    $item;
+                },
+                '12 hours',
+            );
+            if ($item && $item->{image} !~ /no_photo/) {
+                # prefer smaller image
+                my $image = $item->{image};
+                   $image =~ s/\.jpg$/s.jpg/;
+                $entry->icon({
+                    title => $item->{name},
+                    url   => $image,
+                    link  => $item->{link},
+                });
+            }
+        }
+
+        my @comments;
+        if ($self->conf->{fetch_body} && !$blocked && $msg->{link} =~ /view_/ && defined $MAP->{$type}->{get_detail}) {
+            # view_enquete is not implemented and probably
+            # won't be implemented as it seems redirected to
+            # reply_enquete
+            next if $msg->{link} =~ /view_enquete/;
+            $context->log(info => "Fetch body from $msg->{link}");
+            my $item = $self->cache->get_callback(
+                "item-".$msg->{link},
+                sub {
+                    Time::HiRes::sleep( $self->conf->{fetch_body_interval} || 1.5 );
+                    my $item = $self->{mixi}->parse($msg->{link});
+                    $item;
+                },
+                '12 hours',
+            );
+            if ($item) {
+                my $body = $item->{description};
+                   $body =~ s!(\r\n?|\n)!<br />!g;
+		$body =~ s!<a href=.*?&#39;(.*?)&#39;.*?</a>!<object type="text/html" data="http://mixi.jp/$1">http://mixi.jp/$1</object>!g;
+		$body =~ s!&#38;!&!g;
+                $entry->body($body);
+
+                $entry->date( Plagger::Date->parse($format, $item->{time}) );
+		if ($entry->date) {
+		    $entry->date->set_time_zone('Asia/Tokyo');
+		}
+                if ($self->conf->{fetch_comment}) {
+                  for my $comment (@{ $item->{comments} || [] }) {
+                      my $c = Plagger::Entry->new;
+                         $c->title($entry->title . ': '. $comment->{subject});
+		      $body = $comment->{description};
+		      $body =~ s!<a href=.*?&#39;(.*?)&#39;.*?</a>!<object type="text/html" data="http://mixi.jp/$1">http://mixi.jp/$1</object>!g;
+		      $body =~ s!&#38;!&!g;
+                         $c->body($body);
+                         $c->link($comment->{link});
+                         $c->author($comment->{name});
+                         $c->date( Plagger::Date->parse($format, $comment->{time}) );
+		      if ($c->date) {
+			  $c->date->set_time_zone('Asia/Tokyo');
+		      }
+                      push @comments, $c;
+                  }
+                }
+            } else {
+                $context->log(warn => "Fetch body failed. You might be blocked?");
+                $blocked++;
+            }
+        }
+
+        $feed->add_entry($entry);
+        for my $comment ( @comments ) {
+            $feed->add_entry($comment);
+        }
+	$context->update->add($feed);
+    }
+}
+
 1;
 
 __END__
やっていることは、以下です。
  • Store::Fastladderで、feedsのmodified_onを更新するように。(これは別に不要だけど)
  • CustomFeed::MixiScraperで、feed_typeが'BBS'のときだけaggregate_feedじゃなくてaggregate_feed_bbsを呼ぶように。(汚くてごめんなさいごめんなさいごめんなさい)
  • ところどころでset_time_zone('Asia/Tokyo')してるのは、作者さんに「yamlが正しければ不要なはず」、と言われたんだけど何故か私の環境では必要だったので。
  • aggregate_feedをコピーしてaggregate_feed_bbsを作成し、ループの中でPlagger::Feedを作るように変更。
  • gmailじゃなくてfastladderで読むので、画像はenclosureではなくてobjectタグでリンクを埋め込み(これも、plaggerの思想的にあり得ないよね。enclosureも残しつつうまくやれるように別のフィルタでも書くべきかなあ)
最初、画像の部分は寝ぼけてimgタグを使おうとしたんだけど、mixiの画像表示cgiは画像そのものではなくてhtmlを返すので、うまくいかない。
で、苦肉の策でiframeかobjectタグでhtmlを埋め込もうと考えたわけだけど、mixiは画像のサイズを教えてくれないので、objectタグにwidthやheightが書けない。
もちっと詳しく書くと、たとえばコミュニティの掲示板の場合、画像は
<a href="javascript:void(0);" onClick="MM_openBrWindow('show_bbs_comment_picture.pl?bbs_id=xxx&id=yyy&comm_id=zzz&number=nnn','pict','width=650,height=660,toolbar=no,scrollbars=yes,left=5,top=5')"><img src="http://ic61.mixi.jp/p/RANDOM_URL.jpg" border="0" alt="" /></a>
のような形式になっている。
gmailに送っていたときは、img src に書いてあるサムネイル画像を、Filter::FetchEnclosureで取ってきてメールに添付していたのだけれど、Store::Fastladderではそう言うわけにはいかないので、対応方法は二つ。
  1. FetchEnclosureと同じようにローカルに画像をfetchして、fastladderと同じサーバで閲覧できるようにURLを生成する。
  2. 画像はfastladderで見るときにmixiのサーバに取りに行く
で、今回は後者の方法を採用した。
mixiの画像のURLは一時的なものなので、imgタグを書けないとすると、iframeやobjectタグでshow_bbs_comment_picture.plを埋め込むしかない。
しかし、上記URLには画像のサイズの情報が入っていないし、show_bbs_comment_picture.plが返すhtmlにもサイズの情報は入っていない。
結局、画像のサイズが欲しかったら一度show_bbs_comment_picture.plを呼び出して、その結果をscrapeして、実際の画像ファイルのURLを取り出して、画像を取得して、までやらないと画像のサイズがわからない。(サムネイルのサイズは1段階少なくて取得できるけど、サムネイルを表示させるURLは存在しないので)
そこまでやるのは面倒だったので、今回はサイズ指定なし。これだと、Firefoxだと適当なサイズで表示されてスクロールバーが出る。IEだと表示されないそうだ。(試していないけど)
で、ここまでやったらfastladderで写真が見れると思ったんだけど、何も表示されない。
なんでかな~と思ってfastladderを見ていたら、lib/string_utils.rb の scrub_htmlでALLOW_TAGSにあるタグ以外は消しているように見える。
ALLOW_TAGSはconfig/initializers/constants.rb で定義されていたので、
===================================================================
--- constants.rb        (revision 28)
+++ constants.rb        (working copy)
@@ -1,8 +1,8 @@
-MAX_UNREAD_COUNT = 10
+MAX_UNREAD_COUNT = 200
 DEFAULT_FAVICON = "#{RAILS_ROOT}/public/img/icon/default.png"
 SUBSCRIBE_LIMIT = 5000
 SAVE_PIN_LIMIT = 100
 CRAWL_INTERVAL = 60

-ALLOW_TAGS = %w(a i u b em strong table tr td th tbody font center div pre code blockquote ins del img br p hr ul li ol dl dt dd)
-ALLOW_ATTRIBUTES = %w(src width height border alt title href color size align)
+ALLOW_TAGS = %w(a i u b em strong table tr td th tbody font center div pre code blockquote ins del img br p hr ul li ol dl dt dd object)
+ALLOW_ATTRIBUTES = %w(src width height border alt title href color size align type data)
みたいな感じでobjectタグとtype, data属性を許可してみた。
objectタグを許可するのは果てしなくセキュリティホールな気がするので、もっと良いやり方がありそうだけど。

とりあえずマイミクの日記の写真と、コミュニティの掲示板のコメントの写真は見れることを確認したけど、このソースだとコミュニティのトピック自身の画像が見れない気がするな。
早速、CustomFeed::MixiScraperの作者さんにBBSのトピック毎にfeedを分ける部分を取り込んでもらえました。ありがとうございます。
画像に関しては、フィルタで対応するのが適切だろうとの指摘をもらいましたので、暇ができたら考えてみたいと思います。
と、言うことでまだ最新のCustomFeed::MixiScraperを取り込んでいないのですが、フィルタと合わせて再度差分を掲載したいと思います。

なんの差分かと言うと、今までは重複を省くのに、Rule::Freshを使っていました。Mixiのときはこれで問題ないのですが、rssを処理しようと思うと、plaggerがcrawlした時間よりも前の作成時間を持つ記事を取りこぼしてしまいます。(記事を一度下書きで投稿して、時間が確定した後で、なんどか推敲してから公開するような場合)
で、Rule::Dedupedを使うように変えてみたのですが、Dedupedはpermalinkとdateを比較して重複を判断します。(compare_body=0の場合)
で、標準のCustomFeed::MixiScraperだと、entryのpermalinkに現在のコメント数が含まれるため、コメントが増えるとpermalinkが変わってしまい、毎回新しいentryとして登録されてしまいます。
と、言うわけでこの辺の対応について後でまとめようかなと。

さて、今日のネタは、PlaggerのStore::Fastladderで記事をDBに入れて、fastladderで読んでいる場合に、fastladderからfeedを登録できるようにする方法です。
まずは、以下の内容でPlaggerの設定ファイルを作成します。
fastladder.yaml(plaggerに食わせる設定ファイル)
include: path_to_feeds.yaml/config.feeds.yaml

recipes:
  - feeds

以下、元のconfig.yamlの内容。mixiとか
config.feeds.yaml
define_recipes:
  feeds:
    - module: Subscription::Config
      config:
        feed:
          既に購読済みのfeedがあれば、ここに書く
両ファイルはplaggerを動かすユーザが読める必要があり、config.feeds.yamlはmongrelを動かすユーザが書ける必要があります。
fastladder/config/initializers/constants.rb に config.feeds.yamlのパスを設定する。
FEEDS_CONFIG_FILE = "path_to_feeds.yaml/config.feeds.yaml"
fastladder/app/controller/api/feed_controller.rb の subscribe_feedを以下のようにする。
  def subscribe_feed(feedlink, options)
    open(FEEDS_CONFIG_FILE, "a") { |feedsfile|
        feedsfile.puts("          - url: " + feedlink)
    }
    member.subscribe_feed(feedlink, options)
  end
後は、mongrelを再起動すればfastladderのUIからfeedが追加できるようになります。
あ、これだと手抜きでmemberが複数いる場合はうまくいかないので、複数に対応するにはファイル名にmemberidとかをつける必要がありますね。
  def subscribe_feed(feedlink, options)
    open(FEEDS_CONFIG_FILE + "." + member.id, "a") { |feedsfile|
        feedsfile.puts("          - url: " + feedlink)
    }
    member.subscribe_feed(feedlink, options)
  end
とかにして、config.feeds.yaml.1とかにすれば行けるかなあ。試してないけど。
削除には対応してないので、削除は手動で。
2008年3月
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          

このアーカイブについて

このページには、2008年3月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2008年2月です。

次のアーカイブは2008年4月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 6.1.1