sifue's blog

プログラマな二児の父の日常

XML編集で簡単に返信内容を作れるRubyの TwitterBotスクリプト

今日の昼、TwitterBotを作ろうと思い立ち、さくっと作ってみました。アルゴリズム的にはかなり簡単な内容になってます。

WindowsのタスクやUnix系のcronタスクで特定の時間置きに起動

フォローした人をフォローし返し、フォローやめた人をフォローをやめ返す

はじめての人にはウエルカムポストを実行

他人の時間内のタイムラインの中で、指定したキーワードを含む内容があるかチェック、あればコメントを返信し終了

返信する内容がなければ、一人でつぶやき終了

以上の流れになっています。ソースは末尾にあります。
設置の仕方は、


1. 設定ファイルとスクリプトのzipをダウンロードして解凍。
2. Rubyをインストール(ググって)
3. Rubygemsをインストール(ググって)
4. (sudo) gem install twitter をコンソールで打ってtwitter4rをインストール
5. bot用のtwitterアカウントを作る(画像とか説明とか準備)
6. 解凍した設定ファイルのconf.ymlをメモ帳以外のUTF-8が編集できるエディタで設定(ログイン名、パスワード、ランダム投稿か実行が少ない順と投稿かどうか、投稿間隔)

## YAML形式なので、タブのインデントや順番を崩さないように編集してください。
## loginはユーザー名、post_randomはtrue(ランダムで内容選択)かfalse(回数少ない順)で、
## インターバルは秒で設定してください(2時間は7200秒、6時間は21600、12時間は43200など)
bot:
  login: aisha_bot
  password: password
  post_random: false
  interval: 3600

7. 返信用のコンテンツのreply.xmlをメモ帳以外のUTF-8が編集できるエディタで設定 (フォローした内容に反応するキーワードと返信内容を書く、last_timeとcountは実績なので適当な内容で大丈夫)

<reply>
    <keyword term='@aisha_bot'>
        <comment last_time='1253687695' count='0' content='私にコメントありがとう!'/>
        <comment last_time='1253687695' count='0' content='すっごく元気がでてきたよ!'/>
        <comment last_time='1253687695' count='0' content='うれしい!'/>
    </keyword>
    <keyword term='おはよう'>
        <comment last_time='1253687695' count='0' content='今日もあなたによいことがありますように'/>
        <comment last_time='1253687695' count='0' content='やっとおきた?'/>
        <comment last_time='1253687695' count='0' content='おはよう!'/>
    </keyword>
    <keyword term='こんにちは'>
        <comment last_time='1253689729' count='0' content='こんにちは、よろしくね!'/>
    </keyword>
    <keyword term='こんばんは'>
        <comment last_time='1253689729' count='0' content='いい夢みてね'/>
        <comment last_time='1253689729' count='0' content='こんばんは!'/>
    </keyword>
    <keyword term='わかる'>
        <comment last_time='1253689729' count='0' content='イト...はい'/>
    </keyword>
    <keyword term='ニーサ'>
        <comment last_time='1253689729' count='0' content='母なる神ニーサ、我らを導きたまえ'/>
        <comment last_time='1253689729' count='0' content='偉大なニーサよ、我らを守りたまえ'/>
    </keyword>
</reply>

8. 同様に一人つぶやきの際のコンテンツのpost.xmlをメモ帳以外のUTF-8が編集できるエディタで設定

<?xml version='1.0' encoding='UTF-8'?>
<post>
    <comment last_time='1253691596' count='0' content='アイシャ...アメ ホオ...'/>
    <comment last_time='1253689416' count='0' content='カヤキス!カヤキスレビタ!'/>
    <comment last_time='1253689416' count='0' content='えっ おゆにはいるの? やけどしちゃう!'/>
    <comment last_time='1253689416' count='0' content='みちゃだめ!!'/>
    <comment last_time='1253689416' count='0' content='...へん!水は つめたいほうがいいな'/>
    <comment last_time='1253689416' count='0' content='アイシャです'/>
    <comment last_time='1253689416' count='0' content='やさしいかたですね'/>
    <comment last_time='1253689416' count='0' content='私の おじいちゃんが なにか?'/>
    <comment last_time='1253689416' count='0' content='ジャミルって あなたのこいびと?'/>
    <comment last_time='1253689416' count='0' content='私もがんばるわ!'/>
    <comment last_time='1253689416' count='0' content='神々よ、お力をお貸しください!'/>
    <comment last_time='1253689416' count='0' content='おじいちゃん...'/>
    <comment last_time='1253689416' count='0' content='私、タラール族のアイシャ。よろしくね。'/>
    <comment last_time='1253689416' count='0' content='はーい わかりました'/>
    <comment last_time='1253689729' count='0' content='母なる神ニーサ、我らを導きたまえ'/>
    <comment last_time='1253689729' count='0' content='偉大なニーサよ、我らを守りたまえ'/>
