Skip to content
Keith Schacht edited this page Dec 30, 2017 · 8 revisions

Here is an example of working code which may help someone else.

System:

  • Ruby 2.3.5
  • On a Mac with Chrome installed, it will launch Chrome to authenticate

Notables:

  • The oauth2 code is adapted from Google's example for the Google API, the SCOPE needed to be changed to authenticate for IMAP. Every 60 minutes you have to re-authenticate, I could not figure out how to fix that
  • I didn't want email messages I wanted conversations so I wrote a couple classes that fake conversations
  • The reason I use gmail.mailbox(...) rather than gmail.inbox. is because it's required in order for .archive! to work. If you're in the inbox view you can't remove a message from the inbox view.
require 'google/apis/gmail_v1'
require 'googleauth'
require 'googleauth/stores/file_token_store'
require 'gmail'

require 'fileutils'
require 'forwardable'
require 'set'

OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'
APPLICATION_NAME = 'Taskmaster'
CLIENT_SECRETS_PATH = 'client_secret.json'
CREDENTIALS_PATH = File.join(Dir.home, '.credentials',
                             "taskmaster.yaml")
SCOPE = "https://mail.google.com/" # gmail API: Google::Apis::GmailV1::AUTH_GMAIL_MODIFY

class GmailAuthorize

  def token; @service.authorization.access_token; end

  def initialize
    # Initialize the API
    @service = Google::Apis::GmailV1::GmailService.new
    @service.client_options.application_name = APPLICATION_NAME
    @service.authorization = authorize

    @service.authorization.access_token
  end

  ##
  # Ensure valid credentials, either by restoring from the saved credentials
  # files or intitiating an OAuth2 authorization. If authorization is required,
  # the user's default browser will be launched to approve the request.
  #
  # @return [Google::Auth::UserRefreshCredentials] OAuth2 credentials
  def authorize
    FileUtils.mkdir_p(File.dirname(CREDENTIALS_PATH))

    client_id = Google::Auth::ClientId.from_file(CLIENT_SECRETS_PATH)
    token_store = Google::Auth::Stores::FileTokenStore.new(file: CREDENTIALS_PATH)
    authorizer = Google::Auth::UserAuthorizer.new(
      client_id, SCOPE, token_store)
    user_id = 'default'
    credentials = authorizer.get_credentials(user_id)
    if credentials.nil? || credentials.expires_at < Time.now
      url = authorizer.get_authorization_url(
        base_url: OOB_URI)
      puts "Open the following URL in the browser and enter the " +
           "resulting code after authorization"
      puts url
      system("/usr/bin/open -a '/Applications/Google Chrome.app' '#{url}'")
      print "paste response: "
      code = gets
      credentials = authorizer.get_and_store_credentials_from_code(
        user_id: user_id, code: code, base_url: OOB_URI)
    end
    credentials
  end
end

class Conversations
  include Enumerable
  extend Forwardable

  def_delegators :to_a, :each, :<<, :[], :collect, :select, :first, :last, :count,
    :length; :reverse

  def to_a; @conversations.values; end

  def initialize(emails)
    @emails = emails
    @conversations = {}

    emails.reverse.each_with_index do |email, i|
      print "#{i+1}.."
      if @conversations.has_key?(email.thr_id)
        @conversations[email.thr_id].add_email(email)
      else
        @conversations[email.thr_id] = Conversation.new(email)
      end
    end
  end

  def inspect
    "#<Conversations count=#{count}>"
  end
end

class Conversation

  def emails; @emails.values; end
  def subject; @subject; end
  def full_name; @full_name; end
  def labels; @labels; end
  def label; @labels.first; end
  def id; @id; end
  def permalink; "https://mail.google.com/mail/#all/#{@id.to_i.to_s(16)}"; end
  def length; @emails.keys.length; end

  def initialize(email)
    @emails = {}
    @subject = email.subject
    @full_name = email.from.first.name
    @id = email.thr_id
    @labels = Set.new()

    add_email(email)
  end

  def add_email(email)
    @emails[email.msg_id] = email
    email.labels.each { |l| @labels << l if l.is_a?(String) }
    email
  end

  def archive!
    emails.each { |email| email.archive! }
  end

  def star!
    emails.first.star!
  end

  def inspect
    "#<Conversation id=#{id} length=#{length} subject=#{subject}>"
  end
end

puts "Checking inbox..."
gmail = Gmail.connect(:xoauth2, "[email protected]", GmailAuthorize.new.token)
puts gmail.mailbox('[Gmail]/All Mail').emails(gm: 'in:inbox').count
conversations = Conversations.new(gmail.mailbox('[Gmail]/All Mail').emails(gm: 'in:inbox'))
Clone this wiki locally