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

Here is an example of working code. I spent way too many hours just trying to get a list of conversations from Gmail that I could manipulate so hopefully this helps 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 ruby example for the Gmail API. Follow their guide first to get your app created and a local YML key. Once their code works you'll see the code below is almost identical, notably the SCOPE needed to be changed to authenticate for IMAP. Oh, and every 60 minutes you have to re-authenticate with IMAP, 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, "xxxxx@gmail.com", 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