Skypeのチャットに自動応答するJavaScriptを簡単に作れるようにする

前回書いてた、

一本のプログラムだけが走っていて、チャットのメッセージを監視し、条件に合ったメッセージを見つけたら決められたスクリプトを呼び出すようなのを作りたい。

つまり、自動応答のコアになるようなやつ。あとは実際の応答ロジックはプラグイン式に手軽に書く。

Skypeのチャットに自動応答するJavaScript - 今日覚えたこと

というのを作った。

autoreply.js

まずはメインとなるスクリプト。このスクリプトは、設定を読み込んだら無限ループをして、Skypeのチャットを監視する。そして何か発言を受信したら、設定に従って外部スクリプトを呼び出し、その処理結果を自動的に発言するもの。

// config
var conf = eval_file('autoreply.config.json');

// attach Skype
var skype = new ActiveXObject('Skype4COM.Skype');
WScript.ConnectObject(skype, 'Skype_');
skype.Attach();

// main
while(true){
  WScript.Sleep(1000);
}

// event
function Skype_MessageStatus(msg, status){
  if(status == skype.Convert.TextToChatMessageStatus('RECEIVED')){
    var user = msg.FromDisplayName;
    var body = msg.Body;
    var timestamp = msg.Timestamp;
    var chat = msg.Chat;
    WScript.echo('received: ' + [chat.FriendlyName, user, timestamp].join('::'));
    WScript.echo('body: ' + body);
    for(var i = 0; i < conf.length; i++){
      var item = conf[i];
      if((!item.chat || item.chat.test(chat.FriendlyName))
      && (!item.user || item.user.test(user))
      && (!item.body || item.body.test(body))){
        WScript.echo('file: ' + item.file);
	var f = eval_file(item.file);
        var reply = f(user, body);
        if(!reply) continue;
        chat.SendMessage(reply);
        WScript.echo('reply: ' + reply);
      }
    }
  }
}

// eval
function eval_file(filename){
  var fso = new ActiveXObject('Scripting.FileSystemObject');
  var path = fso.GetParentFolderName(WScript.ScriptFullName);
  var stream = fso.OpenTextFile(fso.BuildPath(path, filename), 1, false, -2);
  var t = stream.ReadAll();
  var result = eval('(function(){ return (' + t + '); })();');
  stream.Close();
  return result;
}

autoreply.config.json

設定はJSONで書く。

[
  {
    chat: /チャットタイトルの条件の正規表現(省略可)/,
    user: /発言したユーザIDの条件の正規表現(省略可)/,
    body: /発言内容の条件の正規表現(省略可)/,
    file: '条件に合致したときに呼び出すjsファイル.js'
  },
  {
    // 複数の条件が指定できる。
  }
]

こんな感じの書式。chat, user, body には正規表現を指定して、その条件に合致する発言を受信したときに、file に指定されたJavaScriptを呼び出すわけだ。

条件は省略可能なので、fileだけを指定することも可能。その場合、全ての発言に対してスクリプトが呼び出される。

スクリプト

呼び出されるスクリプトは、無名関数の形で書く。2つの引数を取り、nullまたは文字列を返す。

function(user, body){
  if(body == 'おはよう'){
    return 'おはようございます';
  }else{
    return null;
  }
}

例えば上記のような感じ。

この例は、受信した内容が「おはよう」であった場合に「おはようございます」を返している。returnで返した文字列が、自動応答の内容として発言されることになる。nullを返した場合は何も発言されない。

要は、何か文字列を返すような無名関数を書けばいい。それだけ。

使い方の例 : 天気予報

例えば、「天気」の文字が含む発言があった場合に、天気予報を返すような自動応答プログラムは、以下のように作る。

autoreply.config.json

設定ファイルはこんな感じ。

[
  {
    body: /天気/,
    file: 'weather.js'
  }
]

/天気/ の正規表現にマッチしたときに weather.js を呼び出す設定。

weather.js

そして肝心の weather.js は以下。

function(user, body){
  var xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
  var uri = 'http://weather.livedoor.com/forecast/rss/4/25.xml';
  xmlhttp.open('GET', uri, false);
  xmlhttp.send(null);
  if(xmlhttp.status != 200) return null;

  var result = [];
  var items = xmlhttp.responseXML.getElementsByTagName('item');
  for(var i = 0; i < items.length; i++){
    var category = items[i].getElementsByTagName('category')[0].firstChild.nodeValue;
    var desc = items[i].getElementsByTagName('description')[0].firstChild.nodeValue;
    if(!/PR/.test(category)) result.push(desc);
    if(3 <= result.length) break;
  }
  return result.join('\n');
}

livedoorWeather Hacksで提供している天気予報のRSSを取得して、その中から最新3件分の天気予報を返却している。

ちなみに、上記のコードは仙台市の天気を取得している。uriのところを一覧を参考にして他のに変えることもできる。

動かす

まずSkypeを起動してログインしておく。

そして、

  • autoreply.js
  • autoreply.config.json
  • weather.js

の3ファイルを同じ場所に置いて、コマンドプロンプトから、

cscript autoreply.js

とやってスクリプトを起動。

そのSkypeのアカウントに対して、別なアカウントからチャットで「天気」を含む言葉を話すと、自動応答で天気予報が返ってくるはず。


使い方色々

スクリプトを作るのは、引数も戻り値も文字列なので簡単だと思う。Skypeの制御に関するところは全部autoreply.jsの中でやってるので気にしなくて良いわけだ。

要はbotなので、頑張れば人工無能みたいなのも作れるはず。もっと便利になるようなアイディアは無いかなー。

追記 : 地震情報

ちょうど地震があって思いついた。