0

skype-socket-gatewayを作った

Skypeのグループチャットにbotを放ったり色々したかったので作った。

Skype APIはSkypeが起動しているマシンの中でSkypeのプログラムと自分のアプリを通信させて使う物で、developer.skype.comにAPIのドキュメントがある。電話をかけたりチャットの中身を取得したり発言したりできる。

Skypeが起動しているマシンでskype socket gatewayを起動しておくと、他のマシンからもtelnetでSkype APIが使える。また、全チャット(含むグループチャット)のデータがJSONでどんどん流れてくるようになっている。

うちではWindows XPのネットブックでbotアカウントでSkypeを起動してあった。Skype chatはP2Pなのでログをそれぞれが持っていて、後で接続してきた人は先に居た人から取得する様になっている。だから、ただ何もしなくてもずっとオンラインの人がいるだけでログの流れが良くなる。
で、そのbotアカウントを真なるbotにするために、Windows用のActiveScriptRuby 1.8.7でskype socket gatewayを動かしてそこに別のLinuxのサーバーから複数のbotが接続して色々処理をしている。

Linux上で動くSkype用のbotを作る方法 – muddy brown thangにXvfbを使ってlinuxで仮想フレームバッファを作ってSkype APIを使う方法が解説されてるけど、なんかLinuxが難しくて挫折したのでwindowsでも動くsocket gatewayを作った。


■ソースコード
githubに全部置いておいた
shokai's skype-socket-gateway at master – GitHub


■インストール

git clone git://github.com/shokai/skype-socket-gateway.git

必要なgemもインストールする
gem install Ruby4Skype json eventmachine

Ruby4Skypeというのを使っているので、Windows、Mac、Linuxで動く。ただしRuby4Skypeが64bit MacOSXでは動かない。Linuxも、Ruby4Skypeのlib/skype/os/linux.rbを修正してinitialize methodのsuperにapp_nameを渡すように修正しないと動かない。

それとWindowsでRuby4Skypeを動かすには色々DLLを入れないとダメ → WinXPでgem install Ruby4Skypeするメモ – yuisekiのいまさらruby厨日記 – Rubyist


■起動する
先にSkypeを起動しておく。

ruby skype-gateway.rb
でport 20000で起動する。


■使う
telnetで接続する。
telnet 192.168.1.37 20000

そしてそのまま
CALL shokaishokai
MESSAGE shokaishokai hello work!!
とか書く。Skype APIがそのまま実行できる。
レスポンスはJSONで来る。

あと、接続しているだけで全てのchatの発言が流れてくる。


■Windowsのスタートアップに登録する
run_skype_gateway.bat というバッチファイルを作る
"C:\Program Files\ruby-1.8\bin\ruby.exe" "C:\Document and Settings/sho/src/ruby/skype-socket-gateway/skype-gateway.rb"
中身はこの1行でいい。これをスタートメニューのスタートアップに入れておけば自動起動できる。


■Skype botを作る
RubyだとTCPSocketクラスを使うと簡単に作れる。

キーワードに反応する単純なbotの例。誰かが「ざんまい」と書くと、即座に「ざんまい行きたい!」と返す。
samples/zanmai_bot.rb at master from shokai's skype-socket-gateway – GitHub
require 'rubygems'
require 'socket'
require 'json'
require 'eventmachine'
$KCODE = 'u'

HOST = "192.168.1.37"
PORT = 20000

begin
  s = TCPSocket.open(HOST, PORT)
  s.puts "MESSAGE shokaishokai ざんまいbot start"
rescue => e
  STDERR.puts e
  exit 1
end

EventMachine::run do
  
  EventMachine::defer do
    loop do
      res = s.gets
      exit unless res
      res = JSON.parse(res) rescue next
      p res
      if res['type'] == 'chat_message' 
        if res['body'] =~ /ざんまい/ # キーワードに反応
          s.puts "CHATMESSAGE #{res['chat']} ざんまい行きたい!"
        elsif res['body'] =~ /かず(すけ|助)/
          s.puts "CHATMESSAGE #{res['chat']} かずにゃんぺろぺろ"
        end
      end
    end
  end
  
  EventMachine::defer do
    loop do
      mes = gets                                                                                                                            
      s.puts mes if mes                                                                                                                     
    end
  end

end


