plum IRCのログをMSNで送信

http://d.hatena.ne.jp/holidays-l/20061108/p1
に影響されて、./module/log/msn.plm つくった(><*)
bot側はメンバからの追加リクエスト (1)の状態でメッセージを送れるので、registorしただけでok
plumよいよー(゜∀゜) plagger-ircbotをあえて使わない(ぉ

todo:

  • ログインし続ける
  • 双方向通信できそう。やった
    • log.msn.proxy: yes が必要
    • 先頭 /でMSNで送るとコマンドとして解釈
    • 先頭チャンネルなら "チャンネル + スペース + メッセージ" のフォーマット で発言できる

%萌えたんくらぶ はぁはぁ さくらちゃん かわいいよなー

↑サンプル

      • デバック厨(1/1) まだあるんだぜw
      • セキュリティ強化セキュリティregex ($friendly . '!' . $chandle ) これはしないと危険だな...やった
      • mask 判定追加やった
      • 単体テストやった
        • デバック(0/1)
    • おそらくこちらもUTF8 -> JIS しないとダメやった
  • 日本語化けるのはmirandaのセイカ???(><*)Messagnr.exe いれたくねぇぇぇぇぇwebで確認 Bug 治す治った
  • バグ ネット
    • なんか放置してたら2度とログインできなくなる(ぉ
      • ERROR 800 (ユーザー名の変更を頻繁に行いすぎています。時間をおいて変更して下さい。)
  • 全世界ぷにろりうさみみ萌え
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><!-- $_ if 0; # -*- perl -*-
# ./module/log/msn.plm                          0.03.18
#
# http://plum.sourceforge.jp/                   2.33.2
# http://sourceforge.jp/projects/plum/files/    2.33.2
# http://plum.sourceforge.jp/                   2.33.1
#
# base code is module/log/daily.plm @ http://plum.madoka.org
# base code is sys/timer.plm @ http://plum.madoka.org (main_loop)
# DOC: http://www.kwasan.kyoto-u.ac.jp/~shimono/tempfiles/plum2.33.1/doc/readme.html
# base code is Plagger/Plugin/Publish/MSN.pm @ http://d.hatena.ne.jp/holidays-l/20061108/p1 (Net::MSN)
# --  cat /usr/local/libdata/perl5/site_perl/Net/MSN.pm
# --  cat /usr/local/libdata/perl5/site_perl/Net/MSN/Base.pm
# DOC: http://search.cpan.org/~djr/Net-MSN-1.022/
# msn-messenger-protocol でぐぐるといろいろ出てくる
# MSN-ERROR CODE: http://www.hypothetic.org/docs/msn/reference/error_list.php
# MSN-ERROR CODJ: http://regnessem.sourceforge.jp/help/error/protocolerror.html
# *** <MSN[error]> Switch Board closed the connection : 会議室が閉じたって感じ channelみたいなもん

# cpan> install Digest::SHA Hash::Merge LWP Net::MSN Encode
# $Id: MSN.pm,v 3.18 2999 oreoreoredayoore Exp $
package log_msn;

use vars qw($VERSION);
$VERSION = do { my @r=(q$Revision: 3.18 $=~/\d+/g); sprintf "%d."."%03d"x$#r,@r };


use Encode qw/ decode encode /;
use Net::MSN;

$TIMEOUT = 20;
$WAIT = 3;
$HEADER = '%H:%M';
$COMMAND = &'list('privmsg', 'join', 'part', 'kick' ,'invite', 'mode', 'nick', 'quit', 'topic');
$LOGLEVEL = 3; # 3 DEBUG / 2 INFO / 1 ERROR / 0 MSG Only [advice 2 is better]
my @messages = ();

my $msn;
my $is_alive = 0;
my $process_event;

$_ = 'log_msn';

BEGIN {
    my $process_event = Net::MSN->can('process_event');
    undef *Net::MSN::process_event;
    *Net::MSN::process_event = sub {
        my ( $self, $this_self, $line, $fh ) = @_;
#        print {*STDERR} ( substr $line, 0, 60 ) . "\n";

        my ( $cmd, @data ) = split( / /, $line );

        return unless ( defined $cmd && $cmd );

        if ( $cmd eq 'LST' ) {
            $self->buddyupdate( $data[0], $data[1], 'NLN' );
            return 1; # 対象一人なのでこれで.
        }
        elsif ( $cmd eq 'QNG' ) {
            if ( $self->if_callback_exists('_on_QNG') ) {
                &{ $self->{Callback}->{_on_QNG} };
            }
        }
        goto &{$process_event};
    };
}

sub module_enable {
  local($userno) = @_;

  $msn = Net::MSN->new;
  $msn->{_Log} = \&c_msg;
  $msn->set_event(
    _on_QNG => sub {
      if ( $msn->is_buddy_online( &'property($userno, 'email') ) ) {
        $msn->call( &'property($userno, 'email') );
      }
    },
    on_message => \&msn_on_message,
    );
}

sub module_disable {
  local($userno) = @_;

  if(defined $msn)
  {
    &msn_term();
  }

  $is_alive = 0;
}

sub check_event {

  if(defined $msn)
  {
     # socket 確立してなくてもぶん回す
     eval '$msn->check_event()';

     if($@)
     {
        warn $@ ;
        $is_alive = 0; # reconnect 準備
     }
  }

}

sub msn_on_message {
  local ( $self, $chandle, $friendly, $message ) = @_;
  local ( $serverlist, $sno );
  local ( $line );

  # encode utf8 to shiftjis to 8bit-jis
  $message = &'sjis_jis( encode( "shiftjis", decode( "utf8", $message )));

  foreach $line ( split( /\n/, $message ))
  {
    next if ( $line =~ /^$/ ); # empty?

    &c_msg( $friendly . '!' . $chandle . " $line" );

    # command proxy
    next unless (&'property($userno, 'proxy') =~ /^yes/i );
    next unless (&check_mask($userno, $friendly . '!' . $chandle)); # nickname!msnアカウント

    # send to server $cmd
    foreach $sno (&'array($'serverlist)) {
      next unless $userno == $'userno[$sno];

      if( $line =~ /^\// )
      {
        my $cmd = $';
        &'s_print($sno, '', split( / /, $cmd, 1) ); # コマンドなら そういう風に送る
      }
      else
      {
        # 発言か
        my( $vchan, $mchan ) = split( /\s+/, $line, 2);
        $vchan = &'real($vchan); # % prifix をとる
        last unless( &'channel($vchan) ); # チャンネルなら...
        &'s_print($sno, '', 'PRIVMSG', $vchan, $mchan );

        # それ以外は無視
      }
    }
    # next.line
  }
}

sub main_loop {
  local($userno) = @_;
  local($now, $regex, $str, $date, $time, @addlist);

  # Connect to MSN and auto-reconnect
  if( $is_alive == 0 )
  {
    $is_alive = 1;
    $msn->connect( &'property($userno, 'bot'), &'property($userno, 'password') ) or &msn_term();
  }

  &check_event();

  &msn_send();
}

# send to MSN
sub msn_send {

  local $timeout = &'property($userno, 'timeout') || $TIMEOUT; # -- 最大保留時間
  local $wait  = &'property($userno, 'wait') || $WAIT; # -- 洪水対策
  local $email  = &'property($userno, 'email');

  local $t = time;
  
  &check_event();
  
  while (1) {
    last unless $is_alive;
    last unless @messages;
    last if $timeout < time - $t;  # join 待ち & IRC に制御戻す
    last unless($msn->is_buddy_online($email)); # 相手ログイン待ち

    if (1) {
      my $message = shift @messages;
      my $t2    = time;

      # 8bit-JIS to SJIS to UTF8
      my $ret = $msn->sendmsg( $email,
        encode("utf8", decode( 'shiftjis' , &'jis_sjis($message) ) ) );

      # wait loop
      while ( time - $t2 <= $wait ) {
        &check_event();
      }

      # 相手がログインしてなくて送信待ち状態
      unless(defined($ret))
      {
        unshift @messages, $message;
        last;
      }
    }
    
    &check_event();
    
  }
  
  &check_event();
  
}

# IRC ログをストア + MSNへ投げる
sub sendlog {
  local($fileno, $msg) = @_;
  local($userno, $name, $code, $dir, $header, $mode);

  $userno = $'userno[$fileno];

  $code = &'property($userno, 'code');

  $header = &'property($userno, 'header');
  $header = $HEADER unless defined($header);
  $header = &'date($header);

  push @messages, $msg;

  &msn_send();
}

sub msn_term{
  if( $is_alive == 1 )
  {
    $msn->disconnect;
    $is_alive = 0;
  }
}

# announce MSN infomation for All connected client
sub c_msg {
  local ($msg , $level) = @_;
  local ($cno);

  $level = 0 unless(defined($level));
  return if( $level > $LOGLEVEL );

  my $info = '';
  $info = '[error]' if($level == 1 );
  $info = '[info]'  if($level == 2 );
  $info = '[debug]' if($level == 3 );

  foreach $cno (&'array($'clientlist)) {
    next unless $'avail[$cno];
    &'c_print($cno, '', 'NOTICE', $'nick[$cno], "*** <MSN$info> " . $msg );
  }

  print '[' . localtime . "] *** <MSN$info>" . $msg . "\n";
}

sub ss_privmsg {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick, $vchan);
  if ($params[1] && &savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    if (&'channel($params[0])) {
      $vchan = &'alias($params[0]);
      if (&'exist($'nameslist{$serverno, $params[0]}, $nick, "\+$nick", "\@$nick")) {
        &sendlog($serverno, "<$vchan:$nick> $params[1]");
      } else {
        &sendlog($serverno, "($vchan:$nick) $params[1]");
      }
    } else {
      &sendlog($serverno, "=$nick= $params[1]");
    }
  }
  return ($prefix, $cmd, @params);
}

sub ss_notice {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick, $vchan);
  if ($params[1] && &savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    if (&'channel($params[0])) {
      $vchan = &'alias($params[0]);
      if (&'exist($'nameslist{$serverno, $params[0]}, $nick, "\+$nick", "\@$nick")) {
        &sendlog($serverno, "<$vchan:$nick> $params[1]");
      } else {
        &sendlog($serverno, "($vchan:$nick) $params[1]");
      }
    } else {
      &sendlog($serverno, "=$nick= $params[1]");
    }
  }
  return ($prefix, $cmd, @params);
}

sub ss_join {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick, $name, $mode, $vchan);
  if (&savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    ($name, $mode) = (split(/\cG/, $params[0]), '');
    $vchan = &'alias($name);
    if (index($mode, 'o') != -1) {
      &sendlog($serverno, "+ \@$nick ($prefix) to $vchan");
    } elsif (index($mode, 'v') != -1) {
      &sendlog($serverno, "+ +$nick ($prefix) to $vchan");
    } else {
      &sendlog($serverno, "+ $nick ($prefix) to $vchan");
    }
  }
  return ($prefix, $cmd, @params);
}

sub ss_part {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick, $vchan, $msg);
  if (&savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    $vchan = &'alias($params[0]);
    $msg = $params[1] || '';
    &sendlog($serverno, "- $nick from $vchan ($msg)");
  }
  return ($prefix, $cmd, @params);
}

sub ss_kick {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick, $vchan);
  if (&savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    $vchan = &'alias($params[0]);
    &sendlog($serverno, "- $params[1] by $nick from $vchan ($params[2])");
  }
  return ($prefix, $cmd, @params);
}

sub ss_invite {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick, $vchan);
  if (&savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    $vchan = &'alias($params[1]);
    &sendlog($serverno, "Invited by $nick: $vchan");
  }
  return ($prefix, $cmd, @params);
}

