Hatena::ブログ(Diary)

今日覚えたこと RSSフィード

2008.05.06

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

f:id:nacookan:20080507000248j:image

使い方色々

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

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

追記 : 地震情報

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

reirei 2010/02/06 19:21 初めまして。reiと申します。

この記事を参考にファイルを作ってコマンドプロンプトでautoreply.jsを走らせようとしたのですが、
「C:\skypebot\autoreply.js(45, 3) Microsoft JScript コンパイル エラー: '}' があり
ません。」
と表示されて、走らせることが出来ませんでした…
一応コピー&ペーストしたのでミスはないと思います。ちなみに、repeat.jsはいけました。
ご教授のほど、どうかお願いします…

nacookannacookan 2010/02/06 21:49 そこは外部ファイルを読んで評価してるところなので、読ませてるjsonまたはjsに問題があるっぽいですね。

MBMB 2010/03/30 23:32 自分で作のむずかしいので、どこかに載せてダウンロードさせてくれませんか?

nacookannacookan 2010/03/31 00:06 コピペして保存するだけですが。。。

こっこくんこっこくん 2010/05/17 22:39 すばらしいです。
参考にさせていただきます。

guran01guran01 2010/06/06 12:24 [
{
body: /A/,
file: 'A.js'
}
]

[
{
body: /B/,
file: 'B.js'
}
]

↑みたいに複数やるにはどうすればいいですか?

名無し名無し 2010/08/04 13:01 複数(A,B,C)記述する場合・・・

autoreply.config.json に、

[
{
body: /A/,
file: 'A.js'
}
],
{
body: /B/,
file: 'B.js'
}
],
{
body: /C/,
file: 'C.js'
}
]

