【Lifehacks】mixiに投稿する

■タスク
 ファイルに記述したメッセージをmixiに投稿する。


■背景
・残念ながらmixiでは日記等への投稿APIを公開していません。
PerlWWW::Mixiモジュールなど、有志によるAPIモジュールが存在しますが、mixiの通常ウェブ画面操作を元に作り上げているため、mixiの使用変更に追従できず使用不可に陥ります。
・2007.06.28、mixiは常駐型クライアントソフトウェア「mixi station」に新着通知機能等を追加しましたが、これに目をつけたユーザがサーバとの通信を解析した結果、一般的なWebAPIであるAtomPubに準拠していることが判明しました。非公開ながら安定性の高いAPIとして注目を集めました。
・2008.07.30、mixiiPhoneから日記投稿等が可能なソフトウェア「mixi for iPhone」を発表。
 上記と同様に通信を解析し、AtomPub準拠で日記投稿を行う方法が判明しました。
・このAPIを利用してごく初歩的な投稿プログラムを作成してみました。


■参考
mixiのあしあとAPI発掘
http://ido.nu/kuma/2007/06/29/mixi%E3%81%AE%E3%81%82%E3%81%97%E3%81%82%E3%81%A8api%E7%99%BA%E6%8E%98/
mixi notifyにはAtomが使われている
http://blog.fkoji.com/2007/06290200.html
たけまる / mixi station API
http://teahut.sakura.ne.jp/b/2007-07-01-1.html
mixi に写真をアップロードする
http://blog.8-p.info/articles/2007/06/29/mixi
mixi for iPhoneから発掘されたmixi日記投稿用API
http://ido.nu/kuma/2008/07/30/digging-mixi-for-iphone-application-and-new-api-for-posting-a-diary-with-a-photo/
mixiステーション2.2.1で追加されたAPIの発掘
http://ido.nu/kuma/2007/08/01/two-more-api-found-in-mixi-station-221/


■仕様
・入力ファイルの文字コードShift_JISとします。
・入力ファイルは以下の形式としています。
 - 1行目:タイトル。先頭に「【…】」を含むこと。
 - 2行目:空行。


■使用方法
> perl SgPostMixi.pl < Input.txt > Result.txt


■プログラム

#
# SgPostMixi.pl
# mixiに投稿する。
#
# Author: Orihika Ikuo
# Create: 2009.06.26
# Update: 2009.XX.XX
#

##### Pragma
use strict;
use warnings;
use utf8;

##### Module
use Encode;
use URI;
use URI::Escape;
use LWP::Authen::Wsse;
use LWP::UserAgent;

##### Setting
our $MIXI_URI                = 'mixi.jp:80';
our $USR                     = 'USER@DOMAIN';
our $PWD                     = 'XXXXXXXX';
our $MEMBER_ID               = 'XXXXXXXX';
our $UA_NAME                 = 'SgPostMixi/1.0 ';
our $KEEP_ALIVE              = 4;
our $DIARY_URI               = "http://mixi.jp/atom/diary/member_id=$MEMBER_ID";
our $ENC_SJIS                = ':encoding(shiftjis)';
our $ENC_UTF8                = ':encoding(utf8)';

##### Constant
our $ERR_ILL_FORMAT          = "ERR: Illegal format.";
our $ERR_ILL_TITLE           = "ERR: Illegal title.";
our $ERR_SEP_NOT_EXIST       = "ERR: Separator is not there.";
our $ERR_ILL_MSG             = "ERR: Illegal message.";
our $RET_SUCCESS             = 'RET: Success.';
our $RET_FAILURE             = 'RET: Failure.';
our $CTYPE_JPG               = 'image/jpeg',
our $CTYPE_ATOM              = 'application/atom+xml',

##### Global
binmode STDIN,  $ENC_SJIS;
binmode STDOUT, $ENC_UTF8;

##### Main routine
&main();

sub main {
  my $ua;
  my $req;
  my $res;
  my $line;
  my $title;
  my $msg;
  my $content;
  my %param_asc;

  # メッセージの読み込み
  ($title, $msg) = SgMxReadSrcTxt(); 

  # 投稿の準備
  $ua = LWP::UserAgent->new(
    agent      => $UA_NAME,
    keep_alive => $KEEP_ALIVE
  );
  $ua->credentials($MIXI_URI, "", $USR, $PWD);

  $content = <<SG_MX_CONTENT;
<?xml version='1.0' encoding='utf-8'?>
<entry xmlns='http://www.w3.org/2007/app'>
  <title>$title</title>
  <summary>$msg</summary>
</entry>
SG_MX_CONTENT

  %param_asc = ('Content-Type' => $CTYPE_ATOM,
                'content'      => encode('utf8', $content)
               );

  # 投稿の実行
  $res = $ua->post($DIARY_URI, %param_asc);

  # 返信を標準出力に出力
  if ($res->is_success) {
    print $RET_SUCCESS,      "\n";
    print $res->status_line, "\n";
    print $res->content,     "\n";
    print "\n";
  } else {
    print $RET_FAILURE,      "\n";
    print $res->status_line, "\n";
    print $res->content,     "\n";
    print "\n";
  }
}

sub SgMxReadSrcTxt {
  my @lines;
  my $title;
  my $line_2nd;
  my $msg;

  @lines = <STDIN>;

  if (@lines < 3) {
    die $ERR_ILL_FORMAT;
  }

  $title = shift(@lines);
  chomp($title);

  unless ($title =~ /^【.+/) {
    die $ERR_ILL_TITLE;
  }

  $line_2nd = shift(@lines);
  chomp($line_2nd);

  if ($line_2nd ne "") {
    die $ERR_SEP_NOT_EXIST;
  }

  $msg = join('', @lines);
  if ($msg eq "") {
    die $ERR_ILL_MSG;
  }

  return $title, $msg;
}