#!/usr/bin/env ruby $LOAD_PATH << 'lib' $LOAD_PATH << '../lib' require 'optparse' require 'uri' require 'rubygems' require 'daemons' require 'net/irc' require 'mechanize' require 'nokogiri' Version = '0.0.1' =begin = mixi_voice_bot.rb == IRC チャンネルに JOIN して mixi ボイスの閲覧・投稿・返信・削除を行う Bot Authors:: Tomohiro, TAIRA Version:: 0.0.1 Copyright:: Copyright (C) Tomohiro, TAIRA, 2009. All rights reserved. URL:: https://github.com/Tomohiro == 参考 https://github.com/cho45/net-irc/blob/master/examples/echo_bot.rb == 起動例 (1) 通常の起動 * $ ./mixi_voice_bot.rb -h irc.example.com -c "#mixi_example" -n MyNick -E your_account@example.com -P your_mixi_password (2) デーモンとして起動 * $ ./mixi_voice_bot.rb -h irc.example.com -c "#mixi_example" -n MyNick -E your_account@example.com -P your_mixi_password -D (3) 起動オプション * $ ./mixi_voice_bot.rb --help Usage: mixi_voice_bot [options] -h, --irc-host HOST 接続先の IRC サーバ名 -p, --irc-port [PORT=6667] 接続先の IRC ポート番号 (規定は 6667) -b, --bot-name [BOT=voice] mixi ボイスを発言する bot の名前 (規定は voice) -c, --channel CHANNEL 接続先のチャンネル名 -n, --nickname NICK ボイスへの投稿を許す IRC のニックネーム -t, --crwal-time [SEC=60] mixi ボイスをクロールする間隔 (規定は 60秒) -E, --email EMAIL mixi のログイン Email アドレス -P, --password PASSWORD mixi のログインパスワード -D, --daemonize プロセスをデーモン化する == 使用方法 (1) mixi ボイス Bot と同じチャンネルに JOIN する (2) 閲覧: [ニックネーム] メッセージ (KEY) の形式で表示される (3) 投稿: 発言する (4) 返信: 以下のフォーマットで発言する * re KEY メッセージ (5) 削除: 以下のフォーマットで発言する * rm KEY =end class MixiVoiceBot < Net::IRC::Client MIXI_LOGIN_URI = 'http://mixi.jp' RECENT_VOICE_URI = 'http://mixi.jp/recent_echo.pl' def initialize setup_options super(@irc_host, @irc_port, { :nick => @bot_name, :user => @bot_name, :real => @bot_name, :channel => @channel, }) @agent = WWW::Mechanize.new if ENV['http_proxy'] proxy = URI.parse(ENV['http_proxy']) @agent.set_proxy(proxy.host, proxy.port) end @caches = [] @identity = nil end def setup_options @irc_port = 6667 @bot_name = 'voice' @crawl_time = 60 ARGV.options do |o| o.on('-h', '--irc-host HOST', '接続先の IRC サーバ名') { |v| @irc_host = v } o.on('-p', "--irc-port [PORT=#{@irc_port}]", '接続先の IRC ポート番号 (規定は 6667)') { |v| @irc_port = v } o.on('-b', "--bot-name [BOT=#{@bot_name}]", 'mixi ボイスを発言する bot の名前 (規定は voice)') { |v| @bot_name = v } o.on('-c', '--channel CHANNEL', '接続先のチャンネル名') { |v| @channel = v } o.on('-n', '--nickname NICK', 'ボイスへの投稿を許す IRC のニックネーム') { |v| @nickname = v } o.on('-t', "--crwal-time [SEC=#{@crawl_time}]", 'mixi ボイスをクロールする間隔 (規定は 60秒)') { |v| @crawl_time = v } o.on('-E', '--email EMAIL', 'mixi のログイン Email アドレス') { |v| @email = v } o.on('-P', '--password PASSWORD', 'mixi のログインパスワード') { |v| @password = v } o.on('-D', '--daemonize', 'プロセスをデーモン化する') { |v| Daemons.daemonize } o.parse! end end def start login @identity = get_identity @socket = TCPSocket.open(@host, @port) @socket.gets post(NICK, @opts.nick) post(USER, @opts.user, '0', '*', @opts.real) post(JOIN, @opts.channel) post_thread = Thread.new do while line = @socket.gets message = Message.parse(line) puts message name = "on_#{(COMMANDS[message.command.upcase] || message.command).downcase}" send(name, message) if respond_to? name end end get_thread = Thread.new do loop do get sleep @crawl_time end end post_thread.join get_thread.join rescue IOError => e @log.error 'IOError' + e.to_s ensure finish end def on_privmsg(message) if message.prefix =~ Regexp.new(@nickname) case message[1] when /^re ([0-9]+) (.+)/ reply($1, $2) when /^rm ([0-9]+)/ delete($1) else add(message[1]) end get end end def login @agent.get MIXI_LOGIN_URI do |login_page| login_page.form 'login_form' do |form| form.email = @email form.password = @password end.submit end end def add(voice) @agent.get RECENT_VOICE_URI do |post_page| post_page.form_with(:action => 'add_echo.pl') do |form| form.body = voice end.submit end end def reply(key, voice) if @caches.has_key? key member_id = @caches[key][:member_id] post_time = @caches[key][:post_time] @agent.get RECENT_VOICE_URI do |post_page| post_page.form_with(:action => '/add_echo.pl') do |form| form.body = voice form.parent_member_id = member_id form.parent_post_time = post_time end.submit end else post(NOTICE, @opts.channel, '指定された返信先が見つかりません') end end def delete(post_time) @agent.post "http://mixi.jp/delete_echo.pl?post_time=#{post_time}&post_key=#{@identity}&redirect=recent_echo" @caches = crawl_recent_voice end def get voices = crawl_recent_voice voices.sort.each do |key, voice| if @caches.empty? or !@caches.has_key? key post(PRIVMSG, @opts.channel, "[#{voice[:nickname]}]#{voice[:reply]} #{voice[:comment]} (#{key})") sleep 5 end end @caches = voices end def get_identity recent_page = @agent.get RECENT_VOICE_URI identity = (Nokogiri::HTML(recent_page.body)/'input#post_key').first['value'] end def crawl_recent_voice recent_page = @agent.get RECENT_VOICE_URI voices = {} (Nokogiri::HTML(recent_page.body)/'td.comment').each do |comment| key = timestamp(comment) voices[key] = build_voice(comment) end voices end def timestamp(comment) comment.at('div.echo_post_time').text end def build_voice(comment) { :member_id => comment.at('div.echo_member_id').text, :post_time => comment.at('div.echo_post_time').text, :nickname => comment.at('div.echo_nickname').text, :reply => ((' ' + comment.at('a').text) if comment.at('a').text =~ /^>/), :comment => comment.at('div.echo_body').text } end end MixiVoiceBot.new.start