と記述します。
(上に記述されてあります。

ななしーななしー 2011/01/03 01:55 yahooニュースなどのRSSのアドレスをweatherのプログラムにいれてみたのですが
できません。どうしたらできますか?

nacookannacookan 2011/01/03 02:11 この辺が参考になると思います
http://www.hyuki.com/writing/techask.html

ななしーななしー 2011/01/03 14:54 weather.jsというプログラムの
var uri = 'http://weather.livedoor.com/forecast/rss/4/25.xml';
の部分をヤフーニュースなどのRSSのアドレスに変えたのですが
チャットでメッセージを言われても呼び出すことができません。
これはいったいどういうことですか?

nacookannacookan 2011/01/03 15:48 その説明じゃ、何をやったら何が起きたのかがわからないのでなんとも言えないです。
ヤフーニュースなどなんていう言い方じゃなく、どんなURLを指定したのか素直にコピペすればいいと思いますし、
できませんとかじゃなく、期待に反して何が起きたのかを書いてくれないとどんな問題があったか推測もできないです。
さっきのURLをもっと参考にして欲しいです。
ハンバーグのレシピの記事に対して「ひき肉の代わりに他の肉などを入れてみたのですが、うまくいきません。どうしたらできますか?」と質問してるようなものです。

そんな中、がんばって考えてみましたが、RSSの種類が違うのかも知れないですね。
RSSにもいくつか種類があるので、その種類によっては、その後のxmlの要素を取り出してる処理を直さないと
ダメなのかもしれません。

ななしーななしー 2011/01/03 16:06 分かりにくい質問で申し訳ございません。

このページにあるweather.jsというプログラムの
var uri = 'http://weather.livedoor.com/forecast/rss/4/25.xml';
という部分をヤフーニュースのギズモードジャパンというRSS
http://headlines.yahoo.co.jp/rss/giz_ent.xml このアドレスに変更し、
weather.jsではなくnews.jsにして相手がニュースというと呼び出すようにしました。そうすることで
相手がニュースと発言したら新しいニュース3件が出るのではないか、と思い
アドレスだけを変更してみたところ
相手がニュースといってもなにも反応がありませんでした。

どうしたらいいでしょうか?やはりRSSの種類というものなのでしょうか?

nacookannacookan 2011/01/03 16:18 現象がよくわかったので、そのRSSのxmlを見てみました。バージョンはlivedoorの天気予報と同じでした。
それはよかったですが、以下の2点についてnews.jsの修正が必要であると思います。
- 元のコードでは、categoryにPRを含む場合は無視するような処理が入ってますが、不要になります。
- 元のコードでは、descriptionを取り出していますが、titleを取り出すように直す必要があります。

あと、そもそも天気のものが動くのは確認済みなんですよね?それすら動いてないなら、
プログラムがどうこうじゃなく、やり方が悪い可能性があります。

ななしーななしー 2011/01/03 16:46 天気そのものや、メッセージを返すということはできたのでやり方は正しいと思います
var desc = items[i].getElementsByTagName('description')[0].firstChild.nodeValue
なんどもすみません
この部分のdescriptionをtitleに直し、
if(!/PR/.test(category)) result.push(desc);
この部分は削除してよいということですよね?

nacookannacookan 2011/01/03 17:41 それでいいと思います。
あとcategoryを取得してるところももう不要になります。
それから、ニュースの場合は、タイトルだけ表示されても困るだろうから、
URLも表示するようにした方がいいんじゃないですか?

まあその辺は好きにやってください。いちいちここで聞かないで、自分で試してみればいいと思います。
なにか困ったらまたどうぞ。

ななしーななしー 2011/01/03 18:32 とりあえず削除するところは削除して
治すところだけは直しました。
それでとりあえずテストをしてみようと思い
ニュースと発言してもらったのですが、コマンドプロンプトではnews.jsファイルが読み込まれてるのにも関わらず
Skypeチャット内には反映されません。

nacookannacookan 2011/01/03 20:53 あの、まさかとは思いますが念のため。
if(!/PR/.test(category)) result.push(desc); の部分ですが、まるまる全部消しちゃいました?
判定だけ消すという意味だったのですが、大丈夫でしょうか。
result.push(desc); は残します。

ななしーななしー 2011/01/03 21:30 まるまる消してしまっていたみたいです…
おかげで反映させることができました。
URLも取得しないと意味がないと上記の通りに思い、アドレスを取得しようとおもったのですが
var desc = items[i].getElementsByTagName('title','url')[0].firstChild.nodeValue;
このようにurlを追加したのですがskypeで表示されなくなってしまいました

nacookannacookan 2011/01/03 21:52 getElementsByTagNameの使い方が全然違います。カンマなんかで区切ってもダメだと思いますよ。
titleとurlは別々にそれぞれgetElementsByTagNameしておいて、resultにpushするときにくっつければいいんじゃないですか?
というか自分がいじってる部分のコードの意味やメソッドの使い方くらいは調べたり確認しながらやらないと。
もはやこの記事の本題と関係ない部分の、JavaScriptの基本の話になっちゃいそうです。そっちまではつきあいきれません。

leilei 2012/01/06 01:42 このスクリプトを見て、自分なりにいろいろ弄らせていただいております。
不躾な質問なのですが、
スクリプト自身が、発言をすることはできないのでしょうか。
返信という形で発言は出来ますが、例えば定刻に発言、他のskypeユーザーが秒数を指定して
返信させる、という動作をしたいのです。

nacookannacookan 2012/01/06 10:51 「定刻に発言」っていうことであれば、記事中のコードで言うところの、chat.SendMessage(reply); さえ呼べばいいわけですから、できるんじゃないですか?

chatのインスタンスをどうやって取得するか、というのだけが課題だと思います。いろいろ方法は思いつきますが、簡単そうなのは、最初だけ人間が発言して、それに反応させてインスタンスを取っておき、以後は無限ループしてシステム時刻を監視し、時間になったら発言する、とかがいいですかね。試したわけじゃないですが、どうですか。

「他のskypeユーザーが秒数を指定して返信させる」の部分は意味がわかりませんでした。

leilei 2012/01/06 14:40 有難うございます。windowsのスケジューラでchat.SendMessage(reply); を呼ぼうと思います。
>「他のskypeユーザーが秒数を指定して返信させる」
skypeユーザーが発言内に時間を決める 例)ラーメン 180秒 →180秒後に「ラーメン」をチャット送信。
みたいなものなのですが、相手の文章を入れるため、不正なコードが実行される可能性もあり怖いですね。

