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.
@@ -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
@@ -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