</post>

10. 同様にウェルカムの際のコンテンツのwelcome.xmlをメモ帳以外のUTF-8が編集できるエディタで設定

<?xml version='1.0' encoding='UTF-8'?>
<post>
    <comment last_time='1253707638' count='1' content='はじめまして、私はタラール族のアイシャ、よろしくね。'/>
    <comment last_time='1253691596' count='0' content='はじめての方ね。これからよろしくね。'/>
</post>

10. 後は適当なところにフォルダを置いて、ruby main.rbが定期的に実行されるように設定。crontab -eで1時間置きの4分に実行ならば

4 0-23/1 * * * ruby /home/****/ruby/aisha_bot/main.rb /dev/null 2>&1

一応、Ubuntu8.04で5時間ぐらい動かしてみて問題なさそうな感じです。これで完了。


Windowsでは実行確認はしてませんがWindowsのタスクなら.batを以下の内容で作成。

ruby c:\twitter_bot\aisha_bot\main.rb

その後、コントロールパネル>管理>タスクからそのバッチを好きな間隔で実行するようにして完成です。


というわけで、今回完成したbotは以下の通り!自分が好きなロマンシングサガ ミンストレルソングのアイシャのbotです。
http://twitter.com/aisha_bot


botのプログラムを作るのは思ったより簡単ですが、面白いコンテンツを作ろうと思うと結構難しい。今後コンテンツはゆっくり作ろうかなと思います。
あと、自分でbotを作ってみたい方ソースは以下にありますので、いろいろ改造したりして遊んでみてください。あと何かアドバイスなどありましたらよろしくお願いします。


追伸
1時間に一回実行するようにして、ウェルカムポストも実行するように改造しました。これで大抵のことはできるようになっているのではと思います。

# Twitter Bot スクリプト
# 
# フォローされている人の中で、自分がフォローしていないひとが入ればフォローし、
# フォローされなくなっている人がいれば、フォローをはずす。
# はじめての人にはウェルカムポストをする。
# その後、誰かがヒットするキーワードを言っていれば、コメントをランダムにリプライし、
# 誰にもリプライできなかった時は、つぶやくBot。
# コメント選択は、ランダムか、回数少ない順かつ最後に投稿されたもの順のどちらかが選択可
#
# 2009/09/23
# Copyright(c) 2009- Soichiro YOSHIMURA

require 'rubygems'
gem 'twitter4r', '>=0.3.0'
require 'twitter'
require 'twitter/console'
require 'kconv'
require 'yaml'
require "rexml/document" 

# カレントディレクトリ設定
Dir::chdir(File::dirname(__FILE__))

# 各種設定を読み込む
env = 'bot'
conf_file = 'conf.yml'
conf = YAML::load(File.read(conf_file))
bot_conf = conf[env]
my_name = bot_conf['login'] # ログイン名
is_random = bot_conf['post_random'] == 'true' # ランダムポストかどうか
interval = bot_conf['interval'].to_i # 間隔の秒数

# Twitter4Rのインスタンスを生成する
twitter = Twitter::Client.from_config( conf_file , env )

# 新しいfollowerをフォロー返し、フォローやめた人を取り除く
follower_names = Array.new()
twitter.my(:followers).each { |user| follower_names << user.screen_name}
friend_names = Array.new()
twitter.my(:friends).each { |user| friend_names << user.screen_name}
add_followers = follower_names - friend_names
add_followers.each { |name| twitter.friend(:add, name)}
remove_followers = friend_names - follower_names
remove_followers.each { |name| twitter.friend(:remove, name) }

