Notify::XMPP 0.03

変更点

  • コネクション張るのを1回だけにした。(0.02までは、entryごとに接続/切断を繰り返してたのでした)
  • Google Talkに接続できるようにした。ただし、XML::Streamをちと変更する必要あり(後述)
  • pod書いた。


Google Talkのアカウントで接続するときはXML::Streamをちと変更する必要があります。

その理由はこうです。

Google Talkの認証はSASLのPLAINで行われます。(starttlsが必須なためか、CRAM-MD5とかは対応してない)

んで、PLAINの認証は、

[authorize-id] NUL authenticate-id NUL password

base64したものをサーバに送ります。

authorize-idは、「username@servername」の形式で、Google Talkの場合は「hirose31@gmail.com」になってないと認証が失敗します。

でですね、Google Talkに繋げる場合は、サーバ名はtalk.google.comで、gmail.comじゃないんです。なので、XML::Streamが作るauthorize-idは「hirose31@talk.google.com」となり、(ユーザ名とパスワードが正しくても)認証に失敗しちゃうんです。

ほんでどうしたかというと、Net::XMPP::COnnection::Connectでcomponentname => 'gmail.com'を指定すると、それがXML::Streamでは$self->{SIDS}->{$sid}->{to}で取り出せるので、{to}がある場合はそれをauthorize-idのservernameんところに使うようにしたわけです。

ちなみに仕様上authorize-idは省略可能で、省略した場合でもGoogle Talkの認証は通るんですが、XML::Streamでそれを省略させるオプションがないんす。

ただですね、これは繋げるアカウントのお話で、jabber.orgとGoogle Talkは相互接続しているので、jabber.orgのアカウントで接続してもGoogle Talkな人にメッセージは送れます。なので、XML::Streamいじらなくても、Google TalkにNotifyすることはできます。

--- Stream.pm.orig	2004-08-25 23:23:32.000000000 +0900
+++ Stream.pm	2006-08-17 19:00:07.000000000 +0900
@@ -2113,7 +2113,7 @@
     
     my $sasl = new Authen::SASL(mechanism=>join(" ",@{$mechanisms}),
                                 callback=>{
-                                           authname => $username."@".$self->{SIDS}->{$sid}->{hostname},
+                                           authname => $username."@".($self->{SIDS}->{$sid}->{to} or $self->{SIDS}->{$sid}->{hostname}),
 
                                            user     => $username,
                                            pass     => $password
package Plagger::Plugin::Notify::XMPP;
our $VERSION = '0.03';
use strict;
use warnings;
use base qw( Plagger::Plugin );

use Encode;
use Net::XMPP;

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

sub initialize {
    my ($self, $context, $args) = @_;
    $context->log(debug => ">>initialize");

    my $connectattempts = 3;
    my $connectsleep    = 1;

    $self->{connected} = 0;
    $self->{client} = Net::XMPP::Client->new(debuglevel=>0);

    my $jid = $self->conf->{jid} || do {
        $context->log(error => "missing: jid");
        return;
    };
    unless (index($jid, '@') >= 1) {
        $context->log(error => "missing: jid");
        return;
    }
    my ($username, $componentname) = split /@/, $jid;
    my $password    = $self->conf->{password} || do {
        $context->log(error => "missing: password");
        return;
    };

    my $hostname = $self->conf->{server_host} || $componentname;
    my $port     = $self->conf->{server_port} || '5222';
    my $tls      = $self->conf->{tls} || 0;
    unless ($self->conf->{to}) {
        $context->log(warn => "missing: to");
        return;
    }

    $context->log(debug => "hostname=$hostname port=$port tls=$tls componentname=$componentname");

    my ($status, $error);
    while (--$connectattempts >= 0) {
        $status = $self->{client}->Connect(
            hostname      => $hostname,
            port          => $port,
            tls           => $tls,
            componentname => $componentname,
           );
        last if defined $status;
        $context->log(warn => "retry[$connectattempts]");
        sleep $connectsleep;
    }
    unless (defined $status) {
        $context->log(error => "connection failure");
        return;
    }

    ($status,$error) = $self->{client}->AuthSend(
        username => $username,
        password => $password,
        resource => 'Plagger',
       );
    unless ($status and $status eq 'ok') {
        $context->log(error => "authentication failure");
        return;
    }

    $self->{connected} = 1;
}

sub notify {
    my ($self, $context, $args) = @_;
    return unless $self->{connected};
    $context->log(debug => ">>notify");

    my $body = $self->templatize('xmpp_notify.tt', $args);
    utf8::decode($body) if utf8::is_utf8($body);

    foreach my $a_to (@{$self->conf->{to}}) {
        $context->log(info => "Notifying <" . $args->{entry}->title . "> to $a_to");
        $self->{client}->MessageSend(
            to   => $a_to,
            body => $body,
           );
    }
}

sub finalize {
    my ($self, $context, $args) = @_;
    return unless $self->{connected};
    $context->log(debug => ">>finalize");
    $self->{client}->Disconnect();
}

1;

__END__

=head1 NAME

Plagger::Plugin::Notify::XMPP - Notify feed updates to XMPP client

=head1 SYNOPSIS

jabber.org

  - module: Notify::XMPP
    config:
      jid: foo@jabber.org
      password: plahplahplah
      to:
        - bar@jabber.org
        - baz@gmail.com
        - qux@jabber.jp

Google Talk

  - module: Notify::XMPP
    config:
      jid: foo@gmail.com
      password: plahplahplah
      server_host: talk.google.com
      tls: 1
      to:
        - bar@jabber.org
        - baz@gmail.com
        - qux@jabber.jp

=head1 DESCRIPTION

This plugin allows you to notify feed updates to XMPP clients (jabber,
Google Talk) using Net::XMPP.

=head1 AUTHOR

HIROSE Masaaki (hirose31)

=head1 SEE ALSO

L<Plagger>, L<Net::XMPP>

=cut

# for Emacsen
# Local Variables:
# mode: cperl
# cperl-indent-level: 4
# cperl-indent-parens-as-block: t
# indent-tabs-mode: nil
# coding: euc-jp
# End:

# vi: set ts=4 sw=4 sts=0 :

テンプレートは0.02んときid:hirose31:20060815:1155646327と同じのでOK。

POE::Component::Jabberとか使えば、Notify::IRCみたいに常駐できるかも。ただ、IMなんで一発送ってハイさようならでもいい気がする(プロセス常駐させなくていいし)んだけどどうすかねぇ。