メールからtwitterに投稿。画像添付にも対応(はてなフォトライフ利用)

kujoo2009-07-05



ってのをやってみました。

メールの内容は標準入力(STDIN)に渡される前提

#!perl
use strict;
use warnings;
use utf8;
use Encode;
use Email::MIME;
use Email::MIME::ContentType;
use Email::MIME::XPath;
use Config::Pit;
use WebService::Hatena::Fotolife;
use Net::Twitter;

  my $message = join '', <STDIN>;
  unless($message) { die 'no message error' }

  my $email = Email::MIME->new($message);

  my @image_parts = $email->xpath_findnodes('//*[@content_type=~"^image/"]');
  my($text_part,) = $email->xpath_findnodes('//*[@content_type=~"^text"][1]');

  my $from    = $email->header('from') || die 'no header-from error';
  my $subject = $email->header('Subject');
  chomp($subject, $from);
  my $post    = $subject;
  my $caption = $subject;

  if($text_part) {
    my $body = Encode::decode(
      parse_content_type($text_part->content_type)->{attributes}->{charset},
      $text_part->body,
    );
    $body =~ s/(\r\n|\r|\n)//g;
    $post   .= $body;
    $caption = $body;
  }

  foreach(@image_parts) {
    $post .= ' '.&post_image($caption, \$_->body);
  }

  $post = &update_message($post, $from);

  exit;

####

sub update_message {
  my($msg, $from) = @_;
  unless($msg || $from) { return; }

  my $config = pit_get("twitter.com", requires => {
    username  => "on twitter.com",
    password  => "on twitter.com",
    mail_from => "on twitter.com",
  });
  unless($from =~ /^([\s\S]*<)?$config->{mail_from}>?$/i) { return; }

  my $twit = Net::Twitter->new({
    username => $config->{username},
    password => $config->{password},
  });

  return $twit->update($msg);
}

sub post_image {
  my($title, $imageref) = @_;
  unless($imageref) { return; }

  my $config = pit_get("hatena.ne.jp", requires => {
    id       => "on hatena.ne.jp",
    password => "on hatena.ne.jp",
  });

  my $fotolife = WebService::Hatena::Fotolife->new();
  $fotolife->username($config->{id});
  $fotolife->password($config->{password});

  my $EditURI = $fotolife->createEntry(
    title     => $title,
    scalarref => $imageref,
  );
  unless($EditURI) { return; }

  my $baseuri = 'http://f.hatena.ne.jp/';
  my $regex_i = '^'.$baseuri.'atom/edit(/([\d]{8})([\d]{6}))$';
  if($EditURI =~ m/$regex_i/) {
    return $baseuri.$config->{id}.$1;
  }
}

んー。とりあえずは動いてますけどね・・・。


Email::MIME::XPath は便利

メールの取り扱いはEmail::MIMEで。これがこのスクリプトの主題(の割には拘って無いな・・・)。

ファイル添付されるとEmail::MIME->bodyで本文取れない場合があるなあ・・・とか初歩的な所で躓いていたら、Email::MIME::XPath を使えば超絶楽ということで、そのまんまいただいてきました。

  my @image_parts = $email->xpath_findnodes('//*[@content_type=~"^image/"]');
  my($text_part,) = $email->xpath_findnodes('//*[@content_type=~"^text"][1]');

これで添付ファイルしかも画像が取れて、マルチパートの最初のテキストも取れる、とか便利すぎる!!
と安心していたらOutlook(2007?)で添付した画像が取れない??? これ、んにゃ〜?と一瞬悩んだけどどうやらContent-Typeがapplication/octet-streamになっているようですね・・・。というわけで、これは無視しときますwww

('//*[@content_type=~"^application/octet-stream"]'てなXPath文で拾えるんですかね? XPath分かってないうえに試す気は無いですがw ・・・とわいえXPathは勉強しとかないとなあ)

送信元によって微妙に取れ方が違うのが気になりますが、とりあえず機能しているので放置。


WebService::Hatena::Fotolife も便利

なんですが・・・。

画像アップした後で($EditURIから?)どうやってその画像ページのURIを得るのかが分からんかった・・・。ドキュメント見てもチンプンカンプン(XML::Atom::Entryな値に情報入ってんの?)。いちいちフィード読んで取得とかは、無いよねー。うーん・・・。

というわけで汚くなってますw(最初画像のURIにしてたけどそれは必要ない罠〜)


