Twitterのアイコンに自動で文字入れたりするPlagger

Plagger+ImageMagick+WWW::Mechanize といったモジュールの組みあわせで, 「○○ なう」とつぶやいたら,アイコン画像に「○○」を書き込んで自動で Twitter に送信するという Plagger モジュールを書きました.ただ,あんまりやりすぎると Twitter から規制が入る予感もあるので,あまり行儀のいいものではありません.

Plagger::Plugin::Publish::Icontter

使い方

riywo20090225061218 後述の YAML ファイルみたいに設定した Plagger を定期的(僕は 2 分おき)に 回していること前提で,自分の Twitter で

  • 「自宅なう」

と発言すれば,アイコンの下に「自宅」と書き込んで自動でアップロードしてくれます. その他にもソースを見てもらえばいろいろあることがわかりますし,いくらでも 追加できます. riywo20090225051029 riywo20090225064822

  • おれ爆発しろ

    • 爆発します
  • おはよう

    • 起床します
  • 帰宅

    • 帰宅します
  • おやすみ

    • 睡眠します
  • ダメだ

    • ダメ人間です
  • アイコン戻す

    • 元に戻します

メモ書き

いろいろ BK が入ってます w まず,文字サイズの決定のために半角文字数相当を 計算するところでは,UTF-8 なので文字数とバイト数を数えて足して 2 で割ってます. こんなんでいいんだろうか w

で,文字サイズの計算はマジックナンバー入れてます.自分の使ってるアイコン サイズ(368px × 394px)でいい感じのくらいです.使うなら適当に調整してください. こういうの苦手なので><

フォントのパスの指定は自分の環境に合わせて好きにやってください.

それから,Plagger のフックポイントが大事で,publish.feed にフックしています. こうすることで,自作の Publish::Twtter2HatenaDiary みたいにエントリ毎に 処理をしてほしいプラグインと一つの YAML で使うことができます.つまり,

  • Feed でまとめて 1 処理なら publish.feed
  • Entry ごとに処理するなら publish.entry

というフックをさせれば良いようです.他にも Plagger のフックは調べた方が良さそうだ.

その他,無名関数や List::Util などこれまで使ってなかったことをやったので いろいろ勉強になりました.また,ImageMagick は使いにくいとの噂でしたが, まぁそれなりに歴史があるので Perl と同じで WEB に知識がたくさん落ちてるのが いい感じでした.いちいちドキュメント読まなくてもなんとなくまとめページを いくつか見てれば使えました.

lib/Plagger/Plugin/Publish/Icontter.pm

package Plagger::Plugin::Publish::Icontter;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use WWW::Mechanize;
use Image::Magick;
use utf8;
use List::Util qw/max/;
use bytes ();
use DateTime;

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

sub initialize {
    my($self, $context) = @_;
    $self->{mech} = WWW::Mechanize->new;
    $self->{mech}->agent_alias('Linux Mozilla');
    $self->{mech}->get('http://twitter.com');
    $self->{mech}->submit_form(
        form_number => 2,
        fields => {
            'session[username_or_email]' => $self->conf->{username},
            'session[password]' => $self->conf->{password}});
    $self->{base_file} = $self->conf->{basefile};
    my $dt = DateTime->now(time_zone => 'local');
    my $date = $dt->strftime('%Y%m%d%H%M%S');
    $self->{upload_file} = $self->conf->{basefile};
    $self->{upload_file} =~ s/^(.+?)(\..{3})$/$1${date}$2/;
}

