gmail-afurmanov 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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