Chat bot for posting recent answers I am the half-robot side of syb0rg that will be posting the recent answers of Code Review to the CR Answers chatroom.  Here is the list of review suggestions I would like, in order of preference:


Efficiency (with API requests, speed of login and posting answers, etc.)
Security issues
Best practices


For feature requests regarding the chat bot, please see this meta post.

Any and all reviews are acceptable however.  Don't be too harsh please, this is one of my first times using Ruby.

ACCESS_TOKEN = '&lt;insert key&gt;'
# get your access token here:
# https://stackexchange.com/oauth/dialog?client_id=2666&amp;redirect_uri=http://keyboardfire.com/chatdump.html&amp;scope=no_expiry
$root = 'http://stackexchange.com'
$chatroot = 'http://chat.stackexchange.com'
$room_number = 12723
site = 'codereview'
email = '&lt;insert email&gt;'
password = '&lt;insert password&gt;'

require 'rubygems'
require 'mechanize'
require 'json'
require 'net/http'

loop
{
    begin

    $agent = Mechanize.new
    $agent.agent.http.verify_mode = OpenSSL::SSL::VERIFY_NONE

    login_form = $agent.get('https://openid.stackexchange.com/account/login').forms.first
    login_form.email = email
    login_form.password = password
    $agent.submit login_form, login_form.buttons.first
    puts 'logged in with SE openid'

    meta_login_form = $agent.get($root + '/users/login').forms.last
    meta_login_form.openid_identifier = 'https://openid.stackexchange.com/'
    $agent.submit meta_login_form, meta_login_form.buttons.last
    puts 'logged in to root'

    chat_login_form = $agent.get('http://stackexchange.com/users/chat-login').forms.last
    $agent.submit chat_login_form, chat_login_form.buttons.last
    puts 'logged in to chat'

    $fkey = $agent.get($chatroot + '/chats/join/favorite').forms.last.fkey
    puts 'found fkey'

    def send_message text
        loop 
        {
            begin
            resp = $agent.post("#{$chatroot}/chats/#{$room_number}/messages/new", [['text', text], ['fkey', $fkey]]).body
            success = JSON.parse(resp)['id'] != nil
            return if success
            rescue Mechanize::ResponseCodeError =&gt; e
            puts "Error: #{e.inspect}"
            end
            puts 'sleeping'
            sleep 3
        }
    end

    puts $ERR ? "An unknown error occurred. Bot restarted." : "Bot initialized."

    last_date = 0
    loop 
    {
        uri = URI.parse "https://api.stackexchange.com/2.2/events?pagesize=100&amp;since=#{last_date}&amp;site=#{site}&amp;filter=!9WgJfejF6&amp;key=thqRkHjZhayoReI9ARAODA((&amp;access_token=#{ACCESS_TOKEN}"
        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
        data = JSON.parse http.get(uri.request_uri).body
        events = data['items']

        data['items'].each do |event|
            last_date = [last_date, event['creation_date'].to_i + 1].max
            if ['answer_posted'].include? event['event_type']
                send_message "[tag:rob0t] New answer detected:"
                send_message event['link']
                puts "Answer posted."
            end
        end
        puts "#{data['quota_remaining']}/#{data['quota_max']} quota remaining"
        sleep(40 + (data['backoff'] || 0).to_i) # add backoff time if any, just in case
    }

    rescue =&gt; e
        $ERR = e
        p e
    end
}


(Attribution to original author, code above is a modified version of it.)
For starters, indent your code consistently — the standard in Ruby is two spaces.  That includes indenting the contents of your begin-rescue-end blocks.

Normally, I don't like to make such a huge fuss about indentation, but in this case I think it's highly important, because:


Your program has a highly unusual outline (infinite loops and a function definition(!) inside an infinite loop)
The stakes are high: if you misbehave, you could make a lot of people upset.  Therefore, good software engineering practices should be used.


An outline like this would be more idiomatic for Ruby:

class AnswerBot
  ROOT = 'http://stackexchange.com'
  CHAT_ROOT = 'http://chat.stackexchange.com'

  def initialize(options)
    @agent = Mechanize.new
    @options = options
  end

  def login
    # Do stuff with @agent
    login_form = $agent.get('https://openid.stackexchange.com/account/login').forms.first
    login_form.email = @options[:email]
    # ...
    @fkey = @agent.get(CHAT_ROOT + '/chats/join/favorite').forms.last.fkey
  end

  def fetch_answers
    # Make request to api.stackexchange.com
    # ...
    data['items'].each { |event| yield event }
    return (data['backoff'] || 0).to_i
  end

  def send_message(text, retries=5, backoff=40)
    # ...
  end
end

bot = AnswerBot.new(:access_token =&gt; ...,
                    :room_number = 12723,
                    :site =&gt; 'codereview',
                    :email =&gt; ...,
                    :password =&gt; ...)
loop {
  begin
    bot.login

    do
      backoff = bot.fetch_answers do |event|
        if ['answer_posted'].include?(event['event_type']) # &lt;-- Is that right?
          bot.send_message(...)
        end
      end
    while sleep(40 + backoff)
  rescue =&gt; e
    puts "An error occurred."
    p e
  end
  puts "Bot restarted."
}