sub publish_feed {
    my($self, $context, $args) = @_;
    return unless $self->{mech};

    my @case = (
        {
            reg => '^アイコン戻|^アイコンもど',
            prc => sub{$self->{upload_file} = $self->{base_file};}
        },
        {
            reg => '^帰宅',
            prc => sub{$self->annotate($self->{base_file}, 'pink', '帰宅', 'しました');},
        },
        {
            reg => '^俺爆発|^おれ爆発',
            prc => sub{$self->annotate($self->{base_file}, 'red', '爆発');},
        },
        {
            reg => '^おはよう',
            prc => sub{$self->annotate($self->{base_file}, 'pink', '起床');},
        },
        {
            reg => '^おやすみ',
            prc => sub{$self->annotate($self->{base_file}, 'blue', '睡眠');},
        },
        {
            reg => '(.+?)なう[\..。!]{0,1}$',
            prc => sub{$self->annotate($self->{base_file}, 'white', $1);},
        },
        {
            reg => '^ダメだ',
            prc => sub{$self->swirl($self->{base_file});
                       $self->annotate($self->{upload_file}, 'yellow', 'ダメ', '人間');},
        }
    );

    $context->log(debug => "Icontter Search...");
    foreach my $entry (reverse $args->{feed}->entries){
        my $post = $entry->title_text;
        $post =~ s/@.+? //g;

        foreach my $check (@case){
            my $reg = $check->{reg};
            if($post =~ /${reg}/){
                $check->{prc}->();
                $self->{mech}->get('http://twitter.com/account/picture');
                $self->{mech}->submit_form(
                    form_number => 1,
                    fields => {'profile_image[uploaded_data]' => $self->{upload_file}}
                );
                $context->log(debug => "Upload " . $self->{upload_file});
                return;
            }
        }
    }
    $context->log(debug => "No Update");
}

