コマンドラインからXMPPにメッセージを投げてみる
環境であれこれ
昨晩ちょっと試していたら,ppc mac, osx 10.4 + perl 5.10の環境だとAnyEventモジュールがインストールできませんでした.POE::Component::JabberもTestはすべてOKなんだけど,プロセスが終了時に255を返してると文句を言って来るのでforce insatallしないとインストールできません.オフィスのintel mac, osx 10.4 + perl 5.8.8 だと両方ともインストールできたので,perl5.10に起因する問題かもしれません.
昔作ったirc-notify
以前notify_irc.plというスクリプトを作りました.これは,IRCに常駐するサーバスクリプトと,コマンドラインからサーバ経由でIRCにメッセージを投げるクライアントスクリプトからなるシステムです.普段便利に使っているので,これのXMPP版を作ってみようと思いました.POE::Component::JabberはexampleフォルダにあるXMPPClientというスクリプトでGoogleTlakにアクセスできたので,これをベースにしています.起動すると
Reference is already weak at /usr/local/lib/perl5/site_perl/5.8.8/POE/Filter/XML.pm line 82.
という警告がでますが,今の所理由と対処方法がわからないのでほったらかしています.
クライアント
クライアントスクリプトはこんな感じになります.起動時の引数でメッセージの投げ先とメッセージ本体を指定しています.
#!/usr/local/bin/perl use strict; use warnings; use Encode qw (_utf8_on); use POE::Component::IKC::ClientLite; my $r = POE::Component::IKC::ClientLite::create_ikc_client( port => 9999, ip => "localhost", timeout => 5, ) || die "create_ikc\n"; my $to = $ARGV[0]; my $text = $ARGV[1]; _utf8_on($text); $r->post( 'notify_jabber/update', { TO => $to, TEXT => $text } );
即興で作ったので美しくないですが,やりたいことはわかっていただけるでしょうか.
サーバ
サーバ側スクリプトは以前のnotify_irc.plと同様にconfig.yamlから設定を読み込んで動きます.config.yamlは以下のように構造になります.
--- notify_jabber_daemon_host: localhost notify_jabber_daemon_port: 9999 notify_jabber_server_ip: foo.hoge.co.jp notify_jabber_server_port: 5222 notify_jabber_server_hostname: foo.hoge.co.jp notify_jabber_server_username: bot notify_jabber_server_password: PASSWD notify_jabber_server_resource: jabberBot
「daemonなんとか」というキーはIKCに関連するパラメータで,「serverなんとか」というキーがXMPPに関連するパラメータです."notify_jabber_server_ip"と"notify_jabber_server_hostname"に同じ値を設定していますが,前者はサーバのIPアドレスか,サーバの名称を記入し,後者はXMPPのメッセージ中に含まれるJIDのホスト名を記入します.一般的には同じ値が入る事になるでしょう.
で,サーバ本体は以下のようになります.error_eventは元のスクリプトそのままです.POE::Component::Jabber::new()をbot_start()の中で実行しているのは,new()がcurrent Sessionを要求するためです.つまり,POE::Session::create()を実行したときに最初に遷移する,_start状態の中で呼び出しています.
#!/usr/local/bin/perl use POE qw( Filter::XML::Node Filter::XML::Utils Component::IKC::Server Component::IKC::Specifier Component::Jabber Component::Jabber::Error Component::Jabber::ProtocolFactory Component::Jabber::Status Session ); use POE::Filter::XML::NS qw/ :JABBER :IQ /; use warnings; use strict; use Term::ANSIColor qw(:constants); sub msg (@) { print GREEN, BOLD, " * ", RESET, "@_\n" } sub err (@) { print RED, BOLD, " * ", RESET, "@_\n" } msg 'loading configureation'; require YAML; my $config = { %{ YAML::LoadFile('config.yaml') || {} }, }; msg 'creating daemon component'; POE::Component::IKC::Server->spawn( port => $config->{notify_jabber_daemon_port}, name => 'NotifyJabberBot', ); msg 'creating kernel session'; my $server = POE::Session->create( inline_states => { _start => \&bot_start, status_event => \&status_event, error_event => \&error_event, update => \&update_event, } ); msg 'starging the kernel'; POE::Kernel->run(); msg 'exiting'; exit 0; sub bot_start { my ( $kernel, $heap ) = @_[ KERNEL, HEAP ]; msg 'creating server component'; $heap->{'component'} = POE::Component::Jabber->new( IP => $config->{notify_jabber_server_ip}, Port => $config->{notify_jabber_server_port}, Hostname => $config->{notify_jabber_server_hostname}, Username => $config->{notify_jabber_server_username}, Password => $config->{notify_jabber_server_password}, RESOURCE => $config->{notify_jabber_server_resource}, Alias => 'bot', ConnectionType => +XMPP, States => { StatusEvent => 'status_event', InputEvent => 'input_event', ErrorEvent => 'error_event', } ); msg "starting jabber sesion"; $kernel->alias_set('notify_jabber'); $kernel->call( IKC => publish => notify_jabber => ['update'] ); $kernel->post( 'bot', 'connect' ); } sub status_event() { my ( $kernel, $sender, $heap, $state ) = @_[ KERNEL, SENDER, HEAP, ARG0 ]; if ( $state == +PCJ_INIT_FINISHED ) { $heap->{'jid'} = $heap->{'component'}->jid(); $heap->{'sid'} = $sender->ID(); $kernel->post( 'bot', 'output_handler', POE::Filter::XML::Node->new('presence') ); $kernel->post( 'bot', 'purge_queue' ); } } sub update_event() { my ( $kernel, $heap, $msg ) = @_[ KERNEL, HEAP, ARG0 ]; my $node = POE::Filter::XML::Node->new('message'); $node->attr( 'to', get_bare_jid( $$msg{TO} ) ); $node->attr( 'type', 'chat' ); $node->insert_tag('body')->data( $$msg{TEXT} ); $kernel->post( $heap->{'sid'}, 'output_handler', $node ); } sub error_event() { my ( $kernel, $sender, $heap, $error ) = @_[ KERNEL, SENDER, HEAP, ARG0 ]; if ( $error == +PCJ_SOCKETFAIL ) { my ( $call, $code, $err ) = @_[ ARG1 .. ARG3 ]; err "Socket error: $call, $code, $err"; err "Reconnecting!"; $kernel->post( $sender, 'reconnect' ); } elsif ( $error == +PCJ_SOCKETDISCONNECT ) { err 'We got disconneted'; err 'Reconnecting!'; $kernel->post( $sender, 'reconnect' ); } elsif ( $error == +PCJ_CONNECTFAIL ) { err 'Connect failed'; err 'Retrying connection!'; $kernel->post( $sender, 'reconnect' ); } elsif ( $error == +PCJ_SSLFAIL ) { err 'TLS/SSL negotiation failed'; } elsif ( $error == +PCJ_AUTHFAIL ) { err 'Failed to authenticate'; } elsif ( $error == +PCJ_BINDFAIL ) { err 'Failed to bind a resource'; } elsif ( $error == +PCJ_SESSIONFAIL ) { err 'Failed to establish a session'; } }
おわりに
今回のスクリプトは相手に直接メッセージを送ることしかできません.相手がloginしてるかどうかの確認もしていませんし,グループチャットにメッセージを送ることもできません.そこまでやろうとするとPOE::Component::Jabberでは役不足でNet::XMPP2を使う方が色々ツールがそろっています.でもAnyEventよくわかってないので,PoCoみたいに機能単位でモジュール化がすでに行われているのかどうかがわかりません.機能単位のモジュールがあれば,ここで作ったようなスクリプトもすぐにできるでしょうね.飽きなければ,どっちかのモジュールを使ってもうすこし高機能なスクリプトを作ってみたいと思ってます.