pauldix-ruby-gmail 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +23 -0
- data/.gitignore +1 -0
- data/History.txt +75 -0
- data/Manifest.txt +14 -0
- data/README.markdown +172 -0
- data/Rakefile +22 -0
- data/VERSION +1 -0
- data/lib/gmail.rb +168 -0
- data/lib/gmail/mailbox.rb +71 -0
- data/lib/gmail/message.rb +104 -0
- data/lib/smtp_tls.rb +94 -0
- data/ruby-gmail.gemspec +65 -0
- data/test/test_gmail.rb +72 -0
- data/test/test_helper.rb +4 -0
- metadata +103 -0
data/.autotest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
# Autotest.add_hook :initialize do |at|
|
6
|
+
# at.extra_files << "../some/external/dependency.rb"
|
7
|
+
#
|
8
|
+
# at.libs << ":../some/external"
|
9
|
+
#
|
10
|
+
# at.add_exception 'vendor'
|
11
|
+
#
|
12
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
+
# at.files_matching(/test_.*rb$/)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# %w(TestA TestB).each do |klass|
|
17
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
# Autotest.add_hook :run_command do |at|
|
22
|
+
# system "rake build"
|
23
|
+
# end
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/History.txt
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
=== 0.1.1 / 2010-05-11
|
2
|
+
|
3
|
+
* 1 minor fix
|
4
|
+
|
5
|
+
* Added explicit tmail dependency in gemspec
|
6
|
+
* Added better README tutorial content
|
7
|
+
|
8
|
+
=== 0.0.9 / 2010-04-17
|
9
|
+
|
10
|
+
* 1 bugfix
|
11
|
+
|
12
|
+
* Fixed content-transfer-encoding when sending email
|
13
|
+
|
14
|
+
=== 0.0.8 / 2009-12-23
|
15
|
+
|
16
|
+
* 1 bugfix
|
17
|
+
|
18
|
+
* Fixed attaching a file to an empty message
|
19
|
+
|
20
|
+
=== 0.0.7 / 2009-12-23
|
21
|
+
|
22
|
+
* 1 bugfix
|
23
|
+
|
24
|
+
* Improved multipart message parsing reliability
|
25
|
+
|
26
|
+
=== 0.0.6 / 2009-12-21
|
27
|
+
|
28
|
+
* 1 bugfix
|
29
|
+
|
30
|
+
* Fixed multipart parsing for when the boundary is marked in quotes.
|
31
|
+
|
32
|
+
=== 0.0.5 / 2009-12-16
|
33
|
+
|
34
|
+
* 1 bugfix
|
35
|
+
|
36
|
+
* Fixed IMAP initializer to work with Ruby 1.9's net/imap
|
37
|
+
|
38
|
+
* 4 minor enhancements
|
39
|
+
|
40
|
+
* Better logout depending on the IMAP connection itself
|
41
|
+
* Added MIME::Message#text and MIME::Message#html for easier access to an email body
|
42
|
+
* Improved the MIME-parsing API slightly
|
43
|
+
* Added some tests
|
44
|
+
|
45
|
+
=== 0.0.4 / 2009-11-30
|
46
|
+
|
47
|
+
* 4 minor enhancement
|
48
|
+
|
49
|
+
* Added label creation (thanks to Justin Perkins / http://github.com/justinperkins)
|
50
|
+
* Made the gem login automatically when first needed
|
51
|
+
* Added an optional block on the Gmail.new object that will login and logout for you
|
52
|
+
* Added several search options (thanks to Mikkel Malmberg / http://github.com/mikker)
|
53
|
+
|
54
|
+
=== 0.0.3 / 2009-11-19
|
55
|
+
|
56
|
+
* 1 bugfix
|
57
|
+
|
58
|
+
* Fixed MIME::Message#content= for messages without an encoding
|
59
|
+
|
60
|
+
* 1 minor enhancement
|
61
|
+
|
62
|
+
* Added Gmail#new_message
|
63
|
+
|
64
|
+
=== 0.0.2 / 2009-11-18
|
65
|
+
|
66
|
+
* 1 minor enhancement
|
67
|
+
|
68
|
+
* Made all of the examples in the README possible
|
69
|
+
|
70
|
+
=== 0.0.1 / 2009-11-18
|
71
|
+
|
72
|
+
* 1 major enhancement
|
73
|
+
|
74
|
+
* Birthday!
|
75
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
.autotest
|
2
|
+
History.txt
|
3
|
+
lib/gmail/mailbox.rb
|
4
|
+
lib/gmail/message.rb
|
5
|
+
lib/gmail.rb
|
6
|
+
lib/ietf/rfc2045.rb
|
7
|
+
lib/ietf/rfc822.rb
|
8
|
+
lib/mime/entity.rb
|
9
|
+
lib/mime/entity_tmail.rb
|
10
|
+
lib/mime/message.rb
|
11
|
+
lib/smtp_tls.rb
|
12
|
+
Manifest.txt
|
13
|
+
Rakefile
|
14
|
+
README.markdown
|
data/README.markdown
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
# ruby-gmail
|
2
|
+
|
3
|
+
* Homepage: [http://dcparker.github.com/ruby-gmail/](http://dcparker.github.com/ruby-gmail/)
|
4
|
+
* Code: [http://github.com/dcparker/ruby-gmail](http://github.com/dcparker/ruby-gmail)
|
5
|
+
* Gem: [http://gemcutter.org/gems/ruby-gmail](http://gemcutter.org/gems/ruby-gmail)
|
6
|
+
|
7
|
+
## Author(s)
|
8
|
+
|
9
|
+
* Daniel Parker of BehindLogic.com
|
10
|
+
|
11
|
+
Extra thanks for specific feature contributions from:
|
12
|
+
|
13
|
+
* [Justin Perkins](http://github.com/justinperkins)
|
14
|
+
* [Mikkel Malmberg](http://github.com/mikker)
|
15
|
+
* [Julien Blanchard](http://github.com/julienXX)
|
16
|
+
* [Federico Galassi](http://github.com/fgalassi)
|
17
|
+
|
18
|
+
## Description
|
19
|
+
|
20
|
+
A Rubyesque interface to Gmail, with all the tools you'll need. Search, read and send multipart emails; archive, mark as read/unread, delete emails; and manage labels.
|
21
|
+
|
22
|
+
## Features
|
23
|
+
|
24
|
+
* Search emails
|
25
|
+
* Read emails (handles attachments)
|
26
|
+
* Emails: Label, archive, delete, mark as read/unread/spam
|
27
|
+
* Create and delete labels
|
28
|
+
* Create and send multipart email messages in plaintext and/or html, with inline images and attachments
|
29
|
+
* Utilizes Gmail's IMAP & SMTP, MIME-type detection and parses and generates MIME properly.
|
30
|
+
|
31
|
+
## Problems:
|
32
|
+
|
33
|
+
* May not correctly read malformed MIME messages. This could possibly be corrected by having IMAP parse the MIME structure.
|
34
|
+
* Cannot grab the plain or html message without also grabbing attachments. It might be nice to lazy-[down]load attachments.
|
35
|
+
|
36
|
+
## Example Code:
|
37
|
+
|
38
|
+
### 1) Require gmail
|
39
|
+
|
40
|
+
require 'gmail'
|
41
|
+
|
42
|
+
### 2) Start an authenticated gmail session
|
43
|
+
|
44
|
+
# If you pass a block, the session will be passed into the block,
|
45
|
+
# and the session will be logged out after the block is executed.
|
46
|
+
gmail = Gmail.new(username, password)
|
47
|
+
# ...do things...
|
48
|
+
gmail.logout
|
49
|
+
|
50
|
+
Gmail.new(username, password) do |gmail|
|
51
|
+
# ...do things...
|
52
|
+
end
|
53
|
+
|
54
|
+
### 3) Count and gather emails!
|
55
|
+
|
56
|
+
# Get counts for messages in the inbox
|
57
|
+
gmail.inbox.count
|
58
|
+
gmail.inbox.count(:unread)
|
59
|
+
gmail.inbox.count(:read)
|
60
|
+
|
61
|
+
# Count with some criteria
|
62
|
+
gmail.inbox.count(:after => Date.parse("2010-02-20"), :before => Date.parse("2010-03-20"))
|
63
|
+
gmail.inbox.count(:on => Date.parse("2010-04-15"))
|
64
|
+
gmail.inbox.count(:from => "myfriend@gmail.com")
|
65
|
+
gmail.inbox.count(:to => "directlytome@gmail.com")
|
66
|
+
|
67
|
+
# Combine flags and options
|
68
|
+
gmail.inbox.count(:unread, :from => "myboss@gmail.com")
|
69
|
+
|
70
|
+
# Labels work the same way as inbox
|
71
|
+
gmail.mailbox('Urgent').count
|
72
|
+
|
73
|
+
# Getting messages works the same way as counting: optional flag, and optional arguments
|
74
|
+
# Remember that every message in a conversation/thread will come as a separate message.
|
75
|
+
gmail.inbox.emails(:unread, :before => Date.parse("2010-04-20"), :from => "myboss@gmail.com")
|
76
|
+
|
77
|
+
### 4) Work with emails!
|
78
|
+
|
79
|
+
# any news older than 4-20, mark as read and archive it...
|
80
|
+
gmail.inbox.emails(:before => Date.parse("2010-04-20"), :from => "news@nbcnews.com").each do |email|
|
81
|
+
email.mark(:read) # can also mark :unread or :spam
|
82
|
+
email.archive!
|
83
|
+
end
|
84
|
+
|
85
|
+
# delete emails from X...
|
86
|
+
gmail.inbox.emails(:from => "x-fiancé@gmail.com").each do |email|
|
87
|
+
email.delete!
|
88
|
+
end
|
89
|
+
|
90
|
+
# Save all attachments in the "Faxes" label to a folder
|
91
|
+
folder = "/where/ever"
|
92
|
+
gmail.mailbox("Faxes").emails.each do |email|
|
93
|
+
if !email.message.attachments.empty?
|
94
|
+
email.message.save_attachments_to(folder)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Save just the first attachment from the newest unread email (assuming pdf)
|
99
|
+
# For #save_to_file:
|
100
|
+
# + provide a path - save to attachment filename in path
|
101
|
+
# + provide a filename - save to file specified
|
102
|
+
# + provide no arguments - save to attachment filename in current directory
|
103
|
+
email = gmail.inbox.emails(:unread).first
|
104
|
+
email.attachments[0].save_to_file("/path/to/location")
|
105
|
+
|
106
|
+
# Add a label to a message
|
107
|
+
email.label("Faxes")
|
108
|
+
|
109
|
+
# Or "move" the message to a label
|
110
|
+
email.move_to("Faxes")
|
111
|
+
|
112
|
+
### 5) Create new emails!
|
113
|
+
|
114
|
+
Creating emails now uses the amazing [Mail](http://rubygems.org/gems/mail) rubygem. See its [documentation here](http://github.com/mikel/mail). Ruby-gmail will automatically configure your Mail emails to be sent via your Gmail account's SMTP, so they will be in your Gmail's "Sent" folder. Also, no need to specify the "From" email either, because ruby-gmail will set it for you.
|
115
|
+
|
116
|
+
gmail.deliver do
|
117
|
+
to "email@example.com"
|
118
|
+
subject "Having fun in Puerto Rico!"
|
119
|
+
text_part do
|
120
|
+
body "Text of plaintext message."
|
121
|
+
end
|
122
|
+
html_part do
|
123
|
+
body "<p>Text of <em>html</em> message.</p>"
|
124
|
+
end
|
125
|
+
add_file "/path/to/some_image.jpg"
|
126
|
+
end
|
127
|
+
# Or, generate the message first and send it later
|
128
|
+
email = gmail.generate_message do
|
129
|
+
to "email@example.com"
|
130
|
+
subject "Having fun in Puerto Rico!"
|
131
|
+
body "Spent the day on the road..."
|
132
|
+
end
|
133
|
+
email.deliver!
|
134
|
+
# Or...
|
135
|
+
gmail.deliver(email)
|
136
|
+
|
137
|
+
## Requirements
|
138
|
+
|
139
|
+
* ruby
|
140
|
+
* net/smtp
|
141
|
+
* net/imap
|
142
|
+
* tmail
|
143
|
+
* shared-mime-info rubygem (for MIME-detection when attaching files)
|
144
|
+
|
145
|
+
## Install
|
146
|
+
|
147
|
+
gem install ruby-gmail
|
148
|
+
|
149
|
+
## License
|
150
|
+
|
151
|
+
(The MIT License)
|
152
|
+
|
153
|
+
Copyright (c) 2009 BehindLogic
|
154
|
+
|
155
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
156
|
+
a copy of this software and associated documentation files (the
|
157
|
+
'Software'), to deal in the Software without restriction, including
|
158
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
159
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
160
|
+
permit persons to whom the Software is furnished to do so, subject to
|
161
|
+
the following conditions:
|
162
|
+
|
163
|
+
The above copyright notice and this permission notice shall be
|
164
|
+
included in all copies or substantial portions of the Software.
|
165
|
+
|
166
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
167
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
168
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
169
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
170
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
171
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
172
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "ruby-gmail"
|
9
|
+
gem.summary = %Q{A Rubyesque interface to Gmail, with all the tools you'll need.}
|
10
|
+
gem.description = %Q{A Rubyesque interface to Gmail, with all the tools you'll need. Search, read and send multipart emails; archive, mark as read/unread, delete emails; and manage labels.}
|
11
|
+
gem.email = "gems@behindlogic.com"
|
12
|
+
gem.homepage = "http://dcparker.github.com/ruby-gmail"
|
13
|
+
gem.authors = ["BehindLogic"]
|
14
|
+
gem.post_install_message = "\n\033[34mIf ruby-gmail saves you TWO hours of work, want to compensate me for, like, a half-hour?\nSupport me in making new and better gems:\033[0m \033[31;4mhttp://pledgie.com/campaigns/7087\033[0m\n\n"
|
15
|
+
gem.add_dependency('shared-mime-info', '>= 0')
|
16
|
+
gem.add_dependency('mail', '>= 2.2.1')
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/lib/gmail.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'net/imap'
|
2
|
+
|
3
|
+
class Gmail
|
4
|
+
VERSION = '0.0.9'
|
5
|
+
|
6
|
+
class NoLabel < RuntimeError; end
|
7
|
+
|
8
|
+
##################################
|
9
|
+
# Gmail.new(username, password)
|
10
|
+
##################################
|
11
|
+
def initialize(username, password)
|
12
|
+
# This is to hide the username and password, not like it REALLY needs hiding, but ... you know.
|
13
|
+
# Could be helpful when demoing the gem in irb, these bits won't show up that way.
|
14
|
+
class << self
|
15
|
+
class << self
|
16
|
+
attr_accessor :username, :password
|
17
|
+
end
|
18
|
+
end
|
19
|
+
meta.username = username =~ /@/ ? username : username + '@gmail.com'
|
20
|
+
meta.password = password
|
21
|
+
@imap = Net::IMAP.new('imap.gmail.com',993,true,nil,false)
|
22
|
+
if block_given?
|
23
|
+
login # This is here intentionally. Normally, we get auto logged-in when first needed.
|
24
|
+
yield self
|
25
|
+
logout
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
###########################
|
30
|
+
# READING EMAILS
|
31
|
+
#
|
32
|
+
# gmail.inbox
|
33
|
+
# gmail.label('News')
|
34
|
+
#
|
35
|
+
###########################
|
36
|
+
|
37
|
+
def inbox
|
38
|
+
in_label('inbox')
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_label(name)
|
42
|
+
imap.create(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
# List the available labels
|
46
|
+
def labels
|
47
|
+
(imap.list("", "%") + imap.list("[Gmail]/", "%")).inject([]) { |labels,label|
|
48
|
+
label[:name].each_line { |l| labels << l }; labels }
|
49
|
+
end
|
50
|
+
|
51
|
+
# gmail.label(name)
|
52
|
+
def label(name)
|
53
|
+
mailboxes[name] ||= Mailbox.new(self, mailbox)
|
54
|
+
end
|
55
|
+
alias :mailbox :label
|
56
|
+
|
57
|
+
###########################
|
58
|
+
# MAKING EMAILS
|
59
|
+
#
|
60
|
+
# gmail.generate_message do
|
61
|
+
# ...inside Mail context...
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# gmail.deliver do ... end
|
65
|
+
#
|
66
|
+
# mail = Mail.new...
|
67
|
+
# gmail.deliver!(mail)
|
68
|
+
###########################
|
69
|
+
def generate_message(&block)
|
70
|
+
require 'net/smtp'
|
71
|
+
require 'smtp_tls'
|
72
|
+
require 'mail'
|
73
|
+
mail = Mail.new(&block)
|
74
|
+
mail.delivery_method(*smtp_settings)
|
75
|
+
mail
|
76
|
+
end
|
77
|
+
|
78
|
+
def deliver(mail=nil, &block)
|
79
|
+
require 'net/smtp'
|
80
|
+
require 'smtp_tls'
|
81
|
+
require 'mail'
|
82
|
+
mail = Mail.new(&block) if block_given?
|
83
|
+
mail.delivery_method(*smtp_settings)
|
84
|
+
mail.from = meta.username unless mail.from
|
85
|
+
mail.deliver!
|
86
|
+
end
|
87
|
+
|
88
|
+
###########################
|
89
|
+
# LOGIN
|
90
|
+
###########################
|
91
|
+
def login
|
92
|
+
res = @imap.login(meta.username, meta.password)
|
93
|
+
@logged_in = true if res.name == 'OK'
|
94
|
+
end
|
95
|
+
def logged_in?
|
96
|
+
!!@logged_in
|
97
|
+
end
|
98
|
+
# Log out of gmail
|
99
|
+
def logout
|
100
|
+
if logged_in?
|
101
|
+
res = @imap.logout
|
102
|
+
@logged_in = false if res.name == 'OK'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def in_mailbox(mailbox, &block)
|
107
|
+
if block_given?
|
108
|
+
mailbox_stack << mailbox
|
109
|
+
unless @selected == mailbox.name
|
110
|
+
imap.select(mailbox.name)
|
111
|
+
@selected = mailbox.name
|
112
|
+
end
|
113
|
+
value = block.arity == 1 ? block.call(mailbox) : block.call
|
114
|
+
mailbox_stack.pop
|
115
|
+
# Select previously selected mailbox if there is one
|
116
|
+
if mailbox_stack.last
|
117
|
+
imap.select(mailbox_stack.last.name)
|
118
|
+
@selected = mailbox.name
|
119
|
+
end
|
120
|
+
return value
|
121
|
+
else
|
122
|
+
mailboxes[name] ||= Mailbox.new(self, mailbox)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
alias :in_label :in_mailbox
|
126
|
+
|
127
|
+
###########################
|
128
|
+
# Other...
|
129
|
+
###########################
|
130
|
+
def inspect
|
131
|
+
"#<Gmail:#{'0x%x' % (object_id << 1)} (#{meta.username}) #{'dis' if !logged_in?}connected>"
|
132
|
+
end
|
133
|
+
|
134
|
+
# Accessor for @imap, but ensures that it's logged in first.
|
135
|
+
def imap
|
136
|
+
unless logged_in?
|
137
|
+
login
|
138
|
+
at_exit { logout } # Set up auto-logout for later.
|
139
|
+
end
|
140
|
+
@imap
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def mailboxes
|
145
|
+
@mailboxes ||= {}
|
146
|
+
end
|
147
|
+
def mailbox_stack
|
148
|
+
@mailbox_stack ||= []
|
149
|
+
end
|
150
|
+
def meta
|
151
|
+
class << self; self end
|
152
|
+
end
|
153
|
+
def domain
|
154
|
+
meta.username.split('@')[0]
|
155
|
+
end
|
156
|
+
def smtp_settings
|
157
|
+
[:smtp, {:address => "smtp.gmail.com",
|
158
|
+
:port => 587,
|
159
|
+
:domain => domain,
|
160
|
+
:user_name => meta.username,
|
161
|
+
:password => meta.password,
|
162
|
+
:authentication => 'plain',
|
163
|
+
:enable_starttls_auto => true}]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
require 'gmail/mailbox'
|
168
|
+
require 'gmail/message'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'time'
|
3
|
+
class Object
|
4
|
+
def to_imap_date
|
5
|
+
Date.parse(to_s).strftime("%d-%B-%Y")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Gmail
|
10
|
+
class Mailbox
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
def initialize(gmail, name)
|
14
|
+
@gmail = gmail
|
15
|
+
@name = name
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
"<#Mailbox name=#{@name}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
name
|
24
|
+
end
|
25
|
+
|
26
|
+
# Method: emails
|
27
|
+
# Args: [ :all | :unread | :read ]
|
28
|
+
# Opts: {:since => Date.new}
|
29
|
+
def emails(key_or_opts = :all, opts={})
|
30
|
+
if key_or_opts.is_a?(Hash) && opts.empty?
|
31
|
+
search = ['ALL']
|
32
|
+
opts = key_or_opts
|
33
|
+
elsif key_or_opts.is_a?(Symbol) && opts.is_a?(Hash)
|
34
|
+
aliases = {
|
35
|
+
:all => ['ALL'],
|
36
|
+
:unread => ['UNSEEN'],
|
37
|
+
:read => ['SEEN']
|
38
|
+
}
|
39
|
+
search = aliases[key_or_opts]
|
40
|
+
elsif key_or_opts.is_a?(Array) && opts.empty?
|
41
|
+
search = key_or_opts
|
42
|
+
else
|
43
|
+
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."
|
44
|
+
end
|
45
|
+
if !opts.empty?
|
46
|
+
# Support for several search macros
|
47
|
+
# :before => Date, :on => Date, :since => Date, :from => String, :to => String
|
48
|
+
search.concat ['SINCE', opts[:after].to_imap_date] if opts[:after]
|
49
|
+
search.concat ['BEFORE', opts[:before].to_imap_date] if opts[:before]
|
50
|
+
search.concat ['ON', opts[:on].to_imap_date] if opts[:on]
|
51
|
+
search.concat ['FROM', opts[:from]] if opts[:from]
|
52
|
+
search.concat ['TO', opts[:to]] if opts[:to]
|
53
|
+
end
|
54
|
+
|
55
|
+
# puts "Gathering #{(aliases[key] || key).inspect} messages for mailbox '#{name}'..."
|
56
|
+
@gmail.in_mailbox(self) do
|
57
|
+
@gmail.imap.uid_search(search).collect { |uid| messages[uid] ||= Message.new(@gmail, self, uid) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# This is a convenience method that really probably shouldn't need to exist, but it does make code more readable
|
62
|
+
# if seriously all you want is the count of messages.
|
63
|
+
def count(*args)
|
64
|
+
emails(*args).length
|
65
|
+
end
|
66
|
+
|
67
|
+
def messages
|
68
|
+
@messages ||= {}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
#require 'mime/message'
|
2
|
+
class Gmail
|
3
|
+
class Message
|
4
|
+
def initialize(gmail, mailbox, uid)
|
5
|
+
@gmail = gmail
|
6
|
+
@mailbox = mailbox
|
7
|
+
@uid = uid
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
"<#Message:#{object_id} mailbox=#{@mailbox.name}#{' uid='+@uid.to_s if @uid}#{' message_id='+@message_id.to_s if @message_id}>"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Auto IMAP info
|
15
|
+
def uid
|
16
|
+
@uid ||= @gmail.imap.uid_search(['HEADER', 'Message-ID', message_id])[0]
|
17
|
+
end
|
18
|
+
|
19
|
+
# IMAP Operations
|
20
|
+
def flag(flg)
|
21
|
+
@gmail.in_mailbox(@mailbox) do
|
22
|
+
@gmail.imap.uid_store(uid, "+FLAGS", [flg])
|
23
|
+
end ? true : false
|
24
|
+
end
|
25
|
+
|
26
|
+
def unflag(flg)
|
27
|
+
@gmail.in_mailbox(@mailbox) do
|
28
|
+
@gmail.imap.uid_store(uid, "-FLAGS", [flg])
|
29
|
+
end ? true : false
|
30
|
+
end
|
31
|
+
|
32
|
+
# Gmail Operations
|
33
|
+
def mark(flag)
|
34
|
+
case flag
|
35
|
+
when :read
|
36
|
+
flag(:Seen)
|
37
|
+
when :unread
|
38
|
+
unflag(:Seen)
|
39
|
+
when :deleted
|
40
|
+
flag(:Deleted)
|
41
|
+
when :spam
|
42
|
+
move_to('[Gmail]/Spam')
|
43
|
+
end ? true : false
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete!
|
47
|
+
@mailbox.messages.delete(uid)
|
48
|
+
flag(:Deleted)
|
49
|
+
end
|
50
|
+
|
51
|
+
def label(name)
|
52
|
+
@gmail.in_mailbox(@mailbox) do
|
53
|
+
begin
|
54
|
+
@gmail.imap.uid_copy(uid, name)
|
55
|
+
rescue Net::IMAP::NoResponseError
|
56
|
+
raise Gmail::NoLabel, "No label `#{name}' exists!"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def label!(name)
|
62
|
+
@gmail.in_mailbox(@mailbox) do
|
63
|
+
begin
|
64
|
+
@gmail.imap.uid_copy(uid, name)
|
65
|
+
rescue Net::IMAP::NoResponseError
|
66
|
+
# need to create the label first
|
67
|
+
@gmail.create_label(name)
|
68
|
+
retry
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# We're not sure of any 'labels' except the 'mailbox' we're in at the moment.
|
74
|
+
# Research whether we can find flags that tell which other labels this email is a part of.
|
75
|
+
# def remove_label(name)
|
76
|
+
# end
|
77
|
+
|
78
|
+
def move_to(name)
|
79
|
+
label(name) && delete!
|
80
|
+
end
|
81
|
+
|
82
|
+
def archive!
|
83
|
+
move_to('[Gmail]/All Mail')
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Parsed MIME message object
|
89
|
+
def message
|
90
|
+
require 'mail'
|
91
|
+
_body = @gmail.in_mailbox(@mailbox) { @gmail.imap.uid_fetch(uid, "RFC822")[0].attr["RFC822"] }
|
92
|
+
@message ||= Mail.new(_body)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Delegate all other methods to the Mail message
|
96
|
+
def method_missing(*args, &block)
|
97
|
+
if block_given?
|
98
|
+
message.send(*args, &block)
|
99
|
+
else
|
100
|
+
message.send(*args)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/smtp_tls.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require "openssl"
|
2
|
+
require "net/smtp"
|
3
|
+
|
4
|
+
Net::SMTP.class_eval do
|
5
|
+
|
6
|
+
def self.start( address, port = nil,
|
7
|
+
helo = 'localhost.localdomain',
|
8
|
+
user = nil, secret = nil, authtype = nil, use_tls = false,
|
9
|
+
&block) # :yield: smtp
|
10
|
+
new(address, port).start(helo, user, secret, authtype, use_tls, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def start( helo = 'localhost.localdomain',
|
14
|
+
user = nil, secret = nil, authtype = nil ) # :yield: smtp
|
15
|
+
start_method = starttls_auto? ? :do_tls_start : :do_start
|
16
|
+
if block_given?
|
17
|
+
begin
|
18
|
+
send(start_method, helo, user, secret, authtype)
|
19
|
+
return yield(self)
|
20
|
+
ensure
|
21
|
+
do_finish
|
22
|
+
end
|
23
|
+
else
|
24
|
+
send(start_method, helo, user, secret, authtype)
|
25
|
+
return self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def do_tls_start(helodomain, user, secret, authtype)
|
32
|
+
raise IOError, 'SMTP session already started' if @started
|
33
|
+
if RUBY_VERSION == '1.8.6'
|
34
|
+
check_auth_args(user, secret, authtype) if user or secret
|
35
|
+
else
|
36
|
+
check_auth_args(user, secret) if user or secret
|
37
|
+
end
|
38
|
+
|
39
|
+
sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
|
40
|
+
@socket = Net::InternetMessageIO.new(sock)
|
41
|
+
@socket.read_timeout = 60 #@read_timeout
|
42
|
+
@socket.debug_output = STDERR #@debug_output
|
43
|
+
|
44
|
+
check_response(critical { recv_response() })
|
45
|
+
do_helo(helodomain)
|
46
|
+
|
47
|
+
raise 'openssl library not installed' unless defined?(OpenSSL)
|
48
|
+
starttls
|
49
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock)
|
50
|
+
ssl.sync_close = true
|
51
|
+
ssl.connect
|
52
|
+
@socket = Net::InternetMessageIO.new(ssl)
|
53
|
+
@socket.read_timeout = 60 #@read_timeout
|
54
|
+
@socket.debug_output = STDERR #@debug_output
|
55
|
+
do_helo(helodomain)
|
56
|
+
|
57
|
+
authenticate user, secret, authtype if user
|
58
|
+
@started = true
|
59
|
+
ensure
|
60
|
+
unless @started
|
61
|
+
# authentication failed, cancel connection.
|
62
|
+
@socket.close if not @started and @socket and not @socket.closed?
|
63
|
+
@socket = nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def do_helo(helodomain)
|
68
|
+
begin
|
69
|
+
if @esmtp
|
70
|
+
ehlo helodomain
|
71
|
+
else
|
72
|
+
helo helodomain
|
73
|
+
end
|
74
|
+
rescue Net::ProtocolError
|
75
|
+
if @esmtp
|
76
|
+
@esmtp = false
|
77
|
+
@error_occured = false
|
78
|
+
retry
|
79
|
+
end
|
80
|
+
raise
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def starttls
|
85
|
+
getok('STARTTLS')
|
86
|
+
end
|
87
|
+
|
88
|
+
def quit
|
89
|
+
begin
|
90
|
+
getok('QUIT')
|
91
|
+
rescue EOFError, OpenSSL::SSL::SSLError
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/ruby-gmail.gemspec
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{pauldix-ruby-gmail}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["BehindLogic"]
|
12
|
+
s.date = %q{2010-07-01}
|
13
|
+
s.description = %q{A Rubyesque interface to Gmail, with all the tools you'll need. Search, read and send multipart emails; archive, mark as read/unread, delete emails; and manage labels.}
|
14
|
+
s.email = %q{gems@behindlogic.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.markdown"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".autotest",
|
20
|
+
".gitignore",
|
21
|
+
"History.txt",
|
22
|
+
"Manifest.txt",
|
23
|
+
"README.markdown",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/gmail.rb",
|
27
|
+
"lib/gmail/mailbox.rb",
|
28
|
+
"lib/gmail/message.rb",
|
29
|
+
"lib/smtp_tls.rb",
|
30
|
+
"ruby-gmail.gemspec",
|
31
|
+
"test/test_gmail.rb",
|
32
|
+
"test/test_helper.rb"
|
33
|
+
]
|
34
|
+
s.homepage = %q{http://dcparker.github.com/ruby-gmail}
|
35
|
+
s.post_install_message = %q{
|
36
|
+
[34mIf ruby-gmail saves you TWO hours of work, want to compensate me for, like, a half-hour?
|
37
|
+
Support me in making new and better gems:[0m [31;4mhttp://pledgie.com/campaigns/7087[0m
|
38
|
+
|
39
|
+
}
|
40
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = %q{1.3.6}
|
43
|
+
s.summary = %q{A Rubyesque interface to Gmail, with all the tools you'll need.}
|
44
|
+
s.test_files = [
|
45
|
+
"test/test_gmail.rb",
|
46
|
+
"test/test_helper.rb"
|
47
|
+
]
|
48
|
+
|
49
|
+
if s.respond_to? :specification_version then
|
50
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
51
|
+
s.specification_version = 3
|
52
|
+
|
53
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
54
|
+
s.add_runtime_dependency(%q<shared-mime-info>, [">= 0"])
|
55
|
+
s.add_runtime_dependency(%q<mail>, [">= 2.2.1"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<shared-mime-info>, [">= 0"])
|
58
|
+
s.add_dependency(%q<mail>, [">= 2.2.1"])
|
59
|
+
end
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<shared-mime-info>, [">= 0"])
|
62
|
+
s.add_dependency(%q<mail>, [">= 2.2.1"])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/test/test_gmail.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class GmailTest < Test::Unit::TestCase
|
4
|
+
def test_initialize
|
5
|
+
imap = mock('imap')
|
6
|
+
Net::IMAP.expects(:new).with('imap.gmail.com', 993, true, nil, false).returns(imap)
|
7
|
+
gmail = Gmail.new('test', 'password')
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_imap_does_login
|
11
|
+
setup_mocks(:at_exit => true)
|
12
|
+
|
13
|
+
@imap.expects(:disconnected?).at_least_once.returns(true).then.returns(false)
|
14
|
+
@imap.expects(:login).with('test@gmail.com', 'password')
|
15
|
+
@gmail.imap
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_imap_does_login_only_once
|
19
|
+
setup_mocks(:at_exit => true)
|
20
|
+
|
21
|
+
@imap.expects(:disconnected?).at_least_once.returns(true).then.returns(false)
|
22
|
+
@imap.expects(:login).with('test@gmail.com', 'password')
|
23
|
+
@gmail.imap
|
24
|
+
@gmail.imap
|
25
|
+
@gmail.imap
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_imap_does_login_without_appending_gmail_domain
|
29
|
+
setup_mocks(:at_exit => true)
|
30
|
+
|
31
|
+
@imap.expects(:disconnected?).at_least_once.returns(true).then.returns(false)
|
32
|
+
@imap.expects(:login).with('test@gmail.com', 'password')
|
33
|
+
@gmail.imap
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_imap_logs_out
|
37
|
+
setup_mocks(:at_exit => true)
|
38
|
+
|
39
|
+
@imap.expects(:disconnected?).at_least_once.returns(true).then.returns(false)
|
40
|
+
@imap.expects(:login).with('test@gmail.com', 'password')
|
41
|
+
@gmail.imap
|
42
|
+
@imap.expects(:logout).returns(true)
|
43
|
+
@gmail.logout
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_imap_logout_does_nothing_if_not_logged_in
|
47
|
+
setup_mocks
|
48
|
+
|
49
|
+
@imap.expects(:disconnected?).returns(true)
|
50
|
+
@imap.expects(:logout).never
|
51
|
+
@gmail.logout
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_imap_calls_create_label
|
55
|
+
setup_mocks(:at_exit => true)
|
56
|
+
@imap.expects(:disconnected?).at_least_once.returns(true).then.returns(false)
|
57
|
+
@imap.expects(:login).with('test@gmail.com', 'password')
|
58
|
+
@imap.expects(:create).with('foo')
|
59
|
+
@gmail.create_label('foo')
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def setup_mocks(options = {})
|
64
|
+
options = {:at_exit => false}.merge(options)
|
65
|
+
@imap = mock('imap')
|
66
|
+
Net::IMAP.expects(:new).with('imap.gmail.com', 993, true, nil, false).returns(@imap)
|
67
|
+
@gmail = Gmail.new('test@gmail.com', 'password')
|
68
|
+
|
69
|
+
# need this for the at_exit block that auto-exits after this test method completes
|
70
|
+
@imap.expects(:logout).at_least(0) if options[:at_exit]
|
71
|
+
end
|
72
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pauldix-ruby-gmail
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- BehindLogic
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-01 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: shared-mime-info
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: mail
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 2
|
41
|
+
- 2
|
42
|
+
- 1
|
43
|
+
version: 2.2.1
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
description: A Rubyesque interface to Gmail, with all the tools you'll need. Search, read and send multipart emails; archive, mark as read/unread, delete emails; and manage labels.
|
47
|
+
email: gems@behindlogic.com
|
48
|
+
executables: []
|
49
|
+
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
extra_rdoc_files:
|
53
|
+
- README.markdown
|
54
|
+
files:
|
55
|
+
- .autotest
|
56
|
+
- .gitignore
|
57
|
+
- History.txt
|
58
|
+
- Manifest.txt
|
59
|
+
- README.markdown
|
60
|
+
- Rakefile
|
61
|
+
- VERSION
|
62
|
+
- lib/gmail.rb
|
63
|
+
- lib/gmail/mailbox.rb
|
64
|
+
- lib/gmail/message.rb
|
65
|
+
- lib/smtp_tls.rb
|
66
|
+
- ruby-gmail.gemspec
|
67
|
+
- test/test_gmail.rb
|
68
|
+
- test/test_helper.rb
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: http://dcparker.github.com/ruby-gmail
|
71
|
+
licenses: []
|
72
|
+
|
73
|
+
post_install_message: "\n\
|
74
|
+
\e[34mIf ruby-gmail saves you TWO hours of work, want to compensate me for, like, a half-hour?\n\
|
75
|
+
Support me in making new and better gems:\e[0m \e[31;4mhttp://pledgie.com/campaigns/7087\e[0m\n\n"
|
76
|
+
rdoc_options:
|
77
|
+
- --charset=UTF-8
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.3.6
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: A Rubyesque interface to Gmail, with all the tools you'll need.
|
101
|
+
test_files:
|
102
|
+
- test/test_gmail.rb
|
103
|
+
- test/test_helper.rb
|