google-gmail-api 0.0.14
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.
- checksums.yaml +7 -0
- data/.bundle/config +1 -0
- data/.gitignore +32 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +40 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +294 -0
- data/Rakefile +32 -0
- data/TODO.md +3 -0
- data/account.yml.example +17 -0
- data/gmail.gemspec +41 -0
- data/lib/gmail.rb +159 -0
- data/lib/gmail/api_resource.rb +12 -0
- data/lib/gmail/base/create.rb +16 -0
- data/lib/gmail/base/delete.rb +31 -0
- data/lib/gmail/base/get.rb +17 -0
- data/lib/gmail/base/list.rb +54 -0
- data/lib/gmail/base/modify.rb +66 -0
- data/lib/gmail/base/trash.rb +33 -0
- data/lib/gmail/base/update.rb +26 -0
- data/lib/gmail/draft.rb +54 -0
- data/lib/gmail/gmail_object.rb +147 -0
- data/lib/gmail/label.rb +52 -0
- data/lib/gmail/message.rb +242 -0
- data/lib/gmail/thread.rb +38 -0
- data/lib/gmail/util.rb +45 -0
- data/lib/gmail/version.rb +3 -0
- data/test/gmail/api_resource_test.rb +47 -0
- data/test/gmail/draft_test.rb +104 -0
- data/test/gmail/gmail_object_test.rb +39 -0
- data/test/gmail/label_test.rb +129 -0
- data/test/gmail/message_test.rb +365 -0
- data/test/gmail/thread_test.rb +184 -0
- data/test/gmail/util_test.rb +18 -0
- data/test/test_data.rb +127 -0
- data/test/test_helper.rb +38 -0
- metadata +262 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module Gmail
|
2
|
+
module Base
|
3
|
+
module Update
|
4
|
+
def update!(body)
|
5
|
+
if id.nil?
|
6
|
+
d = self.class.create(body)
|
7
|
+
else
|
8
|
+
response = Gmail.request(self.class.base_method.send("update"),{id: id}, body)
|
9
|
+
d = Util.convert_to_gmail_object(response, self.class.class_name.downcase)
|
10
|
+
end
|
11
|
+
@values = d.values
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(body)
|
16
|
+
if id.nil?
|
17
|
+
d = self.class.create(body)
|
18
|
+
else
|
19
|
+
response = Gmail.request(self.class.base_method.send("update"),{id: id}, body)
|
20
|
+
d = Util.convert_to_gmail_object(response, self.class.class_name.downcase)
|
21
|
+
end
|
22
|
+
d
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/gmail/draft.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Gmail
|
2
|
+
class Draft < APIResource
|
3
|
+
include Base::List
|
4
|
+
include Base::Create
|
5
|
+
include Base::Delete
|
6
|
+
include Base::Get
|
7
|
+
include Base::Update
|
8
|
+
|
9
|
+
def message
|
10
|
+
if @values.message.is_a?(Message)
|
11
|
+
@values.message
|
12
|
+
else
|
13
|
+
@values.message = Util.convert_to_gmail_object(to_hash[:message], key="message")
|
14
|
+
if @values.message.payload.nil?
|
15
|
+
self.detailed!
|
16
|
+
message
|
17
|
+
end
|
18
|
+
@values.message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def save(opts={})
|
23
|
+
msg = {raw: message.raw}
|
24
|
+
if message.threadId
|
25
|
+
msg[:threadId] = message.threadId
|
26
|
+
end
|
27
|
+
if message.labelIds
|
28
|
+
msg[:labelIds] = message.labelIds
|
29
|
+
end
|
30
|
+
body = {message: msg}
|
31
|
+
update(body)
|
32
|
+
end
|
33
|
+
|
34
|
+
def save!(opts={})
|
35
|
+
msg = {raw: message.raw}
|
36
|
+
if message.threadId
|
37
|
+
msg[:threadId] = message.threadId
|
38
|
+
end
|
39
|
+
if message.labelIds
|
40
|
+
msg[:labelIds] = message.labelIds
|
41
|
+
end
|
42
|
+
body = {message: msg}
|
43
|
+
update!(body)
|
44
|
+
end
|
45
|
+
|
46
|
+
def deliver
|
47
|
+
response = Gmail.request(self.class.base_method.to_h['gmail.users.drafts.send'],{},{id: id})
|
48
|
+
Message.get(response[:id])
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Gmail
|
2
|
+
class GmailObject
|
3
|
+
include Enumerable
|
4
|
+
include Hooks
|
5
|
+
define_hooks :after_initialize
|
6
|
+
|
7
|
+
# The default :id method is deprecated and isn't useful to us
|
8
|
+
if method_defined?(:id)
|
9
|
+
undef :id
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(hash={})
|
13
|
+
@values = Hashie::Mash.new hash
|
14
|
+
run_hook :after_initialize
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def to_s(*args)
|
19
|
+
JSON.pretty_generate(@values.to_hash)
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}> " + to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](k)
|
27
|
+
@values[k.to_sym]
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(k, v)
|
31
|
+
@values.send("#{k}=", v)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_json(*a)
|
35
|
+
JSON.generate(@values)
|
36
|
+
end
|
37
|
+
|
38
|
+
def as_json(*a)
|
39
|
+
@values.as_json(*a)
|
40
|
+
end
|
41
|
+
|
42
|
+
def detailed
|
43
|
+
if self.class == GmailObject
|
44
|
+
raise "Can't detail a generic GmailObject. It needs to be a Thread, Message, Draft or Label"
|
45
|
+
end
|
46
|
+
|
47
|
+
self.class.get(id)
|
48
|
+
end
|
49
|
+
|
50
|
+
def refresh
|
51
|
+
if self.class == GmailObject
|
52
|
+
raise "Can't refresh a generic GmailObject. It needs to be a Thread, Message, Draft or Label"
|
53
|
+
end
|
54
|
+
@values = self.class.get(id).values
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
alias_method :detailed!, :refresh
|
59
|
+
#
|
60
|
+
def to_hash
|
61
|
+
Util.symbolize_names(@values.to_hash)
|
62
|
+
end
|
63
|
+
|
64
|
+
def values
|
65
|
+
@values
|
66
|
+
end
|
67
|
+
#
|
68
|
+
# def each(&blk)
|
69
|
+
# @values.each(&blk)
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# def _dump(level)
|
73
|
+
# Marshal.dump([@values, @api_key])
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# def self._load(args)
|
77
|
+
# values, api_key = Marshal.load(args)
|
78
|
+
# construct_from(values)
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# if RUBY_VERSION < '1.9.2'
|
82
|
+
# def respond_to?(symbol)
|
83
|
+
# @values.has_key?(symbol) || super
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
protected
|
88
|
+
|
89
|
+
def metaclass
|
90
|
+
class << self; self; end
|
91
|
+
end
|
92
|
+
#
|
93
|
+
# def remove_accessors(keys)
|
94
|
+
# metaclass.instance_eval do
|
95
|
+
# keys.each do |k|
|
96
|
+
# next if @@permanent_attributes.include?(k)
|
97
|
+
# k_eq = :"#{k}="
|
98
|
+
# remove_method(k) if method_defined?(k)
|
99
|
+
# remove_method(k_eq) if method_defined?(k_eq)
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# def add_accessors(keys)
|
105
|
+
# metaclass.instance_eval do
|
106
|
+
# keys.each do |k|
|
107
|
+
# next if @@permanent_attributes.include?(k)
|
108
|
+
# k_eq = :"#{k}="
|
109
|
+
# define_method(k) { @values[k] }
|
110
|
+
# define_method(k_eq) do |v|
|
111
|
+
# if v == ""
|
112
|
+
# raise ArgumentError.new(
|
113
|
+
# "You cannot set #{k} to an empty string." +
|
114
|
+
# "We interpret empty strings as nil in requests." +
|
115
|
+
# "You may set #{self}.#{k} = nil to delete the property.")
|
116
|
+
# end
|
117
|
+
# @values[k] = v
|
118
|
+
# @unsaved_values.add(k)
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
# end
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
def method_missing(name, *args)
|
125
|
+
|
126
|
+
if @values.send(name.to_s + "?")
|
127
|
+
@values.send(name)
|
128
|
+
else
|
129
|
+
begin
|
130
|
+
@values.send(name.to_s, args[0])
|
131
|
+
rescue
|
132
|
+
begin
|
133
|
+
super.send(name)
|
134
|
+
rescue
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
#
|
143
|
+
# def respond_to_missing?(symbol, include_private = false)
|
144
|
+
# super
|
145
|
+
# end
|
146
|
+
end
|
147
|
+
end
|
data/lib/gmail/label.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Gmail
|
2
|
+
class Label < APIResource
|
3
|
+
include Base::List
|
4
|
+
include Base::Create
|
5
|
+
include Base::Delete
|
6
|
+
include Base::Get
|
7
|
+
include Base::Update
|
8
|
+
|
9
|
+
def save
|
10
|
+
update(to_hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
def save!
|
14
|
+
update!(to_hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.boxes
|
18
|
+
@boxes ||= [:inbox, :sent, :trash, :important, :starred, :draft, :spam, :unread, :category_updates, :category_promotions, :category_social, :category_personal, :category_forums ]
|
19
|
+
end
|
20
|
+
|
21
|
+
boxes.each do |method|
|
22
|
+
define_singleton_method method do
|
23
|
+
Label.get(method.to_s.upcase)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def messages filters={}
|
28
|
+
filters = {labelIds: [id]}.merge(filters)
|
29
|
+
filters[:labelIds] = filters[:labelIds] | [id]
|
30
|
+
Message.all(filters)
|
31
|
+
end
|
32
|
+
|
33
|
+
def unread_messages
|
34
|
+
if messagesUnread == 0
|
35
|
+
[]
|
36
|
+
else
|
37
|
+
Message.all({labelIds: [id, "UNREAD"]})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def threads filters={}
|
42
|
+
filters = {labelIds: [id]}.merge(filters)
|
43
|
+
filters[:labelIds] = filters[:labelIds] | [id]
|
44
|
+
Thread.all(filters)
|
45
|
+
end
|
46
|
+
|
47
|
+
def unread_threads
|
48
|
+
Thread.all({labelIds: [id, "UNREAD"]})
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end # Gmail
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Gmail
|
3
|
+
class Message < APIResource
|
4
|
+
include Base::List
|
5
|
+
include Base::Delete
|
6
|
+
include Base::Get
|
7
|
+
include Base::Modify
|
8
|
+
include Base::Trash
|
9
|
+
|
10
|
+
require "stringex"
|
11
|
+
|
12
|
+
after_initialize :set_basics
|
13
|
+
|
14
|
+
def thread
|
15
|
+
Thread.get(threadId)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_draft
|
19
|
+
Draft.create(message: msg_parameters)
|
20
|
+
end
|
21
|
+
|
22
|
+
def deliver!
|
23
|
+
response = Gmail.request(self.class.base_method.to_h['gmail.users.messages.send'],{}, msg_parameters)
|
24
|
+
@values = Message.get(response[:id]).values
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def deliver
|
29
|
+
response = Gmail.request(self.class.base_method.to_h['gmail.users.messages.send'],{}, msg_parameters)
|
30
|
+
Message.get(response[:id])
|
31
|
+
end
|
32
|
+
|
33
|
+
def insert
|
34
|
+
response = Gmail.request(self.class.base_method.insert,{}, msg_parameters)
|
35
|
+
Message.get(response[:id])
|
36
|
+
end
|
37
|
+
|
38
|
+
def insert!
|
39
|
+
response = Gmail.request(self.class.base_method.insert,{}, msg_parameters)
|
40
|
+
@values = Message.get(response[:id]).values
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def reply_all_with msg
|
45
|
+
msg = set_headers_for_reply msg
|
46
|
+
msg = quote_in msg
|
47
|
+
msg
|
48
|
+
end
|
49
|
+
|
50
|
+
def reply_sender_with msg
|
51
|
+
msg = set_headers_for_reply msg
|
52
|
+
msg = quote_in msg
|
53
|
+
msg.cc = nil
|
54
|
+
msg
|
55
|
+
end
|
56
|
+
|
57
|
+
def forward_with msg
|
58
|
+
# save headers that need to be override by users compared to a classic reply
|
59
|
+
x_cc = msg.cc
|
60
|
+
x_to = msg.to
|
61
|
+
x_bcc = msg.bcc
|
62
|
+
x_subject = msg.subject || subject #if user doesn't override keep classic behavior
|
63
|
+
# set headers as for reply
|
64
|
+
msg = set_headers_for_reply msg
|
65
|
+
# quote message
|
66
|
+
msg = quote_in msg
|
67
|
+
# reset saved overridden headers
|
68
|
+
msg.cc = x_cc
|
69
|
+
msg.to = x_to
|
70
|
+
msg.bcc = x_bcc
|
71
|
+
msg.subject = x_subject
|
72
|
+
msg
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def thread_id
|
77
|
+
threadId
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def unread?
|
82
|
+
(labelIds||[]).include?("UNREAD")
|
83
|
+
end
|
84
|
+
|
85
|
+
def sent?
|
86
|
+
(labelIds||[]).include?("SENT")
|
87
|
+
end
|
88
|
+
|
89
|
+
def inbox?
|
90
|
+
(labelIds||[]).include?("INBOX")
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
def raw # is not in private because the method is used in Draft
|
96
|
+
if super #check if raw is set to allow fully custom message to be sent
|
97
|
+
super
|
98
|
+
else
|
99
|
+
s = self
|
100
|
+
msg = Mail.new
|
101
|
+
msg.subject = subject
|
102
|
+
if body
|
103
|
+
msg.body = body
|
104
|
+
end
|
105
|
+
msg.from = from
|
106
|
+
msg.to = to
|
107
|
+
msg.cc = cc
|
108
|
+
msg.header['X-Bcc'] = bcc unless bcc.nil?#because Mail gem doesn't allow bcc headers...
|
109
|
+
msg.in_reply_to = in_reply_to unless in_reply_to.nil?
|
110
|
+
msg.references = references unless references.nil?
|
111
|
+
if text || html
|
112
|
+
bodypart = Mail::Part.new
|
113
|
+
if text
|
114
|
+
bodypart.text_part = Mail::Part.new do |p|
|
115
|
+
content_type 'text/plain; charset=UTF-8'
|
116
|
+
p.body s.text
|
117
|
+
end
|
118
|
+
end
|
119
|
+
if html
|
120
|
+
bodypart.html_part = Mail::Part.new do |p|
|
121
|
+
content_type 'text/html; charset=UTF-8'
|
122
|
+
p.body s.html
|
123
|
+
end
|
124
|
+
end
|
125
|
+
msg.add_part bodypart
|
126
|
+
end
|
127
|
+
if attachments
|
128
|
+
if attachments.is_a?(Hash)
|
129
|
+
attachments.each do |name, attachment|
|
130
|
+
msg.add_file filename: name, content: attachment
|
131
|
+
end
|
132
|
+
elsif attachments.is_a?(Array)
|
133
|
+
attachments.each do |attachment|
|
134
|
+
msg.add_file(attachment)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
Base64.urlsafe_encode64 msg.to_s.sub("X-Bcc", "Bcc") #because Mail gem doesn't allow bcc headers...
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def msg_parameters
|
145
|
+
msg = {raw: raw}
|
146
|
+
if threadId
|
147
|
+
msg[:threadId] = threadId
|
148
|
+
end
|
149
|
+
if labelIds
|
150
|
+
msg[:labelIds] = labelIds
|
151
|
+
end
|
152
|
+
msg
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.find_addresses str
|
156
|
+
Mail::AddressList.new("#{str}".to_ascii.gsub(/<(<(.)*@(.)*>)(.)*>/, '\1'))
|
157
|
+
end
|
158
|
+
|
159
|
+
def set_headers_for_reply msg
|
160
|
+
#to_ar = []
|
161
|
+
#split_regexp = Regexp.new("\s*,\s*")
|
162
|
+
own_email = delivered_to || Gmail.mailbox_email
|
163
|
+
|
164
|
+
|
165
|
+
to_ar = (Message.find_addresses(to).addresses + Message.find_addresses(cc).addresses).map(&:to_s)
|
166
|
+
#to_ar = (to || "").split(split_regexp) + (cc || "").split(split_regexp)
|
167
|
+
result = to_ar.grep(Regexp.new(own_email, "i"))
|
168
|
+
to_ar = to_ar - result
|
169
|
+
|
170
|
+
msg.subject = subject
|
171
|
+
if from.match(Regexp.new(own_email, "i"))
|
172
|
+
msg.to = to_ar.first
|
173
|
+
to_ar = to_ar.drop(1)
|
174
|
+
else
|
175
|
+
msg.to = from
|
176
|
+
end
|
177
|
+
msg.cc = to_ar.join(", ")
|
178
|
+
msg.bcc = nil
|
179
|
+
msg.threadId = thread_id
|
180
|
+
msg.references = ((references || "").split(Regexp.new "\s+") << message_id).join(" ")
|
181
|
+
msg.in_reply_to = ((in_reply_to || "").split(Regexp.new "\s+") << message_id).join(" ")
|
182
|
+
msg
|
183
|
+
end
|
184
|
+
|
185
|
+
def quote_in reply_msg
|
186
|
+
text_to_append = "\r\n\r\n#{date} #{from}:\r\n\r\n>" + (body || text).gsub("\n", "\n>") unless body.nil? && text.nil?
|
187
|
+
html_to_append = "\r\n<br><br><div class=\"gmail_quote\"> #{date} #{CGI.escapeHTML(from)}:<br><blockquote class=\"gmail_quote\" style=\"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex\">" + html + "</blockquote></div><br>" unless html.nil?
|
188
|
+
reply_msg.html = "<div>" + reply_msg.html + "</div>" + html_to_append unless reply_msg.html.nil?
|
189
|
+
reply_msg.text = reply_msg.text + text_to_append unless reply_msg.text.nil?
|
190
|
+
reply_msg.body = reply_msg.body + text_to_append unless reply_msg.body.nil?
|
191
|
+
reply_msg
|
192
|
+
end
|
193
|
+
|
194
|
+
def urlsafe_decode64 code
|
195
|
+
Base64.urlsafe_decode64(code).force_encoding('UTF-8').encode
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
def set_basics
|
200
|
+
if @values.payload
|
201
|
+
["From", "To", "Cc", "Subject", "Bcc", "Date", "Message-ID", "References", "In-Reply-To", "Delivered-To"].each do |n|
|
202
|
+
if payload_n = @values.payload.headers.select{|h| h.name.downcase == n.downcase}.first
|
203
|
+
@values.send(n.downcase.tr("-", "_") + "=", payload_n.value)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
if payload.parts
|
208
|
+
content_payload = @values.payload.find_all_object_containing("mimeType", "multipart/alternative").first
|
209
|
+
content_payload ||= @values.payload
|
210
|
+
text_part=content_payload.find_all_object_containing("mimeType", "text/plain").first
|
211
|
+
if text_part && text_part.body.data
|
212
|
+
@values.text = urlsafe_decode64(text_part.body.data)
|
213
|
+
end
|
214
|
+
html_part=content_payload.find_all_object_containing("mimeType", "text/html").first
|
215
|
+
if html_part && html_part.body.data
|
216
|
+
@values.html = urlsafe_decode64(html_part.body.data)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
if payload.body.data
|
220
|
+
@values.body = urlsafe_decode64(@values.payload.body.data)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class Hashie::Mash
|
226
|
+
def find_all_object_containing(key, value )
|
227
|
+
result=[]
|
228
|
+
if self.send(key) == value
|
229
|
+
result << self
|
230
|
+
end
|
231
|
+
self.values.each do |vs|
|
232
|
+
vs = [vs] unless vs.is_a? Array
|
233
|
+
vs.each do |v|
|
234
|
+
result += v.find_all_object_containing(key,value) if v.is_a? Hashie::Mash
|
235
|
+
end
|
236
|
+
end
|
237
|
+
result
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|