###### 新規に登録してくれた人のためのメッセージを返す #####
doc_post = REXML::Document.new(File.read('welcome.xml'))
post = doc_post.root()
comments = post.get_elements('comment')
add_followers.each { |name|
  comment = nil
  if is_random
    comment = comments[rand(comments.length)]
  else
  # ポスト回数・日付順に破壊的ソート
    comments.sort! { |a,b|
      a.attributes.get_attribute('last_time').to_s.to_i <=> b.attributes.get_attribute('last_time').to_s.to_i
    }.sort! { |a,b|
      a.attributes.get_attribute('count').to_s.to_i <=> b.attributes.get_attribute('count').to_s.to_i
    }
    # 最も古くてポスト回数が少ないものを選択
    comment = comments[0]
  end

  # ポスト!
  twitter.status(:post,
   Kconv.kconv( "@" + name + " " + comment.attributes.get_attribute('content').to_s,
     Kconv::UTF8 ) )

  # 属性を更新
  comment_attrs = comment.attributes
  count = comment_attrs.get_attribute('count').to_s.to_i
  comment.delete_attribute('count')
  comment.add_attribute('count', (count + 1).to_s)
  comment.delete_attribute('last_time')
  comment.add_attribute('last_time', Time.now.to_i.to_s)
  puts "post_welcom(" +  name + "): " + comment.to_s
}
# ファイルに保存
io = open('welcome.xml','w')
doc_post.write(io)
io.close

###### 返信内容のXMLのDOMを取得し、一つ一つ返信していく ########
is_reply = false
doc_rep = REXML::Document.new(File.read('reply.xml'))
reply = doc_rep.root()
twitter.timeline_for( :friends) do | status |
  
  # インターバル時間より前のコメントはパス
  next if status.created_at < Time.at(Time.now.to_i - interval)
  # 自分の投稿はパス
  next if status.user.screen_name == my_name

  reply.elements.each{|k|
   term = k.attributes.get_attribute('term').to_s
   # 一つ一つのキーワードに関して、そのキーワードを含んでいるか
   next if !status.text.include?(term)

   # 含んでいればどのコメントをポストするか判定へ
   comments = k.get_elements('comment')
   comment = nil
   if is_random
    comment = comments[rand(comments.length)]
   else
    # ポスト回数・日付順に破壊的ソート
    comments.sort! { |a,b|
      a.attributes.get_attribute('last_time').to_s.to_i <=> b.attributes.get_attribute('last_time').to_s.to_i
    }.sort! { |a,b|
      a.attributes.get_attribute('count').to_s.to_i <=> b.attributes.get_attribute('count').to_s.to_i
    }
    # 最も古くてポスト回数が少ないものを選択
    comment = comments[0]
   end
   # ポスト!
   twitter.status(:post,
     Kconv.kconv(
       "@" + status.user.screen_name + " " + comment.attributes.get_attribute('content').to_s,
       Kconv::UTF8 ) )
   # 属性を更新して、返信フラグを立てる
   comment_attrs = comment.attributes
   count = comment_attrs.get_attribute('count').to_s.to_i
   comment.delete_attribute('count')
   comment.add_attribute('count', (count + 1).to_s)
   comment.delete_attribute('last_time')
   comment.add_attribute('last_time', Time.now.to_i.to_s)
   is_reply = true
   puts "post_reply(" +  status.user.screen_name + "): " + comment.to_s
  }
end
#XMLの更新を出力
io = open('reply.xml','w')
doc_rep.write(io)
io.close

# 返信していた場合ここで終了
exit(0) if is_reply

########### ひとりごとをポストする場合のスクリプト ######
doc_post = REXML::Document.new(File.read('post.xml'))
post = doc_post.root()
comments = post.get_elements('comment')

comment = nil
if is_random
  comment = comments[rand(comments.length)]
else
# ポスト回数・日付順に破壊的ソート
  comments.sort! { |a,b|
    a.attributes.get_attribute('last_time').to_s.to_i <=> b.attributes.get_attribute('last_time').to_s.to_i
  }.sort! { |a,b|
    a.attributes.get_attribute('count').to_s.to_i <=> b.attributes.get_attribute('count').to_s.to_i
  }
  # 最も古くてポスト回数が少ないものを選択
  comment = comments[0]
end

# ポスト!
twitter.status(:post,
 Kconv.kconv( comment.attributes.get_attribute('content').to_s,
   Kconv::UTF8 ) )

# 属性を更新
comment_attrs = comment.attributes
count = comment_attrs.get_attribute('count').to_s.to_i
comment.delete_attribute('count')
comment.add_attribute('count', (count + 1).to_s)
comment.delete_attribute('last_time')
comment.add_attribute('last_time', Time.now.to_i.to_s)
puts "post: " + comment.to_s

# ファイルに保存
io = open('post.xml','w')
doc_post.write(io)
io.close