laco-ruby-gmail 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gem 'jeweler'
4
+ gem 'shared-mime-info'
5
+ gem 'mime'
6
+ gem 'mail', '~>2.2.1'
7
+
8
+ group :test do
9
+ gem 'mocha'
10
+ end
@@ -0,0 +1,34 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.0.3)
5
+ git (1.2.5)
6
+ i18n (0.4.2)
7
+ jeweler (1.5.1)
8
+ bundler (~> 1.0.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ mail (2.2.10)
12
+ activesupport (>= 2.3.6)
13
+ i18n (~> 0.4.1)
14
+ mime-types (~> 1.16)
15
+ treetop (~> 1.4.8)
16
+ mime (0.1)
17
+ mime-types (1.16)
18
+ mocha (0.9.9)
19
+ rake
20
+ polyglot (0.3.1)
21
+ rake (0.8.7)
22
+ shared-mime-info (0.1)
23
+ treetop (1.4.9)
24
+ polyglot (>= 0.3.1)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ jeweler
31
+ mail (~> 2.2.1)
32
+ mime
33
+ mocha
34
+ shared-mime-info
@@ -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
+
@@ -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
@@ -0,0 +1,182 @@
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
+ * mime
145
+
146
+ ## Install
147
+
148
+ gem install ruby-gmail
149
+
150
+ ## Contribute
151
+
152
+ gem install bundler
153
+ bundle install
154
+
155
+ Fix bug/Add feature
156
+
157
+ bundle exec ruby test/test_gmail.rb
158
+
159
+ ## License
160
+
161
+ (The MIT License)
162
+
163
+ Copyright (c) 2009 BehindLogic
164
+
165
+ Permission is hereby granted, free of charge, to any person obtaining
166
+ a copy of this software and associated documentation files (the
167
+ 'Software'), to deal in the Software without restriction, including
168
+ without limitation the rights to use, copy, modify, merge, publish,
169
+ distribute, sublicense, and/or sell copies of the Software, and to
170
+ permit persons to whom the Software is furnished to do so, subject to
171
+ the following conditions:
172
+
173
+ The above copyright notice and this permission notice shall be
174
+ included in all copies or substantial portions of the Software.
175
+
176
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
177
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
178
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
179
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
180
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
181
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
182
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "laco-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", "Ladislav Martincik"]
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.add_dependency('mime', '>= 0.1')
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{laco-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", "Ladislav Martincik"]
12
+ s.date = %q{2010-11-18}
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
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "History.txt",
23
+ "Manifest.txt",
24
+ "README.markdown",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "laco-ruby-gmail.gemspec",
28
+ "lib/gmail.rb",
29
+ "lib/gmail/mailbox.rb",
30
+ "lib/gmail/message.rb",
31
+ "lib/smtp_tls.rb",
32
+ "test/test_gmail.rb",
33
+ "test/test_helper.rb"
34
+ ]
35
+ s.homepage = %q{http://dcparker.github.com/ruby-gmail}
36
+ s.post_install_message = %q{
37
+ If ruby-gmail saves you TWO hours of work, want to compensate me for, like, a half-hour?
38
+ Support me in making new and better gems: http://pledgie.com/campaigns/7087
39
+
40
+ }
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.7}
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::VERSION) >= Gem::Version.new('1.2.0') then
54
+ s.add_runtime_dependency(%q<jeweler>, [">= 0"])
55
+ s.add_runtime_dependency(%q<shared-mime-info>, [">= 0"])
56
+ s.add_runtime_dependency(%q<mime>, [">= 0"])
57
+ s.add_runtime_dependency(%q<mail>, ["~> 2.2.1"])
58
+ s.add_runtime_dependency(%q<shared-mime-info>, [">= 0"])
59
+ s.add_runtime_dependency(%q<mail>, [">= 2.2.1"])
60
+ s.add_runtime_dependency(%q<mime>, [">= 0.1"])
61
+ else
62
+ s.add_dependency(%q<jeweler>, [">= 0"])
63
+ s.add_dependency(%q<shared-mime-info>, [">= 0"])
64
+ s.add_dependency(%q<mime>, [">= 0"])
65
+ s.add_dependency(%q<mail>, ["~> 2.2.1"])
66
+ s.add_dependency(%q<shared-mime-info>, [">= 0"])
67
+ s.add_dependency(%q<mail>, [">= 2.2.1"])
68
+ s.add_dependency(%q<mime>, [">= 0.1"])
69
+ end
70
+ else
71
+ s.add_dependency(%q<jeweler>, [">= 0"])
72
+ s.add_dependency(%q<shared-mime-info>, [">= 0"])
73
+ s.add_dependency(%q<mime>, [">= 0"])
74
+ s.add_dependency(%q<mail>, ["~> 2.2.1"])
75
+ s.add_dependency(%q<shared-mime-info>, [">= 0"])
76
+ s.add_dependency(%q<mail>, [">= 2.2.1"])
77
+ s.add_dependency(%q<mime>, [">= 0.1"])
78
+ end
79
+ end
80
+
@@ -0,0 +1,166 @@
1
+ require 'net/imap'
2
+
3
+ class Gmail
4
+ class NoLabel < RuntimeError; end
5
+
6
+ ##################################
7
+ # Gmail.new(username, password)
8
+ ##################################
9
+ def initialize(username, password)
10
+ # This is to hide the username and password, not like it REALLY needs hiding, but ... you know.
11
+ # Could be helpful when demoing the gem in irb, these bits won't show up that way.
12
+ class << self
13
+ class << self
14
+ attr_accessor :username, :password
15
+ end
16
+ end
17
+ meta.username = username =~ /@/ ? username : username + '@gmail.com'
18
+ meta.password = password
19
+ @imap = Net::IMAP.new('imap.gmail.com',993,true,nil,false)
20
+ if block_given?
21
+ login # This is here intentionally. Normally, we get auto logged-in when first needed.
22
+ yield self
23
+ logout
24
+ end
25
+ end
26
+
27
+ ###########################
28
+ # READING EMAILS
29
+ #
30
+ # gmail.inbox
31
+ # gmail.label('News')
32
+ #
33
+ ###########################
34
+
35
+ def inbox
36
+ in_label('inbox')
37
+ end
38
+
39
+ def create_label(name)
40
+ imap.create(name)
41
+ end
42
+
43
+ # List the available labels
44
+ def labels
45
+ (imap.list("", "%") + imap.list("[Gmail]/", "%")).inject([]) { |labels,label|
46
+ label[:name].each_line { |l| labels << l }; labels }
47
+ end
48
+
49
+ # gmail.label(name)
50
+ def label(name)
51
+ mailboxes[name] ||= Mailbox.new(self, name)
52
+ end
53
+ alias :mailbox :label
54
+
55
+ ###########################
56
+ # MAKING EMAILS
57
+ #
58
+ # gmail.generate_message do
59
+ # ...inside Mail context...
60
+ # end
61
+ #
62
+ # gmail.deliver do ... end
63
+ #
64
+ # mail = Mail.new...
65
+ # gmail.deliver!(mail)
66
+ ###########################
67
+ def generate_message(&block)
68
+ require 'net/smtp'
69
+ require 'smtp_tls'
70
+ require 'mail'
71
+ mail = Mail.new(&block)
72
+ mail.delivery_method(*smtp_settings)
73
+ mail
74
+ end
75
+
76
+ def deliver(mail=nil, &block)
77
+ require 'net/smtp'
78
+ require 'smtp_tls'
79
+ require 'mail'
80
+ mail = Mail.new(&block) if block_given?
81
+ mail.delivery_method(*smtp_settings)
82
+ mail.from = meta.username unless mail.from
83
+ mail.deliver!
84
+ end
85
+
86
+ ###########################
87
+ # LOGIN
88
+ ###########################
89
+ def login
90
+ res = @imap.login(meta.username, meta.password)
91
+ @logged_in = true if res && res.name == 'OK'
92
+ end
93
+ def logged_in?
94
+ !!@logged_in
95
+ end
96
+ # Log out of gmail
97
+ def logout
98
+ if logged_in?
99
+ res = @imap.logout
100
+ @logged_in = false if res.name == 'OK'
101
+ end
102
+ end
103
+
104
+ def in_mailbox(mailbox, &block)
105
+ if block_given?
106
+ mailbox_stack << mailbox
107
+ unless @selected == mailbox.name
108
+ imap.select(mailbox.name)
109
+ @selected = mailbox.name
110
+ end
111
+ value = block.arity == 1 ? block.call(mailbox) : block.call
112
+ mailbox_stack.pop
113
+ # Select previously selected mailbox if there is one
114
+ if mailbox_stack.last
115
+ imap.select(mailbox_stack.last.name)
116
+ @selected = mailbox.name
117
+ end
118
+ return value
119
+ else
120
+ mailboxes[name] ||= Mailbox.new(self, mailbox)
121
+ end
122
+ end
123
+ alias :in_label :in_mailbox
124
+
125
+ ###########################
126
+ # Other...
127
+ ###########################
128
+ def inspect
129
+ "#<Gmail:#{'0x%x' % (object_id << 1)} (#{meta.username}) #{'dis' if !logged_in?}connected>"
130
+ end
131
+
132
+ # Accessor for @imap, but ensures that it's logged in first.
133
+ def imap
134
+ unless logged_in?
135
+ login
136
+ at_exit { logout } # Set up auto-logout for later.
137
+ end
138
+ @imap
139
+ end
140
+
141
+ private
142
+ def mailboxes
143
+ @mailboxes ||= {}
144
+ end
145
+ def mailbox_stack
146
+ @mailbox_stack ||= []
147
+ end
148
+ def meta
149
+ class << self; self end
150
+ end
151
+ def domain
152
+ meta.username.split('@')[0]
153
+ end
154
+ def smtp_settings
155
+ [:smtp, {:address => "smtp.gmail.com",
156
+ :port => 587,
157
+ :domain => domain,
158
+ :user_name => meta.username,
159
+ :password => meta.password,
160
+ :authentication => 'plain',
161
+ :enable_starttls_auto => true}]
162
+ end
163
+ end
164
+
165
+ require 'gmail/mailbox'
166
+ 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
@@ -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
@@ -0,0 +1,70 @@
1
+ require 'test_helper'
2
+
3
+ class GmailTest < Test::Unit::TestCase
4
+ def setup
5
+ @res_ok = OpenStruct.new(:name => 'OK')
6
+ end
7
+
8
+ def test_initialize
9
+ imap = mock('imap')
10
+ Net::IMAP.expects(:new).with('imap.gmail.com', 993, true, nil, false).returns(imap)
11
+ gmail = Gmail.new('test', 'password')
12
+ end
13
+
14
+ def test_imap_does_login
15
+ setup_mocks(:at_exit => true)
16
+
17
+ @imap.expects(:login).with('test@gmail.com', 'password').returns(@res_ok)
18
+ @gmail.imap
19
+ end
20
+
21
+ def test_imap_does_login_only_once
22
+ setup_mocks(:at_exit => true)
23
+
24
+ @imap.expects(:login).with('test@gmail.com', 'password').returns(@res_ok)
25
+ @gmail.imap
26
+ @gmail.imap
27
+ @gmail.imap
28
+ end
29
+
30
+ def test_imap_does_login_without_appending_gmail_domain
31
+ setup_mocks(:at_exit => true)
32
+
33
+ @imap.expects(:login).with('test@gmail.com', 'password').returns(@res_ok)
34
+ @gmail.imap
35
+ end
36
+
37
+ def test_imap_logs_out
38
+ setup_mocks(:at_exit => true)
39
+
40
+ @imap.expects(:login).with('test@gmail.com', 'password').returns(@res_ok)
41
+ @gmail.imap
42
+ @imap.expects(:logout).returns(@res_ok)
43
+ @gmail.logout
44
+ end
45
+
46
+ def test_imap_logout_does_nothing_if_not_logged_in
47
+ setup_mocks
48
+
49
+ @imap.expects(:logout).never
50
+ @gmail.logout
51
+ end
52
+
53
+ def test_imap_calls_create_label
54
+ setup_mocks(:at_exit => true)
55
+ @imap.expects(:login).with('test@gmail.com', 'password').returns(@res_ok)
56
+ @imap.expects(:create).with('foo')
57
+ @gmail.create_label('foo')
58
+ end
59
+
60
+ private
61
+ def setup_mocks(options = {})
62
+ options = {:at_exit => false}.merge(options)
63
+ @imap = mock('imap')
64
+ Net::IMAP.expects(:new).with('imap.gmail.com', 993, true, nil, false).returns(@imap)
65
+ @gmail = Gmail.new('test@gmail.com', 'password')
66
+
67
+ # need this for the at_exit block that auto-exits after this test method completes
68
+ @imap.expects(:logout).at_least(0).returns(@res_ok) if options[:at_exit]
69
+ end
70
+ end
@@ -0,0 +1,7 @@
1
+ $: << File.dirname( __FILE__ ) + '/../lib'
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'mocha'
6
+ require 'gmail'
7
+ require 'ostruct'
metadata ADDED
@@ -0,0 +1,187 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: laco-ruby-gmail
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - BehindLogic
14
+ - Ladislav Martincik
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-11-18 00:00:00 +01:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ type: :runtime
24
+ prerelease: false
25
+ name: jeweler
26
+ version_requirements: &id001 !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ hash: 3
32
+ segments:
33
+ - 0
34
+ version: "0"
35
+ requirement: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ type: :runtime
38
+ prerelease: false
39
+ name: shared-mime-info
40
+ version_requirements: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ requirement: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ type: :runtime
52
+ prerelease: false
53
+ name: mime
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirement: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ type: :runtime
66
+ prerelease: false
67
+ name: mail
68
+ version_requirements: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ hash: 5
74
+ segments:
75
+ - 2
76
+ - 2
77
+ - 1
78
+ version: 2.2.1
79
+ requirement: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ type: :runtime
82
+ prerelease: false
83
+ name: shared-mime-info
84
+ version_requirements: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirement: *id005
94
+ - !ruby/object:Gem::Dependency
95
+ type: :runtime
96
+ prerelease: false
97
+ name: mail
98
+ version_requirements: &id006 !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 5
104
+ segments:
105
+ - 2
106
+ - 2
107
+ - 1
108
+ version: 2.2.1
109
+ requirement: *id006
110
+ - !ruby/object:Gem::Dependency
111
+ type: :runtime
112
+ prerelease: false
113
+ name: mime
114
+ version_requirements: &id007 !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 9
120
+ segments:
121
+ - 0
122
+ - 1
123
+ version: "0.1"
124
+ requirement: *id007
125
+ 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.
126
+ email: gems@behindlogic.com
127
+ executables: []
128
+
129
+ extensions: []
130
+
131
+ extra_rdoc_files:
132
+ - README.markdown
133
+ files:
134
+ - .autotest
135
+ - Gemfile
136
+ - Gemfile.lock
137
+ - History.txt
138
+ - Manifest.txt
139
+ - README.markdown
140
+ - Rakefile
141
+ - VERSION
142
+ - laco-ruby-gmail.gemspec
143
+ - lib/gmail.rb
144
+ - lib/gmail/mailbox.rb
145
+ - lib/gmail/message.rb
146
+ - lib/smtp_tls.rb
147
+ - test/test_gmail.rb
148
+ - test/test_helper.rb
149
+ has_rdoc: true
150
+ homepage: http://dcparker.github.com/ruby-gmail
151
+ licenses: []
152
+
153
+ post_install_message: "\n\
154
+ \e[34mIf ruby-gmail saves you TWO hours of work, want to compensate me for, like, a half-hour?\n\
155
+ Support me in making new and better gems:\e[0m \e[31;4mhttp://pledgie.com/campaigns/7087\e[0m\n\n"
156
+ rdoc_options: []
157
+
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ hash: 3
166
+ segments:
167
+ - 0
168
+ version: "0"
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ hash: 3
175
+ segments:
176
+ - 0
177
+ version: "0"
178
+ requirements: []
179
+
180
+ rubyforge_project:
181
+ rubygems_version: 1.3.7
182
+ signing_key:
183
+ specification_version: 3
184
+ summary: A Rubyesque interface to Gmail, with all the tools you'll need.
185
+ test_files:
186
+ - test/test_gmail.rb
187
+ - test/test_helper.rb