もう1つ。+ user + で相手の名前を引用しますが、このような引用したり、他の何かをもってくる記述は他に何かないでしょうか。

nacookannacookan 2012/01/06 15:18 秒数を指定について、意味がわかりました。なるほど。まあ頑張ってコード書けばいくらでもできると思います。そこはもうSkypeの制御とは関係なさそうなので。
autoreply.js の Skype_MessageStatus 関数を見ればわかると思いますが、msgっていうところに、元発言に関するいろんな情報が入ってます。どんなのがあるかは、ドキュメントに書いてあったと思います。もう古い話なので詳細は忘れましたが。。。

leilei 2012/01/15 04:29 ありがとうございます。指定時間に発言することに成功しました。

また質問で恐縮なのですが、スクリプト上から通話を切断すること(自身の。マウスで切断をクリックするのではなくスクリプト上から)はできないでしょうか?
SET CALL FINISHED という文字列を使うのでしょうか。
もし、よろしかったらサンプルスクリプトを書いていただけないでしょうか。

nacookannacookan 2012/01/15 05:04 切断については、調べてないので全然わからないです。完全に当てずっぽうの予想ですが、chatのインスタンスにメソッドかなんか無いですかね。
もしわかったらココに書いてもらえると、検索でやってきた他の人のためになると思います。

leilei 2012/01/15 14:54 はい。発信に関しては、チャットコマンドから出来たのですが、切断については特にありませんでした。
出来ましたらご報告します。

leilei 2012/01/22 20:36 度々失礼します。

特定のチャットを受信してから指定時間後にチャットを返す というのをやっているのですが、
どうしてもコンパイルエラーautoreply.js(47, 3)   ')'  が無い と出て実行で出来ません。
どこが間違っているか教えていただけないでしょうか。
タイマー という言葉を見つけたらtimer.js を実行 ←JSON内に記述

timer.js↓
function func()
{
return 'あいう';
}

function t_start()
{
setTimeout(func, 2000);
}

nacookannacookan 2012/01/22 21:00 timer.jsは引数をふたつ受け取る無名関数の形で書かないとだめです。そしてさらに、私が書いたコードはreturnで投稿する文字を返すものなので、setTimeoutを使って2秒後にreturnするってのは書けないと思います。なので、私が書いたコードを改造して、chatのインスタンスも無名関数まで渡してくるようにして、呼び出された側で投稿するなどしないとダメじゃないですかね。

テストしてないので間違ってると思いますが、ムリヤリ想像で書いてみると、

// autoreply.js
var reply = f(user, body);

の部分を

// autoreply.js
var reply = f(user, body, chat);

のように書き換えて、

// timer.js
function(user, body, chat){
setTimeout(function(){ chat.SendMessage('あいう'); }, 2000);
return null;
}

こんな感じでしょうか?

leilei 2012/01/22 23:07 ありがとうございます!
さっそくテストしてみます。

leilei 2012/01/22 23:29 先ほど、試してみました。 が、
\autoreply.js(33, 9) Microsoft JScript 実行時エラー: オブジェクトを指定してください、と返されてしまいます。
var reply = f(user, body, chat); と置き換えたことを確認しましたが、やはりエラーを返されてしましました。

私の技量不足で申し訳ありません。

