Skip to content

Commit

Permalink
Implemented features from Justin Perkins (creating labels) and Mikkel…
Browse files Browse the repository at this point in the history
… Malmberg (email search options).
  • Loading branch information
dcparker committed Dec 3, 2009
1 parent be6b5a4 commit b0d558e
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 46 deletions.
12 changes: 9 additions & 3 deletions History.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
=== 0.0.4 / 2009-11-30

* 1 minor enhancement
* 4 minor enhancement

* Added label creation
* Added label creation (thanks to Justin Perkins / http://github.com/justinperkins)
* Made the gem login automatically when first needed
* Added an optional block on the Gmail.new object that will login and logout for you.
* Added several search options (thanks to Mikkel Malmberg / http://github.com/mikker)

=== 0.0.3 / 2009-11-19

* 1 minor enhancement
* 1 bugfix

* Fixed MIME::Message#content= for messages without an encoding

* 1 minor enhancement

* Added Gmail#new_message

=== 0.0.2 / 2009-11-18
Expand Down
46 changes: 33 additions & 13 deletions README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

* Homepage: http://dcparker.github.com/ruby-gmail/
* Code: http://github.com/dcparker/ruby-gmail
* Gem: http://gemcutter.org/gems/ruby-gmail

== Author(s):

Daniel Parker of BehindLogic.com
Extra thanks for specific feature contributions from:

* Justin Perkins (http://github.com/justinperkins)
* Mikkel Malmberg (http://github.com/mikker)


== DESCRIPTION:

Expand All @@ -24,19 +34,29 @@ A Rubyesque interface to Gmail. Connect to Gmail via IMAP and manipulate emails
== SYNOPSIS:

require 'gmail'
gmail = Gmail.new(username, password)
gmail.inbox.count # => {:read => 41, :unread => 2}
unread = gmail.inbox.emails(:unread)
unread[0].archive!
unread[1].delete!
unread[2].move_to('FunStuff') # => Labels 'FunStuff' and removes from inbox
unread[3].message # => a MIME::Message, parsed from the email body
unread[3].mark(:read)
unread[3].message.attachments.length
unread[4].label('FunStuff') # => Just adds the label 'FunStuff'
unread[4].message.save_attachments_to('path/to/save/into')
unread[5].message.attachments[0].save_to_file('path/to/save/into')
unread[6].mark(:spam)
gmail = Gmail.new(username, password) do |g|
gmail.inbox.count # takes the same arguments as gmail.inbox.emails
unread = gmail.inbox.emails(:unread)
unread[0].archive!
unread[1].delete!
unread[2].move_to('FunStuff') # => Labels 'FunStuff' and removes from inbox
unread[3].message # => a MIME::Message, parsed from the email body
unread[3].mark(:read)
unread[3].message.attachments.length
unread[4].label('FunStuff') # => Just adds the label 'FunStuff'
unread[4].message.save_attachments_to('path/to/save/into')
unread[5].message.attachments[0].save_to_file('path/to/save/into')
unread[6].mark(:spam)
end

# Optionally use a block like above to have the gem automatically login and logout,
# or just use it without a block after creating the object like below, and it will
# automatically logout at_exit. The block method is recommended, to limit your signed-in session.

older = gmail.inbox.emails(:after => '2009-03-04', :before => '2009-03-15')
todays_date = Time.parse(Time.now.strftime('%Y-%m-%d'))
yesterday = gmail.inbox.emails(:after => (todays_date - 24*60*60), :before => todays_date)
todays_unread = gmail.inbox.emails(:unread, :after => todays_date)

new_email = MIME::Message.generate
new_email.to "email@example.com"
Expand Down
25 changes: 18 additions & 7 deletions lib/gmail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ class Gmail

class NoLabel < RuntimeError; end

attr_reader :imap

def initialize(username, password)
# This is to hide the username and password, not like it REALLY needs hiding, but ... you know.
# Could be helpful when demoing the gem in irb, these bits won't show up that way.
meta = class << self
class << self
attr_accessor :username, :password
Expand All @@ -20,8 +19,11 @@ class << self
meta.username = username =~ /@/ ? username : username + '@gmail.com'
meta.password = password
@imap = Net::IMAP.new('imap.gmail.com',993,true)
@connected = true if @imap.login(username, password)
at_exit { logout if @connected }
if block_given?
@connected = true if @imap.login(username, password)
yield self
logout
end
end

# Accessors for IMAP things
Expand All @@ -33,27 +35,36 @@ def mailbox(name)
def inbox
mailbox('inbox')
end
# Accessor for @imap, but ensures that it's logged in first.
def imap
if !@connected
meta = class << self; self end
@connected = true if @imap.login(meta.username, meta.password)
at_exit { logout if @connected } # Set up auto-logout for later.
end
@imap
end
# Log out of gmail
def logout
@connected = false if @imap.logout
end

def create_label(name)
@imap.create(name)
imap.create(name)
end

def in_mailbox(mailbox, &block)
raise ArgumentError, "Must provide a code block" unless block_given?
mailbox_stack << mailbox
unless @selected == mailbox.name
@imap.select(mailbox.name)
imap.select(mailbox.name)
@selected = mailbox.name
end
value = block.arity == 1 ? block.call(mailbox) : block.call
mailbox_stack.pop
# Select previously selected mailbox if there is one
if mailbox_stack.last
@imap.select(mailbox_stack.last.name)
imap.select(mailbox_stack.last.name)
@selected = mailbox.name
end
return value
Expand Down
62 changes: 39 additions & 23 deletions lib/gmail/mailbox.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
require 'date'
require 'time'
class Object
def to_imap_date
Date.parse(to_s).strftime("%d-%B-%Y")
end
end

class Gmail
class Mailbox
attr_reader :name
Expand All @@ -18,38 +26,46 @@ def to_s
# Method: emails
# Args: [ :all | :unread | :read ]
# Opts: {:since => Date.new}
def emails(key = :all, opts = {})
aliases = {
:all => ['ALL'],
:unread => ['UNSEEN'],
:read => ['SEEN']
}
search = aliases[key] || key

# Support other search options
# :before => Date, :on => Date, :since => Date,
# :from => String, :to => String
search += ['BEFORE', date_to_string(opts[:before])] if opts[:before]
search += ['ON', date_to_string(opts[:on])] if opts[:on]
search += ['SINCE', date_to_string(opts[:since])] if opts[:since]
search += ['FROM', opts[:from]] if opts[:from]
search += ['TO', opts[:to]] if opts[:to]
def emails(key_or_opts = :all, opts={})
if key_or_opts.is_a?(Hash) && opts.empty?
search = ['ALL']
opts = key_or_opts
elsif key_or_opts.is_a?(Symbol) && opts.is_a?(Hash)
aliases = {
:all => ['ALL'],
:unread => ['UNSEEN'],
:read => ['SEEN']
}
search = aliases[key_or_opts]
elsif key_or_opts.is_a?(Array) && opts.empty?
search = key_or_opts
else
raise ArgumentError, "Couldn't make sense of arguments to #emails - should be an optional hash of options preceded by an optional read-status bit; OR simply an array of parameters to pass directly to the IMAP uid_search call."
end
if !opts.empty?
# Support for several search macros
# :before => Date, :on => Date, :since => Date, :from => String, :to => String
search.concat ['SINCE', opts[:after].to_imap_date] if opts[:after]
search.concat ['BEFORE', opts[:before].to_imap_date] if opts[:before]
search.concat ['ON', opts[:on].to_imap_date] if opts[:on]
search.concat ['FROM', opts[:from]] if opts[:from]
search.concat ['TO', opts[:to]] if opts[:to]
end

# puts "Gathering #{(aliases[key] || key).inspect} messages for mailbox '#{name}'..."
@gmail.in_mailbox(self) do
@gmail.imap.uid_search(search).collect { |uid| messages[uid] ||= Message.new(@gmail, self, uid) }
end
end

def messages
@messages ||= {}
# This is a convenience method that really probably shouldn't need to exist, but it does make code more readable
# if seriously all you want is the count of messages.
def count(*args)
emails(*args).length
end

private

# Converts the given object to a IMAP date string if we can
def date_to_string(date_or_string)
date_or_string.respond_to?(:strftime) ? date_or_string.strftime("%d-%B-%Y") : date_or_string
def messages
@messages ||= {}
end
end
end

0 comments on commit b0d558e

Please sign in to comment.