これにもう少し語彙を追加すると、だいたい俺の人格と機能を8割方再現したbotが作れる。
shokai_bot.rb at master from shokai's skype-bots – GitHub
6a8dae3e0028f2f870f194cdbec2af75.png


あと、全chatをmongo dbに入れて検索したりできるbotなんかもいる。超便利。
tools/skype_chat_store_mongod.rb at master from shokai's skype-socket-gateway – GitHub
fecba229c251c20a4543e21bd1395929.png


はてなランドに自分のskypeの発言を送るなんて事もできる
hatenaland_proxy.rb at master from shokai's skype-bots – GitHub


■EventMachine
EventMachineというサイバーパンクな名前のライブラリがRubyにあって、これを使うと並行処理が簡単に書けた。
skype socket gatewayでは
  • 標準入力をSkype APIとして実行する
  • 接続してきたTCP Socketクライアントそれぞれからの入力をSkype APIとして実行し、結果をそれぞれに返す
  • 全ソケットクライアントに全chat logを送る
  • クライアントが接続されているかたまに確認する
という処理をやっていて、これらを適当にEventMachine::defer doの中でloopで書いてやればうまく動いた。

不思議だったのは、新しくacceptしたsocket clientオブジェクトそれぞれを個別に処理するためにEventMachine::deferの中でclient毎にそれぞれEventMachine::deferを作ってみたら、ちゃんとスコープが別になってくれた。
  EventMachine::defer do
    loop do
      s = sock.accept
      clients << s
      puts "--- new client : #{clients.size}"
      
      # socket -> invoke Skype API
      EventMachine::defer do
        loop do
          cmd = s.gets
          next if cmd == nil or cmd.to_s.size < 1
          puts "recv => #{cmd}"
          begin
            p res = {
              :type => 'api_response',
              :body => skype.invoke(cmd).to_s
            }
          rescue => e
            res = {
              :type => 'error',
              :body => 'skype api invoke error'
            }
            STDERR.puts e
          end
          begin
            s.puts "\n"+res.to_json
          rescue => e
            STDERR.puts e
            c.close
            break
          end
        end
      end
    end
  end


それぞれのdeferの中でAPI呼び出しと結果を返す処理が動いている。これをthreadでやる時
Thread::start(s) do |s|
  loop do
    res = s.gets
    # 何か処理
    s.puts response.to_json
    sleep 0.01
  end
end
みたいに引数に渡さなくても勝手にスコープ分けてくれてた。


でも、clientの配列とかchatメッセージのqueueを作って複数のEventMachine::deferからdeleteしたりpushしたりしていじっている部分では、defer間で共有されていた。不思議だ。
EventMachine::run do
  # forward all skype chats -> socket
  EventMachine::defer do
    loop do
      if chat_msgs.size > 0
        msg = "\n"+chat_msgs.shift.to_json 
        clients.each{|c|
          begin
            c.puts msg
          rescue => e
            STDERR.puts e
          end
        }
      end
    end
  end

  # check clients connection
  EventMachine::defer do
    loop do
      msg = ''
      errors = Array.new
      clients.each{|c|
        begin
          c.puts msg
        rescue => e
          STDERR.puts e
          errors << c
        end
      }
      errors.each{|c|
        clients.delete(c)
        c.close
      }
    sleep 15
    end
  end

よくわからないけどEventMachineが凄い。

1

im.kayac.comのgem作った

im.kayac.comのAPIを使うためのgemを作った。
im.kayac.comは自分のアプリからiPhoneにpush通知したり、自分のgoogle talkにメッセージを送ったりするのに便利なwebサービスで、俺はよく使ってる。

研究室のwikiの更新情報を自分のケータイに通知したりとか、あとtwitter検索の結果を通知したりとかするアプリの中で使う為にこのgemを作った。Androidケータイがgoogle talkをすごいレスポンスで受信できるので、我ながらいいアプリ作ったと思っている。


■インストール

gem install im-kayac


■使う
require 'rubygems'
require 'im-kayac'

begin
  p ImKayac.post("username", "hello world")
rescue => e
  STDERR.puts e
end


im.kayac.comの設定でパスワード認証をしているなら、引数で渡す。
ImKayac.post("username", "hello world", {:password => 'your-password'})


秘密鍵認証の場合はこうする。
require 'digest/sha1'