nacookannacookan 2012/01/22 23:56 うーん、間違ってるとは思いつつ雰囲気を伝えるために書いたコードなので、そのまま書いてもダメだったんですかね。
要はchatを渡しちゃって、渡された側で時間差で呼べば良いのかなってことを言いたいだけです。
具体的にどういうコードを書けば良いかは考えてないのでわからないです。

leilei 2012/01/23 00:01 失礼しました。
色々と試行錯誤してきます。つまらない質問に返答いただき、有難うございました。

kaitokaito 2012/02/18 21:55 こんにちわ
とてもすごいツールですね
良かったらDLしたいです

もし良かったらSkypeID ke.shi12421315に連絡ください

leilei 2012/02/22 01:43 >>kaitoさん

コピペするだけですよ。

kaitokaito 2012/03/22 21:19 >>leiさん

なんのソフトを使ってどこにコピーすればいいのかすらわからないものなんでw・・・

molganmolgan 2013/03/11 14:48 switch文などを使ってランダムな文字列を選んで返信する分ていうのはどのように書けばよろしいんでしょうか
お力添えがあるとありがたいです

nacookannacookan 2013/03/12 11:38 何がわからないのかがわからないです。乱数を求めてswitchで分岐してそれぞれで適当な文字列をreturnするだけじゃないですか?

molganmolgan 2013/03/12 15:48 言われてみればそのまんまですね、、、自分自身に足りなかったのは考え方ではなく、文型の知識だったようですww
ありがとうございました

プロンプトプロンプト 2013/03/17 15:39 コマンドプロンプトで実行してみたところ、Microsoft JScript 実行時エラー: オートメーション サーバーはオ
ブジェクトを作成できません。といったエラーが発生するのですが、どなたか自分と同じような現象にあっている
人はいないでしょうか.

nacookannacookan 2013/03/17 18:51 Skype4COMがちゃんと入ってないか、64bit版のコマンドプロンプトを使っちゃってるんじゃないですかね。SkypeもSkype4COMも32bitなので、コマンドプロンプトも32bit版を使わないとだめです。C:\Windows\SysWOW64\cmd.exe みたいなパスだったと思います。

プロンプトプロンプト 2013/03/18 10:57 無事に動かすことができました!!
これから、色々と自分好みに改造していこうと思います!!

mondamotimondamoti 2013/03/26 09:31 [
{
body: /おはよう/,
file: 'ohayo.js'
},
{
file:'A.js';
}
]
となった場合、おはようと言う文字列があった場合ohayo.jsがうごくんですよね?
[
{
file: 'A.js'
},
{
body: /おはよう/,
file: 'ohayo.js';
}
]
ではこの場合だと文字列'おはよう'があったとしても無視されて、A.jsが動くんでしょうか?
優先順位なんかを詳しく教えてもらえるとうれしいです。お願いします

nacookannacookan 2013/03/26 14:52 上から順に調べて、マッチしたものを全て動かすと思います

mondamotimondamoti 2013/03/26 15:46 マッチしたものすべてということは、
[
{
body: /おはよう/,
file: 'ohayo.js'
},
{
file:'A.js';
}
]
この条件下で、チャットに'おはよう'が入った場合、ohayo.jsとA.jsの両方が同時に起動するということですか?
だとするなら、どの条件にも合わない時に最終的にそのjsファイルのみが起動するようにはできないのでしょうか

nacookannacookan 2013/03/26 15:52 実際に自分で動かしてみればいいんじゃないですか?それが確実ですよ。
また「どの条件にも合わない時に最終的にそのjsファイルのみが起動するようにはできないのでしょうか」については、autoreply.js を好きなように直せばできると思います。頑張ってください。

mondamotimondamoti 2013/03/26 16:19 確かにそのほうが確実かつ合理的ですね!!ありがとうございます!!
わかりました、色々とためしてみますね。

hikitohikito 2013/04/08 05:09 SimpleAPIを使用して「wiki:hoge」のようなチャットに対しwikipediaのhogeの定義を返したいのですが、
実行した結果↓のスクリプトでは何も応答がありません。

