require "vmail/imap_client" module Vmail class InboxPoller < ImapClient # This is a second IMAP client operating in a separate process def start_polling n = [`which notify-send`.chomp, `which growlnotify`.chomp].detect {|c| c != ''} if n log "Using notify tool: #{n}" @notifier = case n when /notify-send/ Proc.new {|t, m| `#{n} -t 6000000 '#{t}' '#{m}'` } when /growlnotify/ Proc.new {|t, m| `#{n} -t '#{t}' -m '#{m}'` } end else log "No notification tool detected. INBOX polling aborted." return end sleep 30 log "INBOX POLLER: started polling" @mailboxes.unshift "INBOX" select_mailbox "INBOX" search "ALL" loop do log "INBOX POLLER: checking inbox" update sleep 30 end end def update new_ids = check_for_new_messages if !new_ids.empty? @ids = @ids + new_ids notification_title = "Vmail: " notification_body = "" if new_ids.size == 1 # If there is a single email, message is more descriptive about the # received email. # # Example: # # (title) Vmail: Colin Sullivan # (body) New Pull request received! # log "single new message received!" new_message = uncached_headers(new_ids)[0] # Extract just sender's name from sender field notification_title += new_message[:sender].gsub(/\<.*\>/, "").strip() # Truncate message subject if necessary if new_message[:subject].length > 128 # Extract first 128 characters from subject of email notification_body += new_message[:subject][0..128] + "..." else notification_body += new_message[:subject] end else # If there are multiple new messages, notification is just a brief # listing. # # Example: # # (title) Vmail: 3 new messages # (body) Colin Sullivan: New Pull request rec... # Henry Cowell: I am back from the dead! # ... # log "multiple new messages received!" notification_title += new_ids.size.to_s() + " new messages" # for each message for i in 0...new_ids.size new_message = uncached_headers(new_ids)[i] # Create message line message_line = "" # Extract just sender's name from sender field message_line += new_message[:sender].gsub(/\<.*\>/, "").strip() # Extract subject message_line += ": " + new_message[:subject] # Concatenate line if necessary if message_line.length > 32 message_line = message_line[0...29] + "..." end # Add to notification body notification_body += message_line + "\n" end # Concatenate entire notification body if necessary if notification_body.length > 128 notification_body = notification_body[0...125] + "..." end end # Remove any '<>' characters from notification just incase, libnotify # can't print '<' notification_title = notification_title.tr('<>', '') notification_body = notification_body.tr('<>', '') @notifier.call notification_title, notification_body end rescue log "VMAIL_ERROR: #{[$!.message, $!.backtrace].join("\n")}" end # doesn't try to access Sequel / sqlite3 def uncached_headers(id_set) log "Fetching headers for #{id_set.size} messages" results = reconnect_if_necessary do @imap.fetch(id_set, ["FLAGS", "ENVELOPE", "RFC822.SIZE", "UID"]) end results.reverse.map do |x| envelope = x.attr["ENVELOPE"] message_id = envelope.message_id subject = Mail::Encodings.unquote_and_convert_to((envelope.subject || ''), 'UTF-8') recipients = ((envelope.to || []) + (envelope.cc || [])).map {|a| extract_address(a)}.join(', ') sender = extract_address envelope.from.first uid = x.attr["UID"] params = { subject: (subject || ''), flags: x.attr['FLAGS'].join(','), date: Time.parse(envelope.date).localtime.to_s, size: x.attr['RFC822.SIZE'], sender: sender, recipients: recipients } end end def log(string) if string.is_a?(::Net::IMAP::TaggedResponse) string = string.raw_data end @logger.debug "[INBOX POLLER]: #{string}" end end end