Perl × Win32::OLE × Skype4COM

濃密にPerlの話題。
こんなにPerlに入り浸ったのは正直久しぶりだと思う(苦笑)
ふと思いつきで、SkypeにもBotを回せないかと考えてみた。
使用言語はなるべくならPerlがいい。
もし実用できたらCharaBrainのシステムを導入したいからだ。
という訳で、色々と調べる。その結果、use Win32:OLEとSkype4COMの組み合わせが存在する。
もちろんWin32なのでWindows依存になってしまうがそこはある意味仕方ないと思ってしまった。
ともかく、skypeとの通信だけ綺麗にまとめれれば、CharaBrainはそれなりに拡張性・独立性のあるモジュールに仕立て上げるつもりなので、どのOS、どの通信環境でもCharaBrainの実装は可能、であることを目指したい。

で、ソースコードを書いて、Skype4COMのPubcli API仕様書を熟読しながらやって見る。
まずはサンプルがないかと探してみると流石に過去に書いている人は居るようだ。
そのサンプルと合わせて、まずはテストがてら個人通信をしてみる。

use warnings;
use strict;
use Win32::OLE;
use Encode;
use utf8;

my $oSkype = Win32::OLE->new('Skype4COM.Skype','Skype_');
$oSkype->Client->Start unless $oSkype->Client->IsRunning;

my $user_name = 'echo123';

my $chat = $oSkype->CreateChatWith($user_name);
$chat->SendMessage(encode("shiftjis","こんにちわ"));


$user_nameにはコンタクト情報を共有しているユーザーのIDを指定する。
デフォルトで共有しているテストデバイスIDecho123を利用して、サンプルと走らせてみると
“こんにちわ”を送れている事が分かる。
なお、Skypeは起動した状態でスクリプトを実行し、perl.exeをアクセスをSkype側で許可する必要がある。
1回許可すると次回起動しなおすまではずっとスクリプトを許可するので、面倒な操作無く実行出来ることになる。

ここまでは良かったのだが、例えば
・コンタクト全員にメッセージを送りたい
・グループ会話にメッセージを送りたい
となると、例えばグループ会話のリストを取得したり、コンタクトのリストを取得したりしなければならない。
Skype4COMのAPIでは、Skypeクラスのプロパティとしてリストを取得できるのだが、これが厄介だった。
例えば返り値がIUserCollectionとあるように、Perlには無い"コレクション"という概念が存在したのだ。
Skype4COMのサンプルソースコードの殆どがvbsで書かれている。
幸いにもvbsは一番最初にやった言語で読めたのだが、vbsでは取得したコレクションから一つずつプロパティを受け取る。この作業がPerlには無い。
さて、どうしたものかと頭を捻る。

最初に考えたのは、ハッシュリファレンスを利用する方法。
API接続をハッシュリファレンスでやっているのでもしかしたらそのままリストで読み込めないか……と考えたが打ち砕かれる。
関数から返されたのはリファレンス値。実体はそもそもWin32::OLEにある。

過去の記憶を呼び覚まして、取り敢えずData::Dumperで調べるだけ調べてみる。
結果、やはりコレクション自体が返って来てる事が分かった(当たり前の事実だ……)
問題はそのコレクションからどうやってインスタンスを取り出すか。

悩みに悩んでvbsで書こうとも思った。
vbsで書いて、XML形式の通信でPerlと接続して……なんて事も考えたがせっかくなのだからPerlでやってしまいたい。それに、それ自体は実行速度に劣る部分がある。かつ、XML出力部分も増やさなければならない。
vbsで書くのは止めることにはしたが、サンプルを色々と弄る。

そうやって5時間ほど悩んだ結果、あるヒラメキが頭を襲う。実体はWin32::OLEに存在するのだから
このモジュールにコレクションを扱うモノが存在してもいいのではないだろうか。
というわけで、“Win32::OLE コレクション”でググルとWin32::OLE::Enumなるオートメーションコレクションオブジェクトがあることが判明。あったのかよ……orz
と拍子抜け。(もっとも、Win32::OLEを扱うのなら基礎知識として頭の片隅に入っていなければならないのだが……)

というわけで、今度はそれを使って、取り敢えずコンタクトリストを取得して、名前を出してみる。

use warnings;
use strict;
use Win32::OLE;
use Encode;
use utf8;

my $oSkype = Win32::OLE->new('Skype4COM.Skype','Skype_');
$oSkype->Client->Start unless $oSkype->Client->IsRunning;

my $enum = Win32::OLE::Enum->new($oSkype->Friends);
my @users = $enum->All();

foreach my $user(@users){
	print $user->FullName(),$/;
}

結果は、ちゃんとコンタクトリストの名前を取得して出力している。
Friendsが返すのはIUserCollectionで、そこからIUserインスタンスを取得している。
この時点でインスタンスを取得しているので、それぞれに一気にSendMessageを使うことも可能だ。
同じ原理で、GroupsやBookmarkedChatsを使い、正規表現を使ったグループ検索で対象のグループのインスタンスを取得。
会話の内容を取得したり、オンラインユーザーを取得したり、もちろんメッセージを送ったり、通話をしたりすることも出来る。

意外にSkype4COMのAPIはシンプルだった。
コレクションを扱うのが鬼門だっただけで、それをクリアしてしまえば流石扱いやすい。
ただ、やはり分かりにくい部分や面倒くさい部分も含まれるので近々APIMooseでまとめたモジュールを作成して、より通信しやすい様にしようと思っている。

SkypeBotを使うことは考えなかったが、これで気兼ねなくBot用SkypeIDに今のIDを変更することが出来る。
ちなみにSkypeID変わりますたw

参考URL:
perldoc.jp --Win32::OLE::Enumオートメーションコレクションオブジェクト
http://perldoc.jp/docs/modules/libwin32-0.26/Win32/OLE/Enum.pod

Unknown::Programming --PerlからSkypeでチャット
http://d.hatena.ne.jp/fbis/20080710/1215666279

酒日記 はてな支店 --[perl] Perl + Win32::OLE で Skype に発信
http://d.hatena.ne.jp/sfujiwara/20080702/1215003426

Skype Developer Accressories(Public API)
http://developer.skype.com/accessories