そういえば Config::Pit

を今回は使ってます。

事前に ppit set "twitter.com" とか ppit set "hatena.ne.jp" を実行してアカウント情報を登録しておきます。アカウント情報をソースに直書きしなくていいのと呼び出すアカウントの切り替えができたりして便利。

以前使えなかったのはヤッパリ環境変数の設定が足りてなかったらしい。なのでこのスクリプトが呼ばれる前に環境変数を設定するだけのスクリプトが動くという汚い作りになってます〜(さくらインターネットレンタルサーバ環境でのメールドリブン処理ってシェルの初期化処理?って呼ばれないんかな?)。


そういえば Net::Twitter

って、Encode::encodeしてからupdateすると文字化けするようになった・・・? 前は大丈夫だったのになあ・・・。新しいバージョン入れたからかなあ・・・。

あっ!? でも、ひょっとしたら前のでは use utf8; して無かったからかなあ???
(Net::TwitterっていうかTwitter API自体がuse utf8してたらencodeしなくていいんですかね)


ふと思ったんだけど、updateだけだったら、

  my $ua = LWP::UserAgent->new();
  $ua->credentials('twitter.com:80', 'Twitter API', $config->{username}, $config->{password});
  my $response = $ua->post('http://twitter.com/statuses/update.json', [status => $msg]);

で Net::Twitter 使わなくても事足りたりする?($ua->credentialsはいらないかも? 確認してないけど)

確認したら更新できてなかったので直しておきました。 $ua->default_header('Authorization' => encode_base64("$config->{username}:$config->{password}")); でも認証通るような気がするんですが・・・よくわかりません〜。



ん〜。どうも正解が見えなくて、どう書くのが良いのか相変わらず分かんないのですが・・・動いてればいいのか?(ダメな気が)


はてなハイク

でも同じようなのを作ってたんですが・・・(メール投稿で良いじゃんってのは言わない約束)。

とりあえず、ハイクAPIで画像がアップできん!

ただ単にフォトライフにアップしてその画像ページをはてなフォトライフ)記法で書くだけだったら上述のようなフォトライフAPIを使用するので良いんだけど、ハイクAPIには画像のアップ機能もあるんでその挙動を見て、自前で画像をアップするかハイクAPIに任せるか決めようと思ってたんですけどね。

update部分はこんな感じにしてます。

  my %haiku = ("status" => $msg, "file" => $image, "source" => 'Test');
  my $ua = LWP::UserAgent->new();
  $ua->default_header('Authorization:' => encode_base64("$config->{id}:$config->{post_password}"));
  my $response = $ua->post('http://h.hatena.ne.jp/api/statuses/update.xml', \%haiku);

$ua->credentials('h.hatena.ne.jp:80', 'Hatena Haiku API', $config->{id}, $config->{post_password}); なんてのはいらないか・・・。

$imageには添付画像パートの->body_raw を渡してます。ここがダメなのかな? メール添付されてるヤツはきっとmultipart/form-dataエンコードされてるに違いないとか思ってるんですけどね。自前でmultipart/form-dataエンコードって具体的にどうやるんだろ?

content-typeをmultipart/form-dataにしとけばいいって問題じゃないよね。


うーん。わかんないことだらけだw


7/8 追記〜

えーと、ちょいと調べて HTTP::Request::Common も使用して以下のようにしてみました(とりあえずfileは有る前提)。

  my $ua = LWP::UserAgent->new();
  $ua->default_header(
    'Authorization:' =>
      MIME::Base64::encode("$config->{id}:$config->{post_password}"),
  );
  my $response = $ua->request(
    POST 'http://h.hatena.ne.jp/api/statuses/update.xml',
    Content_Type => 'form-data',
    Content => [
      status => Encode::encode('utf8', $msg),
      file   => [undef, 'photoimage',
        Content_Type => $type,
        Content_Transfer_Encoding => 'base64',
        Content => $image,
      ],
      source => 'Test',
    ],
  );

$image には foreach(@image_parts) の $_->body_raw を与えて、 $type には $_->{ct}->{discrete}.'/'.$_->{ct}->{composite} を与えてます。

なんとなくいいんじゃないの? とか自分で思ってみました・・・が、ダメです・・・。'400 failed: upload file' になっちゃいますね〜。

めんどくせーwww もうフォトライフAPI経由でいいやw


このラインの次の勉強はメールの送信でもやってみますか。(Email::Send あたり?)