jookyboi
3/18/2013 - 7:32 PM

Implement IMAP idle in Ruby.

Implement IMAP idle in Ruby.

SERVER = 'imap.gmail.com'
USERNAME = 'XXX'
PW = 'XXX'
require 'net/imap'
require 'net/smtp'
require 'tmail'
require 'maruku'

def to_markdown(text)
  Maruku.new(text).to_html
end

# Extend support for idle command. See online.
# http://www.ruby-forum.com/topic/50828
# but that was wrong. see /opt/ruby-1.9.1-p243/lib/net/imap.rb.
class Net::IMAP
  def idle
    cmd = "IDLE"
    synchronize do
      @idle_tag = generate_tag
      put_string(@idle_tag + " " + cmd)
      put_string(CRLF)
    end
  end

  def say_done
    cmd = "DONE"
    synchronize do
      put_string(cmd)
      put_string(CRLF)
    end
  end

  def await_done_confirmation
    synchronize do
      get_tagged_response(@idle_tag, nil)
      puts 'just got confirmation'
    end
  end
end

class Remailer
  attr_reader :imap

  public
  def initialize
    @imap = nil
    @smtp = nil
    @mailer = nil
    start_imap
  end

  def process
    puts 'Processing!'
    msg_ids = @imap.search(["SINCE", "25-Nov-2009", "TO", "XXX", "UNSEEN"])
    puts 'Done with search!'

    unless msg_ids.empty?
      start_smtp
      msg_ids.each do |msg_id|
        mail = TMail::Mail.parse(@imap.fetch(msg_id, 'RFC822').first.attr['RFC822'])
        print "Processing #{mail.subject}..."
        mail.to = 'post@posterous.com'
        mail.body = to_markdown(mail.body)
        print ' marked down...'
        @smtp.send_message mail.encoded, mail.from, [mail.to, 'XXX']
        puts ' sent.'

        @imap.store msg_id, '+FLAGS', [:Seen]
      end
    end
  ensure
    stop_smtp
  end

  def tidy
    stop_smtp
    stop_imap
  end

  def bounce_idle
    # Bounces the idle command.
    @imap.say_done
    @imap.await_done_confirmation
    # Do a manual check, just in case things aren't working properly.
    process
    @imap.idle
  end

  private
  def start_imap
    @imap = Net::IMAP.new SERVER, :ssl => true
    @imap.login USERNAME, PW
    @imap.select 'INBOX'

    # Add handler.
    @imap.add_response_handler do |resp|
      if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS"
        @imap.say_done
        Thread.new do
          @imap.await_done_confirmation
          process
          @imap.idle
        end
      end
    end

    process
    @imap.idle
  end

  def stop_imap
    @imap.done
  end

  def start_smtp
    # Don't start more than once.
    return unless @smtp.nil?

    @smtp = Net::SMTP.new 'smtp.gmail.com', 465
    @smtp.enable_tls
    @smtp.start 'XXX', USERNAME, PW
  end

  def stop_smtp
    # Don't stop if we haven't started.
    return if @smtp.nil?
    @smtp.finish
    @smtp = nil
  end
end

begin
  Net::IMAP.debug = true
  r = Remailer.new
  loop do
    puts 'bouncing...'
    r.bounce_idle
    sleep 10*60
  end
ensure
  r.tidy
end