function(user, body){
var xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
var ss = body.split(":");
var uri = 'http://wikipedia.simpleapi.net/api?keyword='+ ss[1] + '&output=rss';
WScript.echo('URL: ' + uri);
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 title = items[i].getElementsByTagName('title')[0].firstChild.nodeValue;
var desc = items[i].getElementsByTagName('description')[0].firstChild.nodeValue;
result.push(title);
result.push(desc);
if(3 <= result.length) break;
}
return result.join('\n');
}

autoreply.jsは特に書き換えていません。
ご教授お願いします。随分と古い話なのにすみません。

nacookannacookan 2013/04/08 13:12 処理がどこまで進んでいて、どこから動かないのか、それぞれの行の間にWScript.echoを入れるなどして調べたらいいんじゃないでしょうか。

hikito hikito 2013/04/09 09:43 調べてみたところitemを上手く拾ってこれないみたいです。
var items = xmlhttp.responseXML.getElementsByTagName('item');
この行の直下にWScript.echoでitems.lengthを調べたのですが、0になっていました。
リンク先のxmlを調べても構造は同じように見えるのですが・・・

nacookannacookan 2013/04/09 10:57 もはやそれSkypeの制御と関係ないのでこの場で話しても仕方ないことだと思いますが、とりあえずresponseXMLじゃなくresponseTextの方も見てみたらどうですか?ホントに0件なのかも知れないですよ。

hikitohikito 2013/04/14 14:56 ごめんなさい、完全にカテ違いでしたね・・・
検索語句をURLにエンコードしたら解決しました。
もう少し悩むべきでした申し訳ありません。ありがとうございました。

tatikomatatikoma 2013/05/26 15:48 if(!reply) continue; //replyじゃなかったとき、飛ばす
chat.SendMessage(reply); //replyだったとき、メッセージを出す
の文の後に、
if(reply) break; //replyだったとき、ループを壊す
を入れてみたところ、チャット上に
Failed to find bot
という文が現れるんですが、どこに問題があるのでしょう。

nacookannacookan 2013/05/26 17:48 わかりませんが、そのメッセージを誰が出力してるのか調べるのがいいんじゃないですかね

丼マン丼マン 2014/09/29 08:38 >cscript autoreply.js
autoreply.js(5, 1) Microsoft JScript 実行時エラー: オートメーション サーバーはオブジェクトを作成できません。

コマンドプロンプトで実際に起動しようとするとこのようなエラーを吐かれてしまうのですが、、、

nacookannacookan 2014/09/29 12:01 同じことを言ってる人がすでにこのコメント欄の中にいて、その人は無事解決しています。探して読んでください。

丼マン丼マン 2014/09/29 20:30 よくよく見ないで投稿してしまいました。申し訳ないです。

z変換ってなんだよ(理解不能)z変換ってなんだよ(理解不能) 2015/03/24 15:44 この記事を読んでどんどん成長()していくプログラムが書けそうだなとおもい、上のサンプルを以下のように書き換えました
autoreply.config.json
[{file:'reply_chat.js'}]

reply_chat.js
function(user, body){
var fs = WScript.CreateObject("Scripting.FileSystemObject");
var file = fs.OpenTextFile("chat_reply.txt",8,true);
file.WriteLine(body);
file.Close();

file = fs.OpenTextFile("chat_reply.txt",1,false);
var count=0;
while( file.AtEndOfStream==false )
count = count + 1;
file.Close();

var rand;
rand = Math.floor( Math.random() * count);
file = fs.OpenTextFile("chat_reply.txt",1,false);
var i;
for( i=0; i<rand; i++ )
WScript.Echo(file.ReadLine());
var str = file.ReadLine();
return str;
}
しかしながらうまく読み込んでくれません。
返信すらしないのです。
これはどのような原因があるのでしょうか?

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証