gmail 0.3.4 → 0.4.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/CHANGELOG.md +6 -0
- data/README.md +3 -0
- data/TODO.md +2 -1
- data/gmail.gemspec +5 -4
- data/lib/gmail.rb +46 -11
- data/lib/gmail/client.rb +9 -208
- data/lib/gmail/client/base.rb +220 -0
- data/lib/gmail/client/plain.rb +18 -0
- data/lib/gmail/client/xoauth.rb +49 -0
- data/lib/gmail/labels.rb +4 -4
- data/lib/gmail/mailbox.rb +7 -3
- data/lib/gmail/message.rb +27 -13
- data/lib/gmail/version.rb +2 -2
- data/spec/client_spec.rb +6 -6
- data/spec/gmail_spec.rb +2 -2
- data/spec/mailbox_spec.rb +11 -0
- metadata +33 -7
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -14,6 +14,9 @@ API, is well tested, better documented and have many other improvements.
|
|
14
14
|
|
15
15
|
Extra thanks for specific feature contributions from:
|
16
16
|
|
17
|
+
* [Nicolas Fouché](http://github.com/nfo)
|
18
|
+
* [Stefano Bernardi](http://github.com/stefanobernardi)
|
19
|
+
* [Benjamin Bock](http://github.com/bb)
|
17
20
|
* [Arthur Chiu](http://github.com/achiu)
|
18
21
|
* [Justin Perkins](http://github.com/justinperkins)
|
19
22
|
* [Mikkel Malmberg](http://github.com/mikker)
|
data/TODO.md
CHANGED
data/gmail.gemspec
CHANGED
@@ -15,8 +15,9 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.require_paths = %w[lib]
|
16
16
|
s.extra_rdoc_files = %w[LICENSE README.md CHANGELOG.md TODO.md]
|
17
17
|
|
18
|
-
s.add_runtime_dependency 'mime',
|
19
|
-
s.add_runtime_dependency 'mail',
|
20
|
-
s.
|
21
|
-
s.add_development_dependency '
|
18
|
+
s.add_runtime_dependency 'mime', '>= 0.1'
|
19
|
+
s.add_runtime_dependency 'mail', '>= 2.2.1'
|
20
|
+
s.add_runtime_dependency 'gmail_xoauth', '>= 0.3.0'
|
21
|
+
s.add_development_dependency 'rspec', '~> 2.0'
|
22
|
+
s.add_development_dependency 'mocha', '>= 0.9'
|
22
23
|
end
|
data/lib/gmail.rb
CHANGED
@@ -22,26 +22,61 @@ module Gmail
|
|
22
22
|
autoload :Message, "gmail/message"
|
23
23
|
|
24
24
|
class << self
|
25
|
-
|
26
|
-
|
25
|
+
|
26
|
+
# Creates new Gmail connection using given authorization options.
|
27
|
+
#
|
28
|
+
# ==== Examples
|
29
|
+
#
|
30
|
+
# Gmail.new(:plain, "foo@gmail.com", "password")
|
31
|
+
# Gmail.new(:xoauth, "foo@gmail.com",
|
32
|
+
# :consumer_key => "",
|
33
|
+
# :consumer_secret => "",
|
34
|
+
# :token => "",
|
35
|
+
# :secret => "")
|
36
|
+
#
|
37
|
+
# To use plain authentication mehod you can also call:
|
38
|
+
#
|
39
|
+
# Gmail.new("foo@gmail.com", "password")
|
40
|
+
#
|
41
|
+
# You can also use block-style call:
|
42
|
+
#
|
43
|
+
# Gmail.new("foo@gmail.com", "password") do |client|
|
44
|
+
# # ...
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
def new(*args, &block)
|
48
|
+
client = connect_with_proper_client(*args)
|
27
49
|
client.connect and client.login
|
28
|
-
|
29
|
-
yield client
|
30
|
-
client.logout
|
31
|
-
end
|
32
|
-
client
|
50
|
+
perform_block(client, &block)
|
33
51
|
end
|
34
52
|
alias :connect :new
|
35
|
-
|
36
|
-
def new!(
|
37
|
-
client =
|
53
|
+
|
54
|
+
def new!(*args, &block)
|
55
|
+
client = connect_with_proper_client(*args)
|
38
56
|
client.connect! and client.login!
|
57
|
+
perform_block(client, &block)
|
58
|
+
end
|
59
|
+
alias :connect! :new!
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def connect_with_proper_client(*args)
|
64
|
+
if args.first.is_a?(Symbol)
|
65
|
+
login_method = args.shift
|
66
|
+
else
|
67
|
+
login_method ||= :plain
|
68
|
+
end
|
69
|
+
|
70
|
+
Client.send("new_#{login_method}", *args)
|
71
|
+
end
|
72
|
+
|
73
|
+
def perform_block(client, &block)
|
39
74
|
if block_given?
|
40
75
|
yield client
|
41
76
|
client.logout
|
42
77
|
end
|
43
78
|
client
|
44
79
|
end
|
45
|
-
|
80
|
+
|
46
81
|
end # << self
|
47
82
|
end # Gmail
|
data/lib/gmail/client.rb
CHANGED
@@ -1,221 +1,22 @@
|
|
1
1
|
module Gmail
|
2
|
-
|
2
|
+
module Client
|
3
3
|
# Raised when connection with GMail IMAP service couldn't be established.
|
4
4
|
class ConnectionError < SocketError; end
|
5
5
|
# Raised when given username or password are invalid.
|
6
6
|
class AuthorizationError < Net::IMAP::NoResponseError; end
|
7
7
|
# Raised when delivered email is invalid.
|
8
8
|
class DeliveryError < ArgumentError; end
|
9
|
-
|
10
|
-
# GMail IMAP defaults
|
11
|
-
GMAIL_IMAP_HOST = 'imap.gmail.com'
|
12
|
-
GMAIL_IMAP_PORT = 993
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
10
|
+
autoload :Base, 'gmail/client/base'
|
11
|
+
autoload :Plain, 'gmail/client/plain'
|
12
|
+
autoload :XOAuth, 'gmail/client/xoauth'
|
193
13
|
|
194
|
-
def
|
195
|
-
|
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 ||= []
|
14
|
+
def self.new_plain(*args)
|
15
|
+
Gmail::Client::Plain.new(*args)
|
207
16
|
end
|
208
|
-
|
209
|
-
def
|
210
|
-
|
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
|
-
}]
|
17
|
+
|
18
|
+
def self.new_xoauth(*args)
|
19
|
+
Gmail::Client::XOAuth.new(*args)
|
219
20
|
end
|
220
21
|
end # Client
|
221
22
|
end # Gmail
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Gmail
|
4
|
+
module Client
|
5
|
+
class Base
|
6
|
+
# GMail IMAP defaults
|
7
|
+
GMAIL_IMAP_HOST = 'imap.gmail.com'
|
8
|
+
GMAIL_IMAP_PORT = 993
|
9
|
+
|
10
|
+
# GMail SMTP defaults
|
11
|
+
GMAIL_SMTP_HOST = "smtp.gmail.com"
|
12
|
+
GMAIL_SMTP_PORT = 587
|
13
|
+
|
14
|
+
attr_reader :username
|
15
|
+
attr_reader :options
|
16
|
+
|
17
|
+
def initialize(username, options={})
|
18
|
+
defaults = {}
|
19
|
+
@username = fill_username(username)
|
20
|
+
@options = defaults.merge(options)
|
21
|
+
@mailbox_mutex = Mutex.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Connect to gmail service.
|
25
|
+
def connect(raise_errors=false)
|
26
|
+
@imap = Net::IMAP.new(GMAIL_IMAP_HOST, GMAIL_IMAP_PORT, true, nil, false)
|
27
|
+
rescue SocketError
|
28
|
+
raise_errors and raise ConnectionError, "Couldn't establish connection with GMail IMAP service"
|
29
|
+
end
|
30
|
+
|
31
|
+
# This version of connect will raise error on failure...
|
32
|
+
def connect!
|
33
|
+
connect(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return current connection. Log in automaticaly to specified account if
|
37
|
+
# it is necessary.
|
38
|
+
def connection
|
39
|
+
login and at_exit { logout } unless logged_in?
|
40
|
+
@imap
|
41
|
+
end
|
42
|
+
alias :conn :connection
|
43
|
+
|
44
|
+
# Login to specified account.
|
45
|
+
def login(*args)
|
46
|
+
raise NotImplementedError, "The `#{self.class.name}#login` method is not implemented."
|
47
|
+
end
|
48
|
+
alias :sign_in :login
|
49
|
+
|
50
|
+
# This version of login will raise error on failure...
|
51
|
+
def login!
|
52
|
+
login(true)
|
53
|
+
end
|
54
|
+
alias :sign_in! :login!
|
55
|
+
|
56
|
+
# Returns +true+ when you are logged in to specified account.
|
57
|
+
def logged_in?
|
58
|
+
!!@logged_in
|
59
|
+
end
|
60
|
+
alias :signed_in? :logged_in?
|
61
|
+
|
62
|
+
# Logout from GMail service.
|
63
|
+
def logout
|
64
|
+
@imap && logged_in? and @imap.logout
|
65
|
+
ensure
|
66
|
+
@logged_in = false
|
67
|
+
end
|
68
|
+
alias :sign_out :logout
|
69
|
+
|
70
|
+
# Return labels object, which helps you with managing your GMail labels.
|
71
|
+
# See <tt>Gmail::Labels</tt> for details.
|
72
|
+
def labels
|
73
|
+
@labels ||= Labels.new(conn)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Compose new e-mail.
|
77
|
+
#
|
78
|
+
# ==== Examples
|
79
|
+
#
|
80
|
+
# mail = gmail.compose
|
81
|
+
# mail.from "test@gmail.org"
|
82
|
+
# mail.to "friend@gmail.com"
|
83
|
+
#
|
84
|
+
# ... or block style:
|
85
|
+
#
|
86
|
+
# mail = gmail.compose do
|
87
|
+
# from "test@gmail.org"
|
88
|
+
# to "friend@gmail.com"
|
89
|
+
# subject "Hello!"
|
90
|
+
# body "Hello my friend! long time..."
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# Now you can deliver your mail:
|
94
|
+
#
|
95
|
+
# gmail.deliver(mail)
|
96
|
+
def compose(mail=nil, &block)
|
97
|
+
if block_given?
|
98
|
+
mail = Mail.new(&block)
|
99
|
+
elsif !mail
|
100
|
+
mail = Mail.new
|
101
|
+
end
|
102
|
+
|
103
|
+
mail.delivery_method(*smtp_settings)
|
104
|
+
mail.from = username unless mail.from
|
105
|
+
mail
|
106
|
+
end
|
107
|
+
alias :message :compose
|
108
|
+
|
109
|
+
# Compose (optionaly) and send given email.
|
110
|
+
#
|
111
|
+
# ==== Examples
|
112
|
+
#
|
113
|
+
# gmail.deliver do
|
114
|
+
# to "friend@gmail.com"
|
115
|
+
# subject "Hello friend!"
|
116
|
+
# body "Hi! How are you?"
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# ... or with already created message:
|
120
|
+
#
|
121
|
+
# mail = Mail.new { ... }
|
122
|
+
# gmail.deliver(mail)
|
123
|
+
#
|
124
|
+
# mail = gmail.compose { ... }
|
125
|
+
# gmail.deliver(mail)
|
126
|
+
def deliver(mail=nil, raise_errors=false, &block)
|
127
|
+
mail = compose(mail, &block) if block_given?
|
128
|
+
mail.deliver!
|
129
|
+
rescue Object => ex
|
130
|
+
raise_errors and raise DeliveryError, "Couldn't deliver email: #{ex.to_s}"
|
131
|
+
end
|
132
|
+
|
133
|
+
# This version of deliver will raise error on failure...
|
134
|
+
def deliver!(mail=nil, &block)
|
135
|
+
deliver(mail, true, &block)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Do something with given mailbox or within it context.
|
139
|
+
#
|
140
|
+
# ==== Examples
|
141
|
+
#
|
142
|
+
# mailbox = gmail.mailbox("INBOX")
|
143
|
+
# mailbox.emails(:all)
|
144
|
+
# mailbox.count(:unread, :before => Time.now-(20*24*3600))
|
145
|
+
#
|
146
|
+
# ... or block style:
|
147
|
+
#
|
148
|
+
# gmail.label("Work") do |mailbox|
|
149
|
+
# mailbox.emails(:unread)
|
150
|
+
# mailbox.count(:all)
|
151
|
+
# ...
|
152
|
+
# end
|
153
|
+
def mailbox(name, &block)
|
154
|
+
@mailbox_mutex.synchronize do
|
155
|
+
name = Net::IMAP.encode_utf7(name.to_s)
|
156
|
+
mailbox = (mailboxes[name] ||= Mailbox.new(self, name))
|
157
|
+
switch_to_mailbox(name) if @current_mailbox != name
|
158
|
+
|
159
|
+
if block_given?
|
160
|
+
mailbox_stack << @current_mailbox
|
161
|
+
result = block.arity == 1 ? block.call(mailbox) : block.call
|
162
|
+
mailbox_stack.pop
|
163
|
+
switch_to_mailbox(mailbox_stack.last)
|
164
|
+
return result
|
165
|
+
end
|
166
|
+
|
167
|
+
return mailbox
|
168
|
+
end
|
169
|
+
end
|
170
|
+
alias :in_mailbox :mailbox
|
171
|
+
alias :in_label :mailbox
|
172
|
+
alias :label :mailbox
|
173
|
+
|
174
|
+
# Alias for <tt>mailbox("INBOX")</tt>. See <tt>Gmail::Client#mailbox</tt>
|
175
|
+
# for details.
|
176
|
+
def inbox
|
177
|
+
mailbox("INBOX")
|
178
|
+
end
|
179
|
+
|
180
|
+
def mailboxes
|
181
|
+
@mailboxes ||= {}
|
182
|
+
end
|
183
|
+
|
184
|
+
def inspect
|
185
|
+
"#<Gmail::Client#{'0x%04x' % (object_id << 1)} (#{username}) #{'dis' if !logged_in?}connected>"
|
186
|
+
end
|
187
|
+
|
188
|
+
def fill_username(username)
|
189
|
+
username =~ /@/ ? username : "#{username}@gmail.com"
|
190
|
+
end
|
191
|
+
|
192
|
+
def mail_domain
|
193
|
+
username.split('@')[0]
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def switch_to_mailbox(mailbox)
|
199
|
+
conn.select(mailbox) if mailbox
|
200
|
+
@current_mailbox = mailbox
|
201
|
+
end
|
202
|
+
|
203
|
+
def mailbox_stack
|
204
|
+
@mailbox_stack ||= []
|
205
|
+
end
|
206
|
+
|
207
|
+
def smtp_settings
|
208
|
+
[:smtp, {
|
209
|
+
:address => GMAIL_SMTP_HOST,
|
210
|
+
:port => GMAIL_SMTP_PORT,
|
211
|
+
:domain => mail_domain,
|
212
|
+
:user_name => username,
|
213
|
+
:password => password,
|
214
|
+
:authentication => 'plain',
|
215
|
+
:enable_starttls_auto => true
|
216
|
+
}]
|
217
|
+
end
|
218
|
+
end # Base
|
219
|
+
end # Client
|
220
|
+
end # Gmail
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Gmail
|
2
|
+
module Client
|
3
|
+
class Plain < Base
|
4
|
+
attr_reader :password
|
5
|
+
|
6
|
+
def initialize(username, password, options={})
|
7
|
+
@password = password
|
8
|
+
super(username, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def login(raise_errors=false)
|
12
|
+
@imap and @logged_in = (login = @imap.login(username, password)) && login.name == 'OK'
|
13
|
+
rescue Net::IMAP::NoResponseError
|
14
|
+
raise_errors and raise AuthorizationError, "Couldn't login to given GMail account: #{username}"
|
15
|
+
end
|
16
|
+
end # Plain
|
17
|
+
end # Client
|
18
|
+
end # Gmail
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'gmail_xoauth'
|
2
|
+
|
3
|
+
module Gmail
|
4
|
+
module Client
|
5
|
+
class XOAuth < Base
|
6
|
+
attr_reader :token
|
7
|
+
attr_reader :secret
|
8
|
+
attr_reader :consumer_key
|
9
|
+
attr_reader :consumer_secret
|
10
|
+
|
11
|
+
def initialize(username, options={})
|
12
|
+
@token = options.delete(:token)
|
13
|
+
@secret = options.delete(:secret)
|
14
|
+
@consumer_key = options.delete(:consumer_key)
|
15
|
+
@consumer_secret = options.delete(:consumer_secret)
|
16
|
+
|
17
|
+
super(username, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def login(raise_errors=false)
|
21
|
+
@imap and @logged_in = (login = @imap.authenticate('XOAUTH', username,
|
22
|
+
:consumer_key => consumer_key,
|
23
|
+
:consumer_secret => consumer_secret,
|
24
|
+
:token => token,
|
25
|
+
:token_secret => secret
|
26
|
+
)) && login.name == 'OK'
|
27
|
+
rescue
|
28
|
+
raise_errors and raise AuthorizationError, "Couldn't login to given GMail account: #{username}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def smtp_settings
|
32
|
+
[:smtp, {
|
33
|
+
:address => GMAIL_SMTP_HOST,
|
34
|
+
:port => GMAIL_SMTP_PORT,
|
35
|
+
:domain => mail_domain,
|
36
|
+
:user_name => username,
|
37
|
+
:password => secret = {
|
38
|
+
:consumer_key => consumer_key,
|
39
|
+
:consumer_secret => consumer_secret,
|
40
|
+
:token => token,
|
41
|
+
:token_secret => token_secret
|
42
|
+
},
|
43
|
+
:authentication => :xoauth,
|
44
|
+
:enable_starttls_auto => true
|
45
|
+
}]
|
46
|
+
end
|
47
|
+
end # XOAuth
|
48
|
+
end # Client
|
49
|
+
end # Gmail
|
data/lib/gmail/labels.rb
CHANGED
@@ -11,7 +11,7 @@ module Gmail
|
|
11
11
|
# Get list of all defined labels.
|
12
12
|
def all
|
13
13
|
(conn.list("", "%")+conn.list("[Gmail]/", "%")).inject([]) do |labels,label|
|
14
|
-
label[:name].each_line {|l| labels << l }
|
14
|
+
label[:name].each_line {|l| labels << Net::IMAP.decode_utf7(l) }
|
15
15
|
labels
|
16
16
|
end
|
17
17
|
end
|
@@ -24,20 +24,20 @@ module Gmail
|
|
24
24
|
|
25
25
|
# Returns +true+ when given label defined.
|
26
26
|
def exists?(label)
|
27
|
-
all.include?(label)
|
27
|
+
all.include?(Net::IMAP.encode_utf7(label))
|
28
28
|
end
|
29
29
|
alias :exist? :exists?
|
30
30
|
|
31
31
|
# Creates given label in your account.
|
32
32
|
def create(label)
|
33
|
-
!!conn.create(label) rescue false
|
33
|
+
!!conn.create(Net::IMAP.encode_utf7(label)) rescue false
|
34
34
|
end
|
35
35
|
alias :new :create
|
36
36
|
alias :add :create
|
37
37
|
|
38
38
|
# Deletes given label from your account.
|
39
39
|
def delete(label)
|
40
|
-
!!conn.delete(label) rescue false
|
40
|
+
!!conn.delete(Net::IMAP.encode_utf7(label)) rescue false
|
41
41
|
end
|
42
42
|
alias :remove :delete
|
43
43
|
|
data/lib/gmail/mailbox.rb
CHANGED
@@ -17,9 +17,11 @@ module Gmail
|
|
17
17
|
}
|
18
18
|
|
19
19
|
attr_reader :name
|
20
|
+
attr_reader :external_name
|
20
21
|
|
21
22
|
def initialize(gmail, name="INBOX")
|
22
23
|
@name = name
|
24
|
+
@external_name = Net::IMAP.decode_utf7(name)
|
23
25
|
@gmail = gmail
|
24
26
|
end
|
25
27
|
|
@@ -53,8 +55,10 @@ module Gmail
|
|
53
55
|
opts[:subject] and search.concat ['SUBJECT', opts[:subject]]
|
54
56
|
opts[:label] and search.concat ['LABEL', opts[:label]]
|
55
57
|
opts[:attachment] and search.concat ['HAS', 'attachment']
|
56
|
-
opts[:search] and search.concat [opts[:search]]
|
57
|
-
|
58
|
+
opts[:search] and search.concat ['BODY', opts[:search]]
|
59
|
+
opts[:body] and search.concat ['BODY', opts[:body]]
|
60
|
+
opts[:query] and search.concat opts[:query]
|
61
|
+
|
58
62
|
@gmail.mailbox(name) do
|
59
63
|
@gmail.conn.uid_search(search).collect do |uid|
|
60
64
|
message = (messages[uid] ||= Message.new(self, uid))
|
@@ -97,7 +101,7 @@ module Gmail
|
|
97
101
|
end
|
98
102
|
|
99
103
|
def inspect
|
100
|
-
"#<Gmail::Mailbox#{'0x%04x' % (object_id << 1)} name=#{
|
104
|
+
"#<Gmail::Mailbox#{'0x%04x' % (object_id << 1)} name=#{external_name}>"
|
101
105
|
end
|
102
106
|
|
103
107
|
def to_s
|
data/lib/gmail/message.rb
CHANGED
@@ -64,11 +64,14 @@ module Gmail
|
|
64
64
|
unflag('[Gmail]/Starred')
|
65
65
|
end
|
66
66
|
|
67
|
-
# Move to trash.
|
67
|
+
# Move to trash / bin.
|
68
68
|
def delete!
|
69
69
|
@mailbox.messages.delete(uid)
|
70
|
-
flag(:
|
71
|
-
|
70
|
+
flag(:deleted)
|
71
|
+
|
72
|
+
# For some, it's called "Trash", for others, it's called "Bin". Support both.
|
73
|
+
trash = @gmail.labels.exist?('[Gmail]/Bin') ? '[Gmail]/Bin' : '[Gmail]/Trash'
|
74
|
+
move_to(trash) unless %w[[Gmail]/Spam [Gmail]/Bin [Gmail]/Trash].include?(@mailbox.name)
|
72
75
|
end
|
73
76
|
|
74
77
|
# Archive this message.
|
@@ -78,7 +81,8 @@ module Gmail
|
|
78
81
|
|
79
82
|
# Move to given box and delete from others.
|
80
83
|
def move_to(name, from=nil)
|
81
|
-
label(name, from)
|
84
|
+
label(name, from)
|
85
|
+
delete! if !%w[[Gmail]/Bin [Gmail]/Trash].include?(name)
|
82
86
|
end
|
83
87
|
alias :move :move_to
|
84
88
|
|
@@ -94,11 +98,10 @@ module Gmail
|
|
94
98
|
#
|
95
99
|
# See also <tt>Gmail::Message#label!</tt>.
|
96
100
|
def label(name, from=nil)
|
97
|
-
@gmail.mailbox(from || @mailbox.
|
101
|
+
@gmail.mailbox(Net::IMAP.encode_utf7(from || @mailbox.external_name)) { @gmail.conn.uid_copy(uid, Net::IMAP.encode_utf7(name)) }
|
98
102
|
rescue Net::IMAP::NoResponseError
|
99
103
|
raise NoLabelError, "Label '#{name}' doesn't exist!"
|
100
104
|
end
|
101
|
-
alias :add_label :label
|
102
105
|
|
103
106
|
# Mark this message with given label. When given label doesn't exist then
|
104
107
|
# it will be automaticaly created.
|
@@ -107,10 +110,11 @@ module Gmail
|
|
107
110
|
def label!(name, from=nil)
|
108
111
|
label(name, from)
|
109
112
|
rescue NoLabelError
|
110
|
-
@gmail.labels.add(name)
|
111
|
-
label
|
113
|
+
@gmail.labels.add(Net::IMAP.encode_utf7(name))
|
114
|
+
label(name, from)
|
112
115
|
end
|
113
|
-
alias :add_label
|
116
|
+
alias :add_label :label!
|
117
|
+
alias :add_label! :label!
|
114
118
|
|
115
119
|
# Remove given label from this message.
|
116
120
|
def remove_label!(name)
|
@@ -119,7 +123,7 @@ module Gmail
|
|
119
123
|
alias :delete_label! :remove_label!
|
120
124
|
|
121
125
|
def inspect
|
122
|
-
"#<Gmail::Message#{'0x%04x' % (object_id << 1)} mailbox=#{@mailbox.
|
126
|
+
"#<Gmail::Message#{'0x%04x' % (object_id << 1)} mailbox=#{@mailbox.external_name}#{' uid='+@uid.to_s if @uid}#{' message_id='+@message_id.to_s if @message_id}>"
|
123
127
|
end
|
124
128
|
|
125
129
|
def method_missing(meth, *args, &block)
|
@@ -132,19 +136,29 @@ module Gmail
|
|
132
136
|
super(meth, *args, &block)
|
133
137
|
end
|
134
138
|
end
|
135
|
-
|
139
|
+
|
140
|
+
def respond_to?(meth, *args, &block)
|
141
|
+
if envelope.respond_to?(meth)
|
142
|
+
return true
|
143
|
+
elsif message.respond_to?(meth)
|
144
|
+
return true
|
145
|
+
else
|
146
|
+
super(meth, *args, &block)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
136
150
|
def envelope
|
137
151
|
@envelope ||= @gmail.mailbox(@mailbox.name) {
|
138
152
|
@gmail.conn.uid_fetch(uid, "ENVELOPE")[0].attr["ENVELOPE"]
|
139
153
|
}
|
140
154
|
end
|
141
|
-
|
142
|
-
private
|
143
155
|
|
144
156
|
def message
|
145
157
|
@message ||= Mail.new(@gmail.mailbox(@mailbox.name) {
|
146
158
|
@gmail.conn.uid_fetch(uid, "RFC822")[0].attr["RFC822"] # RFC822
|
147
159
|
})
|
148
160
|
end
|
161
|
+
alias_method :raw_message, :message
|
162
|
+
|
149
163
|
end # Message
|
150
164
|
end # Gmail
|
data/lib/gmail/version.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe "Gmail client" do
|
4
|
-
subject { Gmail::Client }
|
3
|
+
describe "Gmail client (Plain)" do
|
4
|
+
subject { Gmail::Client::Plain }
|
5
5
|
|
6
6
|
context "on initialize" do
|
7
7
|
it "should set username, password and options" do
|
@@ -19,7 +19,7 @@ describe "Gmail client" do
|
|
19
19
|
|
20
20
|
context "instance" do
|
21
21
|
def mock_client(&block)
|
22
|
-
client = Gmail::Client.new(*TEST_ACCOUNT)
|
22
|
+
client = Gmail::Client::Plain.new(*TEST_ACCOUNT)
|
23
23
|
if block_given?
|
24
24
|
client.connect
|
25
25
|
yield client
|
@@ -45,7 +45,7 @@ describe "Gmail client" do
|
|
45
45
|
|
46
46
|
it "should raise error when given GMail account is invalid and errors enabled" do
|
47
47
|
lambda {
|
48
|
-
client = Gmail::Client.new("foo", "bar")
|
48
|
+
client = Gmail::Client::Plain.new("foo", "bar")
|
49
49
|
client.connect.should be_true
|
50
50
|
client.login!.should_not be_true
|
51
51
|
}.should raise_error(Gmail::Client::AuthorizationError)
|
@@ -53,7 +53,7 @@ describe "Gmail client" do
|
|
53
53
|
|
54
54
|
it "shouldn't login when given GMail account is invalid" do
|
55
55
|
lambda {
|
56
|
-
client = Gmail::Client.new("foo", "bar")
|
56
|
+
client = Gmail::Client::Plain.new("foo", "bar")
|
57
57
|
client.connect.should be_true
|
58
58
|
client.login.should_not be_true
|
59
59
|
}.should_not raise_error(Gmail::Client::AuthorizationError)
|
@@ -137,7 +137,7 @@ describe "Gmail client" do
|
|
137
137
|
|
138
138
|
context "labels" do
|
139
139
|
subject {
|
140
|
-
client = Gmail::Client.new(*TEST_ACCOUNT)
|
140
|
+
client = Gmail::Client::Plain.new(*TEST_ACCOUNT)
|
141
141
|
client.connect
|
142
142
|
client.labels
|
143
143
|
}
|
data/spec/gmail_spec.rb
CHANGED
@@ -8,14 +8,14 @@ describe "Any object" do
|
|
8
8
|
%w[new new!].each do |method|
|
9
9
|
it "##{method} should properly connect with GMail service and return valid connection object" do
|
10
10
|
gmail = Gmail.send(method, *TEST_ACCOUNT)
|
11
|
-
gmail.should be_kind_of(Gmail::Client)
|
11
|
+
gmail.should be_kind_of(Gmail::Client::Plain)
|
12
12
|
gmail.connection.should_not be_nil
|
13
13
|
gmail.should be_logged_in
|
14
14
|
end
|
15
15
|
|
16
16
|
it "##{method} should connect with client and give it context when block given" do
|
17
17
|
Gmail.send(method, *TEST_ACCOUNT) do |gmail|
|
18
|
-
gmail.should be_kind_of(Gmail::Client)
|
18
|
+
gmail.should be_kind_of(Gmail::Client::Plain)
|
19
19
|
gmail.connection.should_not be_nil
|
20
20
|
gmail.should be_logged_in
|
21
21
|
end
|
data/spec/mailbox_spec.rb
CHANGED
@@ -34,5 +34,16 @@ describe "A Gmail mailbox" do
|
|
34
34
|
mailbox.emails(:all, :from => message.from.first.name) == message.from.first.name
|
35
35
|
end
|
36
36
|
end
|
37
|
+
|
38
|
+
it "should be able to do a full text search of message bodies" do
|
39
|
+
pending "This can wait..."
|
40
|
+
#mock_mailbox do |mailbox|
|
41
|
+
# message = mailbox.emails.first
|
42
|
+
# body = message.parts.blank? ? message.body.decoded : message.parts[0].body.decoded
|
43
|
+
# emails = mailbox.emails(:search => body.split(' ').first)
|
44
|
+
# emails.size.should > 0
|
45
|
+
#end
|
46
|
+
end
|
47
|
+
|
37
48
|
end
|
38
49
|
end
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gmail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
- 3
|
8
8
|
- 4
|
9
|
-
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- BehindLogic
|
@@ -15,7 +16,7 @@ autorequire:
|
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date:
|
19
|
+
date: 2011-01-13 00:00:00 +01:00
|
19
20
|
default_executable:
|
20
21
|
dependencies:
|
21
22
|
- !ruby/object:Gem::Dependency
|
@@ -26,6 +27,7 @@ dependencies:
|
|
26
27
|
requirements:
|
27
28
|
- - ">="
|
28
29
|
- !ruby/object:Gem::Version
|
30
|
+
hash: 9
|
29
31
|
segments:
|
30
32
|
- 0
|
31
33
|
- 1
|
@@ -40,6 +42,7 @@ dependencies:
|
|
40
42
|
requirements:
|
41
43
|
- - ">="
|
42
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 5
|
43
46
|
segments:
|
44
47
|
- 2
|
45
48
|
- 2
|
@@ -48,33 +51,51 @@ dependencies:
|
|
48
51
|
type: :runtime
|
49
52
|
version_requirements: *id002
|
50
53
|
- !ruby/object:Gem::Dependency
|
51
|
-
name:
|
54
|
+
name: gmail_xoauth
|
52
55
|
prerelease: false
|
53
56
|
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 19
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 3
|
65
|
+
- 0
|
66
|
+
version: 0.3.0
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
54
73
|
none: false
|
55
74
|
requirements:
|
56
75
|
- - ~>
|
57
76
|
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
58
78
|
segments:
|
59
79
|
- 2
|
60
80
|
- 0
|
61
81
|
version: "2.0"
|
62
82
|
type: :development
|
63
|
-
version_requirements: *
|
83
|
+
version_requirements: *id004
|
64
84
|
- !ruby/object:Gem::Dependency
|
65
85
|
name: mocha
|
66
86
|
prerelease: false
|
67
|
-
requirement: &
|
87
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
68
88
|
none: false
|
69
89
|
requirements:
|
70
90
|
- - ">="
|
71
91
|
- !ruby/object:Gem::Version
|
92
|
+
hash: 25
|
72
93
|
segments:
|
73
94
|
- 0
|
74
95
|
- 9
|
75
96
|
version: "0.9"
|
76
97
|
type: :development
|
77
|
-
version_requirements: *
|
98
|
+
version_requirements: *id005
|
78
99
|
description: A Rubyesque interface to Gmail, with all the tools you will need. Search, read and send multipart emails; archive, mark as read/unread, delete emails; and manage labels.
|
79
100
|
email:
|
80
101
|
- chris@nu7hat.ch
|
@@ -97,6 +118,9 @@ files:
|
|
97
118
|
- gmail.gemspec
|
98
119
|
- lib/gmail.rb
|
99
120
|
- lib/gmail/client.rb
|
121
|
+
- lib/gmail/client/base.rb
|
122
|
+
- lib/gmail/client/plain.rb
|
123
|
+
- lib/gmail/client/xoauth.rb
|
100
124
|
- lib/gmail/labels.rb
|
101
125
|
- lib/gmail/mailbox.rb
|
102
126
|
- lib/gmail/message.rb
|
@@ -121,6 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
121
145
|
requirements:
|
122
146
|
- - ">="
|
123
147
|
- !ruby/object:Gem::Version
|
148
|
+
hash: 3
|
124
149
|
segments:
|
125
150
|
- 0
|
126
151
|
version: "0"
|
@@ -129,6 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
154
|
requirements:
|
130
155
|
- - ">="
|
131
156
|
- !ruby/object:Gem::Version
|
157
|
+
hash: 3
|
132
158
|
segments:
|
133
159
|
- 0
|
134
160
|
version: "0"
|