sub annotate {
    my($self, $src, $f_color, @line) = @_;
    my $image = Image::Magick->new;
    $image->Read($src);
    my $max_length = max (map {(length($_) + bytes::length($_))/2} @line)/2;
    $" = '\n';
    my $text = "@line";

    my $sazanami = '/usr/share/fonts/truetype/sazanami/sazanami-gothic.ttf';
    my $mona = '/usr/share/fonts/truetype/mona/mona.ttf';
    my $kochi = '/usr/share/fonts/truetype/kochi/kochi-gothic.ttf';
    my $font = $kochi;

    my ($width, $height) = $image->Get('width', 'height');
    my $pointsize = int(($width-30)/$max_length);
    my $y = $height-30-($pointsize*$#line);
    my $b_color = 'black';
    my $f_width = int((7/90)*$pointsize);
    my $b_width = int((14/90)*$pointsize);
    $image->Annotate(text => $text, stroke => $b_color, fill => $b_color,
                     font => $font, pointsize => $pointsize, strokewidth => $b_width,
                     x => 15, y => $y, encoding =>'UTF-8');
    $image->Annotate(text => $text, stroke => $f_color, fill => $f_color,
                     font => $font, pointsize => $pointsize, strokewidth => $f_width,
                     x => 15, y => $y, encoding =>'UTF-8');
    $image->Write($self->{upload_file});

    undef $image;
}

sub swirl {
    my($self, $src) = @_;
    my $image = Image::Magick->new;
    $image->Read($src);
    $image->Swirl(degrees => 400);
    $image->Write($self->{upload_file});
    undef $image;
}

sub copy {
    my($self, $src) = @_;
    my $image = Image::Magick->new;
    $image->Read($src);
    $image->Write($self->{upload_file});
    undef $image;
}

1;

icontter.yaml

レシピはこんな感じです.いつも通りの base.yaml を読んだあと, Twitter の RSS を読んで,Deduped で一度処理した奴を処理しないようにします. ちなみにここでは DB_File_URL というのを使っています.

日付を消すというハックもあるようですが,後で日付使うので無しで, Twitter の WEB からスクレイプする場合と相互に DB を使えるようにするために 一応入れてますが,まだそんなことやってません w

Filter::RemoveIDFromTwitterRSS というフィルタは,単に Twitter の RSS から 発言者 ID を削除して,Post 内容にする(+改行を削除)だけの Filter です. 一応ソースも下の方に載せておきます.これで,Entry の Title と Body に 発言内容が入った状態なので,Icontter や Twitter2HatenaDiary に流せます (Twitter2HatenaDiary から Filter 分は削除してます).

アイコン変えたいだけなら,はてダ投稿部分はまるまる消せば OK. あとは cron で定期的に回してあげるだけ.わーい

ちなみに食べるフィードを TwitterSearch で検索した自分向けの 返信とかにすると,誰かに「爆発しろ」と言われたら爆発する,とかも 可能ですが,これと同時に走らせると多分アップロードの部分で なんかおかしなことが起こるっぽいので,僕はやってません.単独なら 大丈夫なはず.この辺,Rule とか使えば一つの YAML でできると 思ったのですが,Rule の使い方がついに良くわからず断念しました w

include:
  - /home/user/yaml/base.yaml

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

  - module: Filter::Rule
    rule:
      module: Deduped
      engine: DB_File_URL
      path: /home/user/Dropbox/cache/plagger-mytwitter.db
  - module: Filter::Reverse
  - module: Filter::RemoveIDFromTwitterRSS

  - module: Publish::Icontter
    config:
      basefile: /home/user/riywo.png
      username: user
      password: ********

  - module: Publish::Twitter2HatenaDiary
    config:
      username: user
      password: ********

Plagger::Plugin::Filter::RemoveIDFromTwitterRSS

lib/Plagger/Plugin/Filter/RemoveIDFromTwitterRSS.pm

package Plagger::Plugin::Filter::RemoveIDFromTwitterRSS;
use strict;
use base qw( Plagger::Plugin );

use Encode;

use utf8;
use DateTime;
use DateTime::Format::Strptime;
use DateTime::Format::HTTP;

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

sub filter {
    my ($self, $context, $args) = @_;
    my $entry_title = $args->{entry}->title_text;
    $entry_title =~ s/\r|\n//g;
    $entry_title =~ s/^.+?: //o;

    $args->{entry}->title($entry_title);
    $args->{entry}->body($entry_title);
}

1;

Twitter のアイコン画像について

Twitter のアイコンは現在 Amazon のクラウドを使っています.ランダム?な 数字の入った URL であり,またローカルのファイル名がそのまま使われます.

で,同じファイル名だとなんか割とおかしなことが発生しがちだったので アップロードするファイル名に日付を付加しています.

何がよく発生するかというと,オリジナルファイルはきちんとアップロード できているのに,サムネイルが変更されないことですね.サムネイルの URL が更新前の画像のままだったり,新しいのは作成されなかったり, なんか結構不安定でした.

まぁ頻繁に変更しなければ,それなりに期待通り動作してくれるようなので しばらく回しておきます.

ちなみに,モバツイなどのクライアントではアイコンを結構長くキャッシュしている ので変更が反映されませんが,そんなの僕にはどうにもできません w

おわりに

募集するのは以下の 2 つ

  • おもしろコマンド

    • 文字だけじゃなくて,画像処理もできるのでなんかあれば.

      • とりあえず「クリスマスだ」と言ったらクリスマス仕様になる,くらいは書きます.
  • Perl の間違いなど

    • 突貫工事で書いてるのでひどいソースです.Perl 界隈のエロい人,直して w

以上おわり.おもしろかった.

  • otsune 09-02-25 (水) 18:53

    これと同時に走らせると多分アップロードの部分で なんかおかしなことが起こるっぽいので,僕はやってません.単独なら 大丈夫なはず.この辺,Rule とか使えば一つの YAML でできると 思ったのですが,Rule の使い方がついに良くわからず断念しました w

    plagger のプラグイン一つに何でもかんでも全機能を詰め込むのではなく、「Twitter アイコンを投稿する Publish::TwitterIcon」と「画像に文字を埋め込む Filter::Image なんちゃら」と分けて書けば、あとは Rule なり yaml なりで対処できるのかなぁ。と思いました。

  • riywo 09-02-25 (水) 23:51

    > otsune さん

    さすが otsune さん,というか,なんか前も同じ様なことを
    言われた気がします w

    確かに一つのプラグインに詰め込みすぎると後で苦労しますね.
    参考にさせて頂きます,ありがとうございました.