sub ss_mode {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick, $vchan, @mode);
  @mode = @params;
  shift(@mode);
  if (&savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    if (&'channel($params[0])) {
      $vchan = &'alias($params[0]);
      &sendlog($serverno, "Mode by $nick: $vchan " . join(' ', @mode));
    } else {
      &sendlog($serverno, "Mode by $nick: $params[0] " . join(' ', @mode));
    }
  }
  return ($prefix, $cmd, @params);
}

sub ss_nick {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick);
  if (&savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    if ($nick eq $'nick[$serverno] || $params[0] eq $'nick[$serverno]) {
      &sendlog($serverno, "My nick is changed ($nick -\> $params[0])");
    } else {
      &sendlog($serverno, "$nick -\> $params[0]");
    }
  }
  return ($prefix, $cmd, @params);
}

sub ss_quit {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick);
  if (&savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    &sendlog($serverno, "! $nick ($params[0])");
  }
  return ($prefix, $cmd, @params);
}

sub ss_topic {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($nick, $vchan);
  if (&savecommand($'userno[$serverno], $cmd)) {
    $nick = &'prefix($prefix);
    $vchan = &'alias($params[0]);
    &sendlog($serverno, "Topic of channel $vchan by $nick: $params[1]");
  }
  return ($prefix, $cmd, @params);
}

sub sp_privmsg {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($chan, $vchan);
  if ($params[1] && &savecommand($'userno[$serverno], $cmd)) {
    foreach $chan (split(/\,/, $params[0])) {
      if (&'channel($chan)) {
        $vchan = &'alias($chan);
        &sendlog($serverno, ">$vchan:$'nick[$serverno]< $params[1]");
      } else {
        &sendlog($serverno, ">$chan< $params[1]");
      }
    }
  }
  return ($prefix, $cmd, @params);
}

sub sp_notice {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($chan, $vchan);
  if ($params[1] && &savecommand($'userno[$serverno], $cmd)) {
    foreach $chan (split(/\,/, $params[0])) {
      if (&'channel($chan)) {
        $vchan = &'alias($chan);
        &sendlog($serverno, ">$vchan:$'nick[$serverno]< $params[1]");
      } else {
        &sendlog($serverno, ">$chan< $params[1]");
      }
    }
  }
  return ($prefix, $cmd, @params);
}

sub savecommand {
  local($userno, $cmd) = @_;
  local($save);
  $save = &'property($userno, 'command');
  if (defined($save)) {
    $save = '';
    foreach $mask (&'property($userno, 'command')) {
      $save = &'add($save, split(/\,/, $mask));
    }
  } else {
    $save = $COMMAND;
  }
  return &'exist($save, $cmd);
}

sub check_mask {
  local($userno, $prefix) = @_;
  local($user);
  foreach $user (&'property($userno, 'mask')) {
    if ($user =~ /^\-/) {
      $regex = &'regex(substr($user, 1));
      return 0 if $prefix =~ /$regex/i;
    }
    if ($user =~ /^\+/) {
      $regex = &'regex(substr($user, 1));
    } else {
      $regex = &'regex($user);
    }
    return 1 if $prefix =~ /$regex/i;
  }
  return 0;
}



__END__
--><HTML><HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-2022-JP">
<TITLE>log/msn.plm</TITLE></HEAD><BODY>

オンラインドキュメント


<HR><H3>名前</H3>

log/msn.plm - メッセージをMSNで通知する


<HR><H3>説明</H3>

ログを指定したアカウントへ通知します。


<HR><H3>プロパティ</H3>

<DL>
<DT>  log.msn.email 通知して欲しいアカウント
</DT>
<DD>    !通知して欲しいアカウント
</DD>
<DT>  log.msn.bot ボットのアカウント
</DT>
<DD>    !ボットのアカウント
</DD>
<DT>  log.msn.password ボットのパスワード
</DT>
<DD>    !ボットのパスワード
</DD>
<DT>  log.msn.header へッダ
</DT>
<DD>    ログに出力する時刻のフォーマットを指定します。
        %ではじまる文字があると対応する時間に変換されます。
        デフォルトでは「%H:%M」です。
</DD>
<DT>  log.msn.command ({invite|join|kick|mode|nick|notice|part|privmsg|quit|topic})
</DT>
<DD>    保存するメッセージの種類を指定します。
        デフォルトでは「notice」以外のすべてです。
</DD>
<DT>  log.msn.timeout 20
</DT>
<DD>    メッセージ待つ時間
</DD>
<DT>  log.msn.wait 3
</DT>
<DD>    MSN洪水対策
</DD>
<DT>  log.msn.proxy no
</DT>
<DD>    MSNから来た /**** メッセージを IRCD へ送る
        mask とあわせて使用してください
</DD>
<DT>  log.msn.mask* {ニックネーム}!{MSNアカウント}
</DT>
<DD>    招待した人、およびチャンネルがここで指定された
        ものにマッチした場合かつproxy onの場合、転送します。
</DD>
</DL>


<HR><H3>設定例</H3>

<PRE>
+ log/msn.plm

</PRE>

<PRE>
+ log/msn.plm
log.msn.email: oreore@::1
log.msn.bot: ircmsg@::1
log.msn.password: password
log.msn.command: privmsg,topic,join,part,kick,nick
log.msn.proxy: yes
log.msn.mask: *!*@::1
</PRE>

送信するログを「privmsg」、「topic」、「join」、「part」、「kick」、
「nick」の6種類にします。
oreore へ ircmsg から送ります。
ircmsgへコマンドを送信できますが、アカウント名が ::1 ドメインでなくてはいけません

</BODY></HTML>