理系学生日記

おまえはいつまで学生気分なのか

Plagger で特定カテゴリの blog エントリを mixi ダイアリーに配信しないようにする

ぼくは mixi ダイアリーにこのブログを指定していて,この blog の更新情報はぼくのマイミクに通知されることになっています.ところが SICP だの cisco だの database だのというカテゴリは,ぼくのマイミクのほとんどは全く興味がない.それでいて頻繁にエントリとしてアップされるので,id:kiririmode はスパマーである,みたいな評価をされかねません.なんとかしなくては!
そういう感じでぼくは生きているんですけど,そういや Plagger 使えばこういうのはすぐにできるんじゃね?と思い立ちましたから,Plagger を学ぶのも含めて,SICP やらのエントリを省いたフィードを作ることに挑戦してみたのでした.

設定

プラグインをロードするのは,Plagger#load_plugins になります.Plagger#load_plugins は yaml ファイルの plugins: 欄の中の module: で指定されたプラグインを Plagger#load_plugin によってロードし,プラグインをインスタンス化します.たとえば以下の場合は,Subscription::Config や Filter::Rule などが (明示的に) インスタンス化されます.*1

global:
  assets_path: /home/y-kiri/.plagger-assets

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: http://d.hatena.ne.jp/kiririmode/rss
  - module: Summary::TextOriginal
  - module: Filter::Rule
    rule:
      module: EntryTag
      tag:
        - SICP
        - database
        - cisco
      op: NOR
  - module: Publish::Feed
    config:
      format: RSS
      dir: /home/y-kiri/www/rss
      filename: mixi.rss

ここで,上記設定ファイルでは,Subscription::Config の config 情報として,feed の url にこの blog の rss を指定しています.

Subscription::Config

Subscription::Config は指定したフィードを Plagger で扱えるようにできるプラグイン.フックポイント subscription.load に対し,Subscription::Config#load をフックとして設定します.このあたりの実行フェーズについては,

が詳しいです.
じゃあ load は何をするのかというと,フィードを Plagger::Feed オブジェクトに変換して Plagger オブジェクトに保持させるという仕事をします.

Aggregator::Simple

上記 yaml ファイルには直接指定していませんが,Bundle::Defaults によって Aggregator::Simple プラグインがデフォルトのプラグインとして読み込まれます.この Aggregator::Simple ですが,Subscription::Config プラグインが作った Plagger::Feed を基に,フィードの内容を解析し,その中に含まれているエントリを Plagger::Entry として Plagger オブジェクト自身に保持させるということをしています.たぶん.

Filter::Rule

Subscription::Config と Aggregator::Simple によって,ぼくのブログの各エントリが Plagger に取り込まれました.この中から SICP や database 関連エントリを削除しなければなりません.Hatena の出す RSS には dc:subject タグで,エントリがどのカテゴリに属するものかを表現しており,これは Plagger::Entry#tags でアクセスすることができます.後は,こういう要らないエントリを消すプラグインがあればいいのですが...
と,ここで Filter::Rule に以下の記述を見つけます.

This module strips entries and feeds using Rules. It's sort of like SmartFeed, but while SmartFeed B new feed using Rule, Filter::Rule strips entries and feeds that don't match with Rules.

Filter::Rule を使えば,望みの動作を実現できそうです.でもこのモジュール,どうやって使うんでしょうか?

Plagger::Rule

Plagger におけるプラグインの親玉 (親クラス) は Plagger::Plugin なんですけど,このコンストラクタを見ると,yaml ファイル中で rule オプションを取れるようになっています.つまりどんなプラグインであっても,rule を指定できるということですね.

# Plagger::Plugin#new
sub new {
    my($class, $opt) = @_;
    my $self = bless {
        conf => $opt->{config} || {},
        rule => $opt->{rule},
        rule_op => $opt->{rule_op} || 'AND',
        rule_hook => '',
        meta => {},
    }, $class;
    $self->init();
    $self;
}

この rule については Plagger::Rule[s].pm を見ればよさげ.Plagger::Rule.pm が一つのルールであり,複数のルールをどう組合せるかを Plagger::Rules.pm で制御するという形式の模様.rule を指定すると必ず Plagger::Rules が生成されるようになっています.
そして,Plagger からフック関数が呼ばれる度に,Plagger::Rules#dispatch が呼ばれます.dispatch が true と解釈される値を返せば,実際にフック関数が実行され,false と解釈される値を返せばフック関数は実行されません.プラグインが提供する機能はフック関数として Plagger に提供されるので,dispatch が false を返すと,その機能は実行されないことになります.

# Plagger#run_hook
sub run_hook {
    my($self, $hook, $args, $once, $callback) = @_;

    my @ret;
    for my $action (@{ $self->{hooks}->{$hook} }) {
        my $plugin = $action->{plugin};
        if ( $plugin->rule->dispatch($plugin, $hook, $args) ) {
            my $ret = $action->{callback}->($plugin, $self, $args);
            $callback->($ret) if $callback;
            if ($once) {
                return $ret if defined $ret;
            } else {
                push @ret, $ret;
            }
        } else {
            push @ret, undef;
        }
    }

    return if $once;
    return @ret;
}

ところで,ルールって具体的にどう定義すんの?ってのが次の課題.

Plagger::Rule::EntryTag

そしたら見つけてしまった.したいこと (SICP,database 等のエントリを削除する) そのものを実現する Plagger::Rule::EntryTag である!
重要なのは,Plagger::Rule::EntryTag#dispatch メソッドのこの部分ですね.

    for my $want (@{$self->{tag}}) {
        push @bool, $entry->has_tag($want);
    }

    Plagger::Operator->call($self->{op}, @bool);

@{$self->{tag}} は yaml 中で指定した tag の配列になってます.対象となっている entry がそのタグを持っていれば @bool に push してやるという話.これまでの話ですと,SICP とか database とかをタグ (カテゴリ) として持つエントリについては,dispatch で false を返させれば良いですから,Filter::Rule の op には 'NOR' を渡してやればいいですね.SICP やら database やら cisco やらのカテゴリが一つでも含まれていれば,dispatch から 0 が返されます.

Publish::Feed

あとは,残ったエントリを Publish::Feed プラグインで RSS としてファイルに書き出し,mixi の 「RSS の URL」にそのファイルの URL を指定してやればおk.これを cron で回してやれば,マイミクにスパマー判定されないで済むはず.

*1:明示的にと書いたのは,デフォルトで読み込まれるプラグインがあるためです.Summary::Simple とか