message = 'hello world'
sig = Digest::SHA1.hexdigest(message + 'your-sig')
p ImKayac.post("username", message, {:sig => sig})


■ソースコードとか

1

hugeurlというgemを作った

ふげではなくヒュージ。
tinyurlやbit.lyなどの短縮URLを展開できる。


■インストール

gem install hugeurl


■使う
require 'rubygems'
require 'hugeurl'
puts URI.parse("http://bit.ly/d4VYD2").to_huge
するとhttp://shokai.orgのURI::HTTPインスタンスが返ってくる

もしくは
Hugeurl.get("http://bit.ly/d4VYD2")
でもいい。

展開には http://search.twitter.com/hugeurl?url= を使っている。

1

gyaazzというアウトラインエディタ風wikiを作っている

増井先生が作ってるgyazzが元ネタ。JavaScriptの勉強がしたくてN村ヒロユキっていう人と作った。

使い方は使い方のページを見て欲しい。
ダブルクリックした行から編集できる。方向キーか、ctrlキーを押しながらemacs的操作でカーソル移動できる。
一応アウトラインエディタなので、ctrlやshiftを押しながらキー操作するとブロック毎移動したりインデントしたり、ブロックまるごと入れ替えたりできる。
[[ と ]]で囲んだ部分がURLならリンクに、画像へのURLだったらimgタグに、URLでなければ新しいwikiページへのリンクになる。
しばらく操作しないで置いておくと、右下に「saved!」と表示されて編集が保存される。
あと他の人が編集するとsyncする。


UIはjQueryを使ったので、JSよくわからないけど2、3日でエディタ的機能は実装できた。
サーバー側はSinatraを使っている。ページのデータをJSONで返す簡単なAPIが用意されている。なので一番上の階層に小文字で「api」というページは作れない。

データベースにはTokyoCabinetを使ってる。

ソースコードとインストール方法はhttp://github.com/shokai/gyaazzにある。
でもmongodb使って作り直そうかな

あと、
  • ページ名の変更
  • ページのコピー
  • 認証付きページを作れる機能
が必要だな・・・

0

ムームーDNS設定

shokai.orgはムームードメインで取ったんだけど、1年ぐらい前からムームーDNSというのが無料で使えるようになってたのでいい加減設定してみた。

ムームーDNSを使うと

  • shokai.org, www.shokai.orgはさくらインターネットのレンタル共有サーバーへ
  • メールもさくらへ
  • dev.shokai.orgは他の場所に置いてある自分の開発サーバーへ
送るという設定ができる。

ムームーDNSは便利だが設定が超ややこしい。あらかじめ使ってないドメインで試してみてからshokai.orgの設定をしたが失敗して3時間以上悩んだ。


ムームードメイン – ムームーDNSのセットアップ情報変更から、ムームーDNSを「利用する」に変更する。

それから、各レコードを設定する。
さくらのレンタルサーバーにドメインを関連づけるには、まずさくらインターネットサーバコントロールパネル ドメイン設定からマルチドメインを追加しておく。次にサーバー情報の表示にあるIPアドレスをメモしておく。

ムームーDNSの設定で、サブドメインを空にして、AレコードにさっきのサーバーのIPアドレスを入れる。
MXはドメイン名を入れる。
www付きでもアクセスできるようにしたかったら、wwwも付けて同じIPアドレスを入れておく。
サブドメイン「dev」をAレコードで、自分の開発サーバーのIPアドレスを入れる。
ムームーDNS設定
普通さくらのレンタルサーバーにドメインを向けるには、ムームードメインの設定で「上記以外 のネームサーバを使用する」を選んでさくらのDNSを指定するけど、ムームーDNSからIPアドレスで直接指定してしまえばwebもメールもsshも通った。


まだ設定は終わっていない。
最後に、ムームードメイン – ドメイン詳細からドメイン名を選んで、「ネームサーバ設定変更」というボタンを押して一番上の「ムームードメインのネームサーバ(ムームーDNS)を使用する」を選ぶ。

この最後の操作を忘れやすい。ようするにムームーDNSの設定はしたけど、ムームードメインがムームーDNSを使うようになっていなくて3時間ぐらい悩んだ。


最後に確認する。
ping shokai.org
nsloolup shokai.org