gmail 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +22 -0
- data/CHANGELOG.md +60 -0
- data/LICENSE +21 -0
- data/README.md +247 -0
- data/Rakefile +52 -0
- data/TODO.md +5 -0
- data/VERSION +1 -0
- data/lib/gmail.rb +47 -0
- data/lib/gmail/client.rb +221 -0
- data/lib/gmail/labels.rb +42 -0
- data/lib/gmail/mailbox.rb +92 -0
- data/lib/gmail/message.rb +137 -0
- data/lib/gmail/version.rb +12 -0
- data/spec/client_spec.rb +173 -0
- data/spec/gmail_spec.rb +38 -0
- data/spec/mailbox_spec.rb +48 -0
- data/spec/message_spec.rb +51 -0
- data/spec/spec_helper.rb +13 -0
- metadata +150 -0
data/lib/gmail/client.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
module Gmail
|
2
|
+
class Client
|
3
|
+
# Raised when connection with GMail IMAP service couldn't be established.
|
4
|
+
class ConnectionError < SocketError; end
|
5
|
+
# Raised when given username or password are invalid.
|
6
|
+
class AuthorizationError < Net::IMAP::NoResponseError; end
|
7
|
+
# Raised when delivered email is invalid.
|
8
|
+
class DeliveryError < ArgumentError; end
|
9
|
+
|
10
|
+
# GMail IMAP defaults
|
11
|
+
GMAIL_IMAP_HOST = 'imap.gmail.com'
|
12
|
+
GMAIL_IMAP_PORT = 993
|
13
|
+
|
14
|
+
# GMail SMTP defaults
|
15
|
+
GMAIL_SMTP_HOST = "smtp.gmail.com"
|
16
|
+
GMAIL_SMTP_PORT = 587
|
17
|
+
|
18
|
+
attr_reader :username
|
19
|
+
attr_reader :password
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
def initialize(username, password, options={})
|
23
|
+
defaults = {}
|
24
|
+
@username = fill_username(username)
|
25
|
+
@password = password
|
26
|
+
@options = defaults.merge(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Connect to gmail service.
|
30
|
+
def connect(raise_errors=false)
|
31
|
+
@imap = Net::IMAP.new(GMAIL_IMAP_HOST, GMAIL_IMAP_PORT, true, nil, false)
|
32
|
+
rescue SocketError
|
33
|
+
raise_errors and raise ConnectionError, "Couldn't establish connection with GMail IMAP service"
|
34
|
+
end
|
35
|
+
|
36
|
+
# This version of connect will raise error on failure...
|
37
|
+
def connect!
|
38
|
+
connect(true)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return current connection. Log in automaticaly to specified account if
|
42
|
+
# it is necessary.
|
43
|
+
def connection
|
44
|
+
login and at_exit { logout } unless logged_in?
|
45
|
+
@imap
|
46
|
+
end
|
47
|
+
alias :conn :connection
|
48
|
+
|
49
|
+
# Login to specified account.
|
50
|
+
def login(raise_errors=false)
|
51
|
+
@imap and @logged_in = (login = @imap.login(username, password)) && login.name == 'OK'
|
52
|
+
rescue Net::IMAP::NoResponseError
|
53
|
+
raise_errors and raise AuthorizationError, "Couldn't login to given GMail account: #{username}"
|
54
|
+
end
|
55
|
+
alias :sign_in :login
|
56
|
+
|
57
|
+
# This version of login will raise error on failure...
|
58
|
+
def login!
|
59
|
+
login(true)
|
60
|
+
end
|
61
|
+
alias :sign_in! :login!
|
62
|
+
|
63
|
+
# Returns +true+ when you are logged in to specified account.
|
64
|
+
def logged_in?
|
65
|
+
!!@logged_in
|
66
|
+
end
|
67
|
+
alias :signed_in? :logged_in?
|
68
|
+
|
69
|
+
# Logout from GMail service.
|
70
|
+
def logout
|
71
|
+
@imap && logged_in? and @imap.logout
|
72
|
+
ensure
|
73
|
+
@logged_in = false
|
74
|
+
end
|
75
|
+
alias :sign_out :logout
|
76
|
+
|
77
|
+
# Return labels object, which helps you with managing your GMail labels.
|
78
|
+
# See <tt>Gmail::Labels</tt> for details.
|
79
|
+
def labels
|
80
|
+
@labels ||= Labels.new(conn)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Compose new e-mail.
|
84
|
+
#
|
85
|
+
# ==== Examples
|
86
|
+
#
|
87
|
+
# mail = gmail.compose
|
88
|
+
# mail.from "test@gmail.org"
|
89
|
+
# mail.to "friend@gmail.com"
|
90
|
+
#
|
91
|
+
# ... or block style:
|
92
|
+
#
|
93
|
+
# mail = gmail.compose do
|
94
|
+
# from "test@gmail.org"
|
95
|
+
# to "friend@gmail.com"
|
96
|
+
# subject "Hello!"
|
97
|
+
# body "Hello my friend! long time..."
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# Now you can deliver your mail:
|
101
|
+
#
|
102
|
+
# gmail.deliver(mail)
|
103
|
+
def compose(mail=nil, &block)
|
104
|
+
if block_given?
|
105
|
+
mail = Mail.new(&block)
|
106
|
+
elsif !mail
|
107
|
+
mail = Mail.new
|
108
|
+
end
|
109
|
+
mail.delivery_method(*smtp_settings)
|
110
|
+
mail.from = username unless mail.from
|
111
|
+
mail
|
112
|
+
end
|
113
|
+
alias :message :compose
|
114
|
+
|
115
|
+
# Compose (optionaly) and send given email.
|
116
|
+
#
|
117
|
+
# ==== Examples
|
118
|
+
#
|
119
|
+
# gmail.deliver do
|
120
|
+
# to "friend@gmail.com"
|
121
|
+
# subject "Hello friend!"
|
122
|
+
# body "Hi! How are you?"
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# ... or with already created message:
|
126
|
+
#
|
127
|
+
# mail = Mail.new { ... }
|
128
|
+
# gmail.deliver(mail)
|
129
|
+
#
|
130
|
+
# mail = gmail.compose { ... }
|
131
|
+
# gmail.deliver(mail)
|
132
|
+
def deliver(mail=nil, raise_errors=false, &block)
|
133
|
+
mail = compose(mail, &block) if block_given?
|
134
|
+
mail.deliver!
|
135
|
+
rescue Object => ex
|
136
|
+
raise_errors and raise DeliveryError, "Couldn't deliver email: #{ex.to_s}"
|
137
|
+
end
|
138
|
+
|
139
|
+
# This version of deliver will raise error on failure...
|
140
|
+
def deliver!(mail=nil, &block)
|
141
|
+
deliver(mail, true, &block)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Do something with given mailbox or within it context.
|
145
|
+
#
|
146
|
+
# ==== Examples
|
147
|
+
#
|
148
|
+
# mailbox = gmail.mailbox("INBOX")
|
149
|
+
# mailbox.emails(:all)
|
150
|
+
# mailbox.count(:unread, :before => Time.now-(20*24*3600))
|
151
|
+
#
|
152
|
+
# ... or block style:
|
153
|
+
#
|
154
|
+
# gmail.label("Work") do |mailbox|
|
155
|
+
# mailbox.emails(:unread)
|
156
|
+
# mailbox.count(:all)
|
157
|
+
# ...
|
158
|
+
# end
|
159
|
+
def mailbox(name, &block)
|
160
|
+
name = name.to_s
|
161
|
+
mailbox = (mailboxes[name] ||= Mailbox.new(self, name))
|
162
|
+
switch_to_mailbox(name) if @current_mailbox != name
|
163
|
+
if block_given?
|
164
|
+
mailbox_stack << @current_mailbox
|
165
|
+
result = block.arity == 1 ? block.call(mailbox) : block.call
|
166
|
+
mailbox_stack.pop
|
167
|
+
switch_to_mailbox(mailbox_stack.last)
|
168
|
+
return result
|
169
|
+
end
|
170
|
+
mailbox
|
171
|
+
end
|
172
|
+
alias :in_mailbox :mailbox
|
173
|
+
alias :in_label :mailbox
|
174
|
+
alias :label :mailbox
|
175
|
+
|
176
|
+
# Alias for <tt>mailbox("INBOX")</tt>. See <tt>Gmail::Client#mailbox</tt>
|
177
|
+
# for details.
|
178
|
+
def inbox
|
179
|
+
mailbox("INBOX")
|
180
|
+
end
|
181
|
+
|
182
|
+
def mailboxes
|
183
|
+
@mailboxes ||= {}
|
184
|
+
end
|
185
|
+
|
186
|
+
def inspect
|
187
|
+
"#<Gmail::Client#{'0x%04x' % (object_id << 1)} (#{username}) #{'dis' if !logged_in?}connected>"
|
188
|
+
end
|
189
|
+
|
190
|
+
def fill_username(username)
|
191
|
+
username =~ /@/ ? username : "#{username}@gmail.com"
|
192
|
+
end
|
193
|
+
|
194
|
+
def mail_domain
|
195
|
+
username.split('@')[0]
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
def switch_to_mailbox(mailbox)
|
201
|
+
conn.select(mailbox) if mailbox
|
202
|
+
@current_mailbox = mailbox
|
203
|
+
end
|
204
|
+
|
205
|
+
def mailbox_stack
|
206
|
+
@mailbox_stack ||= []
|
207
|
+
end
|
208
|
+
|
209
|
+
def smtp_settings
|
210
|
+
[:smtp, {
|
211
|
+
:address => GMAIL_SMTP_HOST,
|
212
|
+
:port => GMAIL_SMTP_PORT,
|
213
|
+
:domain => mail_domain,
|
214
|
+
:user_name => username,
|
215
|
+
:password => password,
|
216
|
+
:authentication => 'plain',
|
217
|
+
:enable_starttls_auto => true
|
218
|
+
}]
|
219
|
+
end
|
220
|
+
end # Client
|
221
|
+
end # Gmail
|
data/lib/gmail/labels.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Gmail
|
2
|
+
class Labels
|
3
|
+
attr_reader :connection
|
4
|
+
alias :conn :connection
|
5
|
+
|
6
|
+
def initialize(connection)
|
7
|
+
@connection = connection
|
8
|
+
end
|
9
|
+
|
10
|
+
# Get list of all defined labels.
|
11
|
+
def all
|
12
|
+
(conn.list("", "%")+conn.list("[Gmail]/", "%")).inject([]) do |labels,label|
|
13
|
+
label[:name].each_line {|l| labels << l }
|
14
|
+
labels
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias :list :all
|
18
|
+
|
19
|
+
# Returns +true+ when given label defined.
|
20
|
+
def exists?(label)
|
21
|
+
all.include?(label)
|
22
|
+
end
|
23
|
+
alias :exist? :exists?
|
24
|
+
|
25
|
+
# Creates given label in your account.
|
26
|
+
def create(label)
|
27
|
+
!!conn.create(label) rescue false
|
28
|
+
end
|
29
|
+
alias :new :create
|
30
|
+
alias :add :create
|
31
|
+
|
32
|
+
# Deletes given label from your account.
|
33
|
+
def delete(label)
|
34
|
+
!!conn.delete(label) rescue false
|
35
|
+
end
|
36
|
+
alias :remove :delete
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
"#<Gmail::Labels#{'0x%04x' % (object_id << 1)}>"
|
40
|
+
end
|
41
|
+
end # Labels
|
42
|
+
end # Gmail
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Gmail
|
2
|
+
class Mailbox
|
3
|
+
MAILBOX_ALIASES = {
|
4
|
+
:all => ['ALL'],
|
5
|
+
:unread => ['UNSEEN'],
|
6
|
+
:read => ['SEEN']
|
7
|
+
}
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(gmail, name="INBOX")
|
12
|
+
@name = name
|
13
|
+
@gmail = gmail
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns list of emails which meets given criteria.
|
17
|
+
#
|
18
|
+
# ==== Examples
|
19
|
+
#
|
20
|
+
# gmail.inbox.emails(:all)
|
21
|
+
# gmail.inbox.emails(:unread, :from => "friend@gmail.com")
|
22
|
+
# gmail.inbox.emails(:all, :after => Time.now-(20*24*3600))
|
23
|
+
# gmail.mailbox("Test").emails(:read)
|
24
|
+
#
|
25
|
+
# gmail.mailbox("Test") do |box|
|
26
|
+
# box.emails(:read)
|
27
|
+
# box.emails(:unread) do |email|
|
28
|
+
# ... do something with each email...
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
def emails(*args, &block)
|
32
|
+
args << :all if args.size == 0
|
33
|
+
|
34
|
+
if args.first.is_a?(Symbol)
|
35
|
+
search = MAILBOX_ALIASES[args.shift]
|
36
|
+
opts = args.first.is_a?(Hash) ? args.first : {}
|
37
|
+
|
38
|
+
opts[:after] and search.concat ['SINCE', opts[:after].to_imap_date]
|
39
|
+
opts[:before] and search.concat ['BEFORE', opts[:before].to_imap_date]
|
40
|
+
opts[:on] and search.concat ['ON', opts[:on].to_imap_date]
|
41
|
+
opts[:from] and search.concat ['FROM', opts[:from]]
|
42
|
+
opts[:to] and search.concat ['TO', opts[:to]]
|
43
|
+
opts[:subject] and search.concat ['SUBJECT', opts[:subject]]
|
44
|
+
opts[:label] and search.concat ['LABEL', opts[:label]]
|
45
|
+
opts[:attachment] and search.concat ['HAS', 'attachment']
|
46
|
+
opts[:search] and search.concat [opts[:search]]
|
47
|
+
|
48
|
+
@gmail.mailbox(name) do
|
49
|
+
@gmail.conn.uid_search(search).collect do |uid|
|
50
|
+
message = (messages[uid] ||= Message.new(self, uid))
|
51
|
+
block.call(message) if block_given?
|
52
|
+
message
|
53
|
+
end
|
54
|
+
end
|
55
|
+
elsif args.first.is_a?(Hash)
|
56
|
+
emails(:all, args.first)
|
57
|
+
else
|
58
|
+
raise ArgumentError, "Invalid search criteria"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
alias :mails :emails
|
62
|
+
alias :search :emails
|
63
|
+
alias :find :emails
|
64
|
+
alias :filter :emails
|
65
|
+
|
66
|
+
# This is a convenience method that really probably shouldn't need to exist,
|
67
|
+
# but it does make code more readable, if seriously all you want is the count
|
68
|
+
# of messages.
|
69
|
+
#
|
70
|
+
# ==== Examples
|
71
|
+
#
|
72
|
+
# gmail.inbox.count(:all)
|
73
|
+
# gmail.inbox.count(:unread, :from => "friend@gmail.com")
|
74
|
+
# gmail.mailbox("Test").count(:all, :after => Time.now-(20*24*3600))
|
75
|
+
def count(*args)
|
76
|
+
emails(*args).size
|
77
|
+
end
|
78
|
+
|
79
|
+
# Cached messages.
|
80
|
+
def messages
|
81
|
+
@messages ||= {}
|
82
|
+
end
|
83
|
+
|
84
|
+
def inspect
|
85
|
+
"#<Gmail::Mailbox#{'0x%04x' % (object_id << 1)} name=#{@name}>"
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_s
|
89
|
+
name
|
90
|
+
end
|
91
|
+
end # Message
|
92
|
+
end # Gmail
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'mime/message'
|
2
|
+
|
3
|
+
module Gmail
|
4
|
+
class Message
|
5
|
+
# Raised when given label doesn't exists.
|
6
|
+
class NoLabelError < Exception; end
|
7
|
+
|
8
|
+
attr_reader :uid
|
9
|
+
|
10
|
+
def initialize(mailbox, uid)
|
11
|
+
@uid = uid
|
12
|
+
@mailbox = mailbox
|
13
|
+
@gmail = mailbox.instance_variable_get("@gmail") if mailbox
|
14
|
+
end
|
15
|
+
|
16
|
+
def uid
|
17
|
+
@uid ||= @gmail.conn.uid_search(['HEADER', 'Message-ID', message_id])[0]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Mark message with given flag.
|
21
|
+
def flag(name)
|
22
|
+
!!@gmail.mailbox(@mailbox.name) { @gmail.conn.uid_store(uid, "+FLAGS", [name]) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Unmark message.
|
26
|
+
def unflag(name)
|
27
|
+
!!@gmail.mailbox(@mailbox.name) { @gmail.conn.uid_store(uid, "-FLAGS", [name]) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Do commonly used operations on message.
|
31
|
+
def mark(flag)
|
32
|
+
case flag
|
33
|
+
when :read then read!
|
34
|
+
when :unread then unread!
|
35
|
+
when :deleted then delete!
|
36
|
+
when :spam then spam!
|
37
|
+
else
|
38
|
+
flag(flag)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Mark this message as a spam.
|
43
|
+
def spam!
|
44
|
+
move_to('[Gmail]/Spam')
|
45
|
+
end
|
46
|
+
|
47
|
+
# Mark as read.
|
48
|
+
def read!
|
49
|
+
flag(:Seen)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Mark as unread.
|
53
|
+
def unread!
|
54
|
+
unflag(:Seen)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Mark message with star.
|
58
|
+
def star!
|
59
|
+
flag('[Gmail]/Starred')
|
60
|
+
end
|
61
|
+
|
62
|
+
# Remove message from list of starred.
|
63
|
+
def unstar!
|
64
|
+
unflag('[Gmail]/Starred')
|
65
|
+
end
|
66
|
+
|
67
|
+
# Move to trash.
|
68
|
+
def delete!
|
69
|
+
@mailbox.messages.delete(uid)
|
70
|
+
flag(:Deleted)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Archive this message.
|
74
|
+
def archive!
|
75
|
+
move_to('[Gmail]/All Mail')
|
76
|
+
end
|
77
|
+
|
78
|
+
# Move to given box and delete from others.
|
79
|
+
def move_to(name, from=nil)
|
80
|
+
label(name, from) && delete!
|
81
|
+
end
|
82
|
+
alias :move :move_to
|
83
|
+
|
84
|
+
# Move message to given and delete from others. When given mailbox doesn't
|
85
|
+
# exist then it will be automaticaly created.
|
86
|
+
def move_to!(name, from=nil)
|
87
|
+
label!(name, from) && delete!
|
88
|
+
end
|
89
|
+
alias :move! :move_to!
|
90
|
+
|
91
|
+
# Mark this message with given label. When given label doesn't exist then
|
92
|
+
# it will raise <tt>NoLabelError</tt>.
|
93
|
+
#
|
94
|
+
# See also <tt>Gmail::Message#label!</tt>.
|
95
|
+
def label(name, from=nil)
|
96
|
+
@gmail.mailbox(from || @mailbox.name) { @gmail.conn.uid_copy(uid, name) }
|
97
|
+
rescue Net::IMAP::NoResponseError
|
98
|
+
raise NoLabelError, "Label '#{name}' doesn't exist!"
|
99
|
+
end
|
100
|
+
alias :add_label :label
|
101
|
+
|
102
|
+
# Mark this message with given label. When given label doesn't exist then
|
103
|
+
# it will be automaticaly created.
|
104
|
+
#
|
105
|
+
# See also <tt>Gmail::Message#label</tt>.
|
106
|
+
def label!(name, from=nil)
|
107
|
+
label(name, from)
|
108
|
+
rescue NoLabelError
|
109
|
+
@gmail.labels.add(name)
|
110
|
+
label!(name, from)
|
111
|
+
end
|
112
|
+
alias :add_label! :add_label
|
113
|
+
|
114
|
+
# Remove given label from this message.
|
115
|
+
def remove_label!(name)
|
116
|
+
move_to('[Gmail]/All Mail', name)
|
117
|
+
end
|
118
|
+
alias :delete_label! :remove_label!
|
119
|
+
|
120
|
+
def inspect
|
121
|
+
"#<Gmail::Message#{'0x%04x' % (object_id << 1)} mailbox=#{@mailbox.name}#{' uid='+@uid.to_s if @uid}#{' message_id='+@message_id.to_s if @message_id}>"
|
122
|
+
end
|
123
|
+
|
124
|
+
def method_missing(meth, *args, &block)
|
125
|
+
# Delegate rest directly to the message.
|
126
|
+
message.send(meth, *args, &block)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def message
|
132
|
+
@message ||= Mail.new(@gmail.mailbox(@mailbox.name) {
|
133
|
+
@gmail.conn.uid_fetch(uid, "RFC822")[0].attr["RFC822"]
|
134
|
+
})
|
135
|
+
end
|
136
|
+
end # Message
|
137
|
+
end # Gmail
|