gmail-afurmanov 0.1.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/.rspec +2 -0
- data/CHANGELOG.md +82 -0
- data/LICENSE +21 -0
- data/README.md +268 -0
- data/Rakefile +39 -0
- data/TODO.md +6 -0
- data/VERSION +1 -0
- data/gemspec.yml +22 -0
- data/gmail.gemspec +18 -0
- data/lib/gmail.rb +65 -0
- data/lib/gmail/client.rb +29 -0
- data/lib/gmail/client/base.rb +223 -0
- data/lib/gmail/client/plain.rb +20 -0
- data/lib/gmail/client/xoauth.rb +51 -0
- data/lib/gmail/labels.rb +48 -0
- data/lib/gmail/mailbox.rb +117 -0
- data/lib/gmail/message.rb +164 -0
- data/lib/gmail/version.rb +12 -0
- data/spec/account.yml.example +2 -0
- data/spec/client_spec.rb +173 -0
- data/spec/gmail_spec.rb +38 -0
- data/spec/mailbox_spec.rb +47 -0
- data/spec/message_spec.rb +51 -0
- data/spec/spec_helper.rb +29 -0
- metadata +185 -0
data/gmail.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
$:.unshift(File.expand_path('../lib', __FILE__))
|
4
|
+
require 'gmail/version'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Ore::Specification.new do |gemspec|
|
8
|
+
gemspec.version = Gmail.version
|
9
|
+
end
|
10
|
+
rescue NameError
|
11
|
+
begin
|
12
|
+
require 'ore/specification'
|
13
|
+
retry
|
14
|
+
rescue LoadError
|
15
|
+
STDERR.puts "The '#{__FILE__}' file requires Ore."
|
16
|
+
STDERR.puts "Run `gem install ore-core` to install Ore."
|
17
|
+
end
|
18
|
+
end
|
data/lib/gmail.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'net/imap'
|
2
|
+
require 'net/smtp'
|
3
|
+
require 'mail'
|
4
|
+
require 'date'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
if RUBY_VERSION < "1.8.7"
|
8
|
+
require "smtp_tls"
|
9
|
+
end
|
10
|
+
|
11
|
+
class Object
|
12
|
+
def to_imap_date
|
13
|
+
Date.parse(to_s).strftime("%d-%B-%Y")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Gmail
|
18
|
+
autoload :Version, "gmail/version"
|
19
|
+
autoload :Client, "gmail/client"
|
20
|
+
autoload :Labels, "gmail/labels"
|
21
|
+
autoload :Mailbox, "gmail/mailbox"
|
22
|
+
autoload :Message, "gmail/message"
|
23
|
+
|
24
|
+
class << self
|
25
|
+
# Creates new Gmail connection using given authorization options.
|
26
|
+
#
|
27
|
+
# ==== Examples
|
28
|
+
#
|
29
|
+
# Gmail.new(:plain, "foo@gmail.com", "password")
|
30
|
+
# Gmail.new(:xoauth, "foo@gmail.com",
|
31
|
+
# :consumer_key => "",
|
32
|
+
# :consumer_secret => "",
|
33
|
+
# :token => "",
|
34
|
+
# :secret => "")
|
35
|
+
#
|
36
|
+
# To use plain authentication mehod you can also call:
|
37
|
+
#
|
38
|
+
# Gmail.new("foo@gmail.com", "password")
|
39
|
+
#
|
40
|
+
# You can also use block-style call:
|
41
|
+
#
|
42
|
+
# Gmail.new("foo@gmail.com", "password") do |client|
|
43
|
+
# # ...
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
|
47
|
+
['', '!'].each { |kind|
|
48
|
+
define_method("new#{kind}") do |*args, &block| # def new(*args, &block)
|
49
|
+
args.unshift(:plain) unless args.first.is_a?(Symbol) # args.unshift(:plain) unless args.first.is_a?(Symbol)
|
50
|
+
client = Gmail::Client.new(*args) # client = Gmail::Client.new(*args)
|
51
|
+
client.send("connect#{kind}") and client.send("login#{kind}") # client.connect and client.login
|
52
|
+
#
|
53
|
+
if block_given? # if block_given?
|
54
|
+
yield client # yield client
|
55
|
+
client.logout # client.logout
|
56
|
+
end # end
|
57
|
+
#
|
58
|
+
client # client
|
59
|
+
end # end
|
60
|
+
}
|
61
|
+
|
62
|
+
alias :connect :new
|
63
|
+
alias :connect! :new!
|
64
|
+
end # << self
|
65
|
+
end # Gmail
|
data/lib/gmail/client.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Gmail
|
2
|
+
module 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
|
+
# Raised when given client is not registered
|
10
|
+
class UnknownClient < ArgumentError; end
|
11
|
+
|
12
|
+
def self.register(name, klass)
|
13
|
+
@clients ||= {}
|
14
|
+
@clients[name] = klass
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.new(name, *args)
|
18
|
+
if client = @clients[name]
|
19
|
+
client.new(*args)
|
20
|
+
else
|
21
|
+
raise UnknownClient, "No such client: #{name}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'gmail/client/base'
|
26
|
+
require 'gmail/client/plain'
|
27
|
+
require 'gmail/client/xoauth'
|
28
|
+
end # Client
|
29
|
+
end # Gmail
|
@@ -0,0 +1,223 @@
|
|
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 = 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
|
+
if mailbox
|
200
|
+
mailbox = Net::IMAP.encode_utf7(mailbox)
|
201
|
+
conn.select(mailbox)
|
202
|
+
end
|
203
|
+
@current_mailbox = mailbox
|
204
|
+
end
|
205
|
+
|
206
|
+
def mailbox_stack
|
207
|
+
@mailbox_stack ||= []
|
208
|
+
end
|
209
|
+
|
210
|
+
def smtp_settings
|
211
|
+
[:smtp, {
|
212
|
+
:address => GMAIL_SMTP_HOST,
|
213
|
+
:port => GMAIL_SMTP_PORT,
|
214
|
+
:domain => mail_domain,
|
215
|
+
:user_name => username,
|
216
|
+
:password => password,
|
217
|
+
:authentication => 'plain',
|
218
|
+
:enable_starttls_auto => true
|
219
|
+
}]
|
220
|
+
end
|
221
|
+
end # Base
|
222
|
+
end # Client
|
223
|
+
end # Gmail
|
@@ -0,0 +1,20 @@
|
|
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
|
+
|
18
|
+
register :plain, Plain
|
19
|
+
end # Client
|
20
|
+
end # Gmail
|
@@ -0,0 +1,51 @@
|
|
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 => {
|
38
|
+
:consumer_key => consumer_key,
|
39
|
+
:consumer_secret => consumer_secret,
|
40
|
+
:token => token,
|
41
|
+
:token_secret => secret
|
42
|
+
},
|
43
|
+
:authentication => :xoauth,
|
44
|
+
:enable_starttls_auto => true
|
45
|
+
}]
|
46
|
+
end
|
47
|
+
end # XOAuth
|
48
|
+
|
49
|
+
register :xoauth, XOAuth
|
50
|
+
end # Client
|
51
|
+
end # Gmail
|
data/lib/gmail/labels.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Gmail
|
2
|
+
class Labels
|
3
|
+
include Enumerable
|
4
|
+
attr_reader :connection
|
5
|
+
alias :conn :connection
|
6
|
+
|
7
|
+
def initialize(connection)
|
8
|
+
@connection = connection
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get list of all defined labels.
|
12
|
+
def all
|
13
|
+
(conn.list("", "%")+conn.list("[Gmail]/", "%")).inject([]) do |labels,label|
|
14
|
+
label[:name].each_line {|l| labels << Net::IMAP.decode_utf7(l) }
|
15
|
+
labels
|
16
|
+
end
|
17
|
+
end
|
18
|
+
alias :list :all
|
19
|
+
alias :to_a :all
|
20
|
+
|
21
|
+
def each(*args, &block)
|
22
|
+
all.each(*args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns +true+ when given label defined.
|
26
|
+
def exists?(label)
|
27
|
+
all.include?(label)
|
28
|
+
end
|
29
|
+
alias :exist? :exists?
|
30
|
+
|
31
|
+
# Creates given label in your account.
|
32
|
+
def create(label)
|
33
|
+
!!conn.create(Net::IMAP.encode_utf7(label)) rescue false
|
34
|
+
end
|
35
|
+
alias :new :create
|
36
|
+
alias :add :create
|
37
|
+
|
38
|
+
# Deletes given label from your account.
|
39
|
+
def delete(label)
|
40
|
+
!!conn.delete(Net::IMAP.encode_utf7(label)) rescue false
|
41
|
+
end
|
42
|
+
alias :remove :delete
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"#<Gmail::Labels#{'0x%04x' % (object_id << 1)}>"
|
46
|
+
end
|
47
|
+
end # Labels
|
48
|
+
end # Gmail
|