gmail-afurmanov 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,117 @@
1
+ module Gmail
2
+ class Mailbox
3
+ MAILBOX_ALIASES = {
4
+ :all => ['ALL'],
5
+ :seen => ['SEEN'],
6
+ :unseen => ['UNSEEN'],
7
+ :read => ['SEEN'],
8
+ :unread => ['UNSEEN'],
9
+ :flagged => ['FLAGGED'],
10
+ :unflagged => ['UNFLAGGED'],
11
+ :starred => ['FLAGGED'],
12
+ :unstarred => ['UNFLAGGED'],
13
+ :deleted => ['DELETED'],
14
+ :undeleted => ['UNDELETED'],
15
+ :draft => ['DRAFT'],
16
+ :undrafted => ['UNDRAFT']
17
+ }
18
+
19
+ attr_reader :name
20
+ attr_reader :external_name
21
+
22
+ def initialize(gmail, name="INBOX")
23
+ @name = name
24
+ @external_name = Net::IMAP.decode_utf7(name)
25
+ @gmail = gmail
26
+ end
27
+
28
+ # Returns list of emails which meets given criteria.
29
+ #
30
+ # ==== Examples
31
+ #
32
+ # gmail.inbox.emails(:all)
33
+ # gmail.inbox.emails(:unread, :from => "friend@gmail.com")
34
+ # gmail.inbox.emails(:all, :after => Time.now-(20*24*3600))
35
+ # gmail.mailbox("Test").emails(:read)
36
+ #
37
+ # gmail.mailbox("Test") do |box|
38
+ # box.emails(:read)
39
+ # box.emails(:unread) do |email|
40
+ # ... do something with each email...
41
+ # end
42
+ # end
43
+ def emails(*args, &block)
44
+ args << :all if args.size == 0
45
+
46
+ if args.first.is_a?(Symbol)
47
+ search = MAILBOX_ALIASES[args.shift].dup
48
+ opts = args.first.is_a?(Hash) ? args.first : {}
49
+
50
+ opts[:after] and search.concat ['SINCE', opts[:after].to_imap_date]
51
+ opts[:before] and search.concat ['BEFORE', opts[:before].to_imap_date]
52
+ opts[:on] and search.concat ['ON', opts[:on].to_imap_date]
53
+ opts[:from] and search.concat ['FROM', opts[:from]]
54
+ opts[:to] and search.concat ['TO', opts[:to]]
55
+ opts[:subject] and search.concat ['SUBJECT', opts[:subject]]
56
+ opts[:label] and search.concat ['LABEL', opts[:label]]
57
+ opts[:attachment] and search.concat ['HAS', 'attachment']
58
+ opts[:search] and search.concat ['BODY', opts[:search]]
59
+ opts[:body] and search.concat ['BODY', opts[:body]]
60
+ opts[:query] and search.concat opts[:query]
61
+
62
+ @gmail.mailbox(name) do
63
+ @gmail.conn.uid_search(search).collect do |uid|
64
+ message = (messages[uid] ||= Message.new(self, uid))
65
+ block.call(message) if block_given?
66
+ message
67
+ end
68
+ end
69
+ elsif args.first.is_a?(Hash)
70
+ emails(:all, args.first)
71
+ else
72
+ raise ArgumentError, "Invalid search criteria"
73
+ end
74
+ end
75
+ alias :mails :emails
76
+ alias :search :emails
77
+ alias :find :emails
78
+ alias :filter :emails
79
+
80
+ # This is a convenience method that really probably shouldn't need to exist,
81
+ # but it does make code more readable, if seriously all you want is the count
82
+ # of messages.
83
+ #
84
+ # ==== Examples
85
+ #
86
+ # gmail.inbox.count(:all)
87
+ # gmail.inbox.count(:unread, :from => "friend@gmail.com")
88
+ # gmail.mailbox("Test").count(:all, :after => Time.now-(20*24*3600))
89
+ def count(*args)
90
+ emails(*args).size
91
+ end
92
+
93
+ # This permanently removes messages which are marked as deleted
94
+ def expunge
95
+ @gmail.mailbox(name) { @gmail.conn.expunge }
96
+ end
97
+
98
+ # Cached messages.
99
+ def messages
100
+ @messages ||= {}
101
+ end
102
+
103
+ def inspect
104
+ "#<Gmail::Mailbox#{'0x%04x' % (object_id << 1)} name=#{external_name}>"
105
+ end
106
+
107
+ def to_s
108
+ name
109
+ end
110
+
111
+ MAILBOX_ALIASES.each_key { |mailbox|
112
+ define_method(mailbox) do |*args, &block|
113
+ emails(mailbox, *args, &block)
114
+ end
115
+ }
116
+ end # Message
117
+ end # Gmail
@@ -0,0 +1,164 @@
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 / bin.
68
+ def delete!
69
+ @mailbox.messages.delete(uid)
70
+ flag(:deleted)
71
+
72
+ # For some, it's called "Trash", for others, it's called "Bin". Support both.
73
+ trash = @gmail.labels.exist?('[Gmail]/Bin') ? '[Gmail]/Bin' : '[Gmail]/Trash'
74
+ move_to(trash) unless %w[[Gmail]/Spam [Gmail]/Bin [Gmail]/Trash].include?(@mailbox.name)
75
+ end
76
+
77
+ # Archive this message.
78
+ def archive!
79
+ move_to('[Gmail]/All Mail')
80
+ end
81
+
82
+ # Move to given box and delete from others.
83
+ def move_to(name, from=nil)
84
+ label(name, from)
85
+ delete! if !%w[[Gmail]/Bin [Gmail]/Trash].include?(name)
86
+ end
87
+ alias :move :move_to
88
+
89
+ # Move message to given and delete from others. When given mailbox doesn't
90
+ # exist then it will be automaticaly created.
91
+ def move_to!(name, from=nil)
92
+ label!(name, from) && delete!
93
+ end
94
+ alias :move! :move_to!
95
+
96
+ # Mark this message with given label. When given label doesn't exist then
97
+ # it will raise <tt>NoLabelError</tt>.
98
+ #
99
+ # See also <tt>Gmail::Message#label!</tt>.
100
+ def label(name, from=nil)
101
+ @gmail.mailbox(Net::IMAP.encode_utf7(from || @mailbox.external_name)) { @gmail.conn.uid_copy(uid, Net::IMAP.encode_utf7(name)) }
102
+ rescue Net::IMAP::NoResponseError
103
+ raise NoLabelError, "Label '#{name}' doesn't exist!"
104
+ end
105
+
106
+ # Mark this message with given label. When given label doesn't exist then
107
+ # it will be automaticaly created.
108
+ #
109
+ # See also <tt>Gmail::Message#label</tt>.
110
+ def label!(name, from=nil)
111
+ label(name, from)
112
+ rescue NoLabelError
113
+ @gmail.labels.add(Net::IMAP.encode_utf7(name))
114
+ label(name, from)
115
+ end
116
+ alias :add_label :label!
117
+ alias :add_label! :label!
118
+
119
+ # Remove given label from this message.
120
+ def remove_label!(name)
121
+ move_to('[Gmail]/All Mail', name)
122
+ end
123
+ alias :delete_label! :remove_label!
124
+
125
+ def inspect
126
+ "#<Gmail::Message#{'0x%04x' % (object_id << 1)} mailbox=#{@mailbox.external_name}#{' uid='+@uid.to_s if @uid}#{' message_id='+@message_id.to_s if @message_id}>"
127
+ end
128
+
129
+ def method_missing(meth, *args, &block)
130
+ # Delegate rest directly to the message.
131
+ if envelope.respond_to?(meth)
132
+ envelope.send(meth, *args, &block)
133
+ elsif message.respond_to?(meth)
134
+ message.send(meth, *args, &block)
135
+ else
136
+ super(meth, *args, &block)
137
+ end
138
+ end
139
+
140
+ def respond_to?(meth, *args, &block)
141
+ if envelope.respond_to?(meth)
142
+ return true
143
+ elsif message.respond_to?(meth)
144
+ return true
145
+ else
146
+ super(meth, *args, &block)
147
+ end
148
+ end
149
+
150
+ def envelope
151
+ @envelope ||= @gmail.mailbox(@mailbox.name) {
152
+ @gmail.conn.uid_fetch(uid, "ENVELOPE")[0].attr["ENVELOPE"]
153
+ }
154
+ end
155
+
156
+ def message
157
+ @message ||= Mail.new(@gmail.mailbox(@mailbox.name) {
158
+ @gmail.conn.uid_fetch(uid, "RFC822")[0].attr["RFC822"] # RFC822
159
+ })
160
+ end
161
+ alias_method :raw_message, :message
162
+
163
+ end # Message
164
+ end # Gmail
@@ -0,0 +1,12 @@
1
+ module Gmail
2
+ class Version #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 4
5
+ PATCH = 0
6
+ STRING = [MAJOR, MINOR, PATCH].join('.')
7
+ end # Version
8
+
9
+ def self.version # :nodoc:
10
+ Version::STRING
11
+ end
12
+ end # Gmail
@@ -0,0 +1,2 @@
1
+ - "test@gmail.com"
2
+ - "test"
@@ -0,0 +1,173 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Gmail client (Plain)" do
4
+ subject { Gmail::Client::Plain }
5
+
6
+ context "on initialize" do
7
+ it "should set username, password and options" do
8
+ client = subject.new("test@gmail.com", "pass", :foo => :bar)
9
+ client.username.should == "test@gmail.com"
10
+ client.password.should == "pass"
11
+ client.options[:foo].should == :bar
12
+ end
13
+
14
+ it "should convert simple name to gmail email" do
15
+ client = subject.new("test", "pass")
16
+ client.username.should == "test@gmail.com"
17
+ end
18
+ end
19
+
20
+ context "instance" do
21
+ def mock_client(&block)
22
+ client = Gmail::Client::Plain.new(*TEST_ACCOUNT)
23
+ if block_given?
24
+ client.connect
25
+ yield client
26
+ client.logout
27
+ end
28
+ client
29
+ end
30
+
31
+ it "should connect to GMail IMAP service" do
32
+ lambda {
33
+ client = mock_client
34
+ client.connect!.should be_true
35
+ }.should_not raise_error(Gmail::Client::ConnectionError)
36
+ end
37
+
38
+ it "should properly login to valid GMail account" do
39
+ client = mock_client
40
+ client.connect.should be_true
41
+ client.login.should be_true
42
+ client.should be_logged_in
43
+ client.logout
44
+ end
45
+
46
+ it "should raise error when given GMail account is invalid and errors enabled" do
47
+ lambda {
48
+ client = Gmail::Client::Plain.new("foo", "bar")
49
+ client.connect.should be_true
50
+ client.login!.should_not be_true
51
+ }.should raise_error(Gmail::Client::AuthorizationError)
52
+ end
53
+
54
+ it "shouldn't login when given GMail account is invalid" do
55
+ lambda {
56
+ client = Gmail::Client::Plain.new("foo", "bar")
57
+ client.connect.should be_true
58
+ client.login.should_not be_true
59
+ }.should_not raise_error(Gmail::Client::AuthorizationError)
60
+ end
61
+
62
+ it "should properly logout from GMail" do
63
+ client = mock_client
64
+ client.connect
65
+ client.login.should be_true
66
+ client.logout.should be_true
67
+ client.should_not be_logged_in
68
+ end
69
+
70
+ it "#connection should automatically log in to GMail account when it's called" do
71
+ mock_client do |client|
72
+ client.expects(:login).once.returns(false)
73
+ client.connection.should_not be_nil
74
+ end
75
+ end
76
+
77
+ it "should properly compose message" do
78
+ mail = mock_client.compose do
79
+ from "test@gmail.com"
80
+ to "friend@gmail.com"
81
+ subject "Hello world!"
82
+ end
83
+ mail.from.should == ["test@gmail.com"]
84
+ mail.to.should == ["friend@gmail.com"]
85
+ mail.subject.should == "Hello world!"
86
+ end
87
+
88
+ it "#compose should automatically add `from` header when it is not specified" do
89
+ mail = mock_client.compose
90
+ mail.from.should == [TEST_ACCOUNT[0]]
91
+ mail = mock_client.compose(Mail.new)
92
+ mail.from.should == [TEST_ACCOUNT[0]]
93
+ mail = mock_client.compose {}
94
+ mail.from.should == [TEST_ACCOUNT[0]]
95
+ end
96
+
97
+ it "should deliver inline composed email" do
98
+ mock_client do |client|
99
+ client.deliver do
100
+ to TEST_ACCOUNT[0]
101
+ subject "Hello world!"
102
+ body "Yeah, hello there!"
103
+ end.should be_true
104
+ end
105
+ end
106
+
107
+ it "should not raise error when mail can't be delivered and errors are disabled" do
108
+ lambda {
109
+ client = mock_client
110
+ client.deliver(Mail.new {}).should be_false
111
+ }.should_not raise_error(Gmail::Client::DeliveryError)
112
+ end
113
+
114
+ it "should raise error when mail can't be delivered and errors are disabled" do
115
+ lambda {
116
+ client = mock_client
117
+ client.deliver!(Mail.new {})
118
+ }.should raise_error(Gmail::Client::DeliveryError)
119
+ end
120
+
121
+ it "should properly switch to given mailbox" do
122
+ mock_client do |client|
123
+ mailbox = client.mailbox("TEST")
124
+ mailbox.should be_kind_of(Gmail::Mailbox)
125
+ mailbox.name.should == "TEST"
126
+ end
127
+ end
128
+
129
+ it "should properly switch to given mailbox using block style" do
130
+ mock_client do |client|
131
+ client.mailbox("TEST") do |mailbox|
132
+ mailbox.should be_kind_of(Gmail::Mailbox)
133
+ mailbox.name.should == "TEST"
134
+ end
135
+ end
136
+ end
137
+
138
+ context "labels" do
139
+ subject {
140
+ client = Gmail::Client::Plain.new(*TEST_ACCOUNT)
141
+ client.connect
142
+ client.labels
143
+ }
144
+
145
+ it "should get list of all available labels" do
146
+ labels = subject
147
+ labels.all.should include("TEST", "INBOX")
148
+ end
149
+
150
+ it "should be able to check if there is given label defined" do
151
+ labels = subject
152
+ labels.exists?("TEST").should be_true
153
+ labels.exists?("FOOBAR").should be_false
154
+ end
155
+
156
+ it "should be able to create given label" do
157
+ labels = subject
158
+ labels.create("MYLABEL")
159
+ labels.exists?("MYLABEL").should be_true
160
+ labels.create("MYLABEL").should be_false
161
+ labels.delete("MYLABEL")
162
+ end
163
+
164
+ it "should be able to remove existing label" do
165
+ labels = subject
166
+ labels.create("MYLABEL")
167
+ labels.delete("MYLABEL").should be_true
168
+ labels.exists?("MYLABEL").should be_false
169
+ labels.delete("MYLABEL").should be_false
170
+ end
171
+ end
172
+ end
173
+ end