glima 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +52 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.org +53 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/config_example.yml +8 -0
- data/exe/glima +287 -0
- data/glima.gemspec +39 -0
- data/lib/glima.rb +18 -0
- data/lib/glima/cli.rb +151 -0
- data/lib/glima/command.rb +35 -0
- data/lib/glima/command/base.rb +23 -0
- data/lib/glima/command/dezip.rb +45 -0
- data/lib/glima/command/guess.rb +30 -0
- data/lib/glima/command/label.rb +29 -0
- data/lib/glima/command/labels.rb +63 -0
- data/lib/glima/command/profile.rb +15 -0
- data/lib/glima/command/push.rb +26 -0
- data/lib/glima/command/relabel.rb +65 -0
- data/lib/glima/command/scan.rb +32 -0
- data/lib/glima/command/trash.rb +22 -0
- data/lib/glima/command/watch.rb +45 -0
- data/lib/glima/command/xzip.rb +103 -0
- data/lib/glima/config.rb +205 -0
- data/lib/glima/context.rb +32 -0
- data/lib/glima/datastore.rb +70 -0
- data/lib/glima/gmail_client.rb +270 -0
- data/lib/glima/imap.rb +219 -0
- data/lib/glima/query_parameter.rb +30 -0
- data/lib/glima/resource.rb +31 -0
- data/lib/glima/resource/history.rb +94 -0
- data/lib/glima/resource/label.rb +22 -0
- data/lib/glima/resource/mail.rb +155 -0
- data/lib/glima/resource/message.rb +74 -0
- data/lib/glima/version.rb +3 -0
- data/lib/glima/zip.rb +156 -0
- data/spec/glima_spec.rb +11 -0
- data/spec/spec_helper.rb +2 -0
- metadata +213 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module Glima
|
5
|
+
class Context
|
6
|
+
|
7
|
+
def initialize(basedir)
|
8
|
+
unless basedir and File.directory?(File.expand_path(basedir.to_s))
|
9
|
+
raise Glima::ConfigurationError, "datastore directory '#{basedir}' not found"
|
10
|
+
end
|
11
|
+
@basedir = Pathname.new(File.expand_path(basedir))
|
12
|
+
end
|
13
|
+
|
14
|
+
def save_page_token(token)
|
15
|
+
File.open(page_token_path, "w") do |f|
|
16
|
+
f.write(token)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_page_token
|
21
|
+
File.open(page_token_path).read.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
################################################################
|
25
|
+
private
|
26
|
+
|
27
|
+
def page_token_path
|
28
|
+
File.expand_path("page_token.context", @basedir)
|
29
|
+
end
|
30
|
+
|
31
|
+
end # class Context
|
32
|
+
end # module Glima
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module Glima
|
5
|
+
class DataStore
|
6
|
+
|
7
|
+
def initialize(basedir)
|
8
|
+
unless basedir and File.directory?(File.expand_path(basedir.to_s))
|
9
|
+
raise Glima::ConfigurationError, "datastore directory '#{basedir}' not found"
|
10
|
+
end
|
11
|
+
@basedir = Pathname.new(File.expand_path(basedir))
|
12
|
+
end
|
13
|
+
|
14
|
+
def update(message)
|
15
|
+
if message.raw
|
16
|
+
save(message)
|
17
|
+
else
|
18
|
+
message.raw = load(message)
|
19
|
+
end
|
20
|
+
return message
|
21
|
+
end
|
22
|
+
|
23
|
+
def save(message)
|
24
|
+
path = folder_message_to_path("+all", message.id)
|
25
|
+
File.open(path, "w") do |f|
|
26
|
+
f.write(message.raw.gsub("\r\n", "\n"))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def load(message)
|
31
|
+
path = folder_message_to_path("+all", message.id)
|
32
|
+
return File.open(path).read
|
33
|
+
end
|
34
|
+
|
35
|
+
def exist?(message_id)
|
36
|
+
File.exist?(folder_message_to_path("+all", message_id))
|
37
|
+
end
|
38
|
+
|
39
|
+
################################################################
|
40
|
+
private
|
41
|
+
|
42
|
+
def folder_to_directory(folder)
|
43
|
+
folder = folder.sub(/^\+/, "")
|
44
|
+
File.expand_path(folder, @basedir)
|
45
|
+
end
|
46
|
+
|
47
|
+
def save_message_in_id(message, folder)
|
48
|
+
directory = folder_to_directory(folder)
|
49
|
+
|
50
|
+
raise "Error: #{directory} not exist" unless File.exist?(directory)
|
51
|
+
|
52
|
+
# name = message.thread_id + "-" + message.id
|
53
|
+
name = message.id + ".eml"
|
54
|
+
filename = File.expand_path(name, directory)
|
55
|
+
|
56
|
+
File.open(filename, "w") do |f|
|
57
|
+
f.write(message.raw.gsub("\r\n", "\n"))
|
58
|
+
end
|
59
|
+
return message.id
|
60
|
+
end
|
61
|
+
|
62
|
+
def folder_message_to_path(folder, message_id = nil)
|
63
|
+
folder = folder.sub(/^\+/, "")
|
64
|
+
directory = File.expand_path(folder, @basedir)
|
65
|
+
return directory unless message_id
|
66
|
+
return File.expand_path(message_id + ".eml", directory)
|
67
|
+
end
|
68
|
+
|
69
|
+
end # class DataStore
|
70
|
+
end # module Glima
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'google/apis/gmail_v1'
|
2
|
+
require 'googleauth'
|
3
|
+
require 'googleauth/stores/file_token_store'
|
4
|
+
require 'launchy'
|
5
|
+
require 'forwardable'
|
6
|
+
|
7
|
+
# Retry if rate-limit.
|
8
|
+
Google::Apis::RequestOptions.default.retries = 5
|
9
|
+
|
10
|
+
module Glima
|
11
|
+
class Authorizer
|
12
|
+
def initialize(client_id, client_secret, scope, token_store_path)
|
13
|
+
@authorizer = Google::Auth::UserAuthorizer.new(
|
14
|
+
Google::Auth::ClientId.new(client_id, client_secret),
|
15
|
+
scope,
|
16
|
+
Google::Auth::Stores::FileTokenStore.new(file: token_store_path)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def credentials(user_id)
|
21
|
+
@authorizer.get_credentials(user_id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def auth_interactively(user_id)
|
25
|
+
shell = Thor.new.shell
|
26
|
+
oob_uri = "urn:ietf:wg:oauth:2.0:oob"
|
27
|
+
|
28
|
+
url = @authorizer.get_authorization_url(base_url: oob_uri)
|
29
|
+
begin
|
30
|
+
Launchy.open(url)
|
31
|
+
rescue
|
32
|
+
puts "Open URL in your browser:\n #{url}"
|
33
|
+
end
|
34
|
+
|
35
|
+
code = shell.ask "Enter the resulting code:"
|
36
|
+
|
37
|
+
@authorizer.get_and_store_credentials_from_code(
|
38
|
+
user_id: user_id,
|
39
|
+
code: code,
|
40
|
+
base_url: oob_uri
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end # Authorizer
|
44
|
+
|
45
|
+
class GmailClient
|
46
|
+
def self.logger
|
47
|
+
Google::Apis.logger
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.logger=(logger)
|
51
|
+
Google::Apis.logger = logger
|
52
|
+
end
|
53
|
+
|
54
|
+
extend Forwardable
|
55
|
+
|
56
|
+
def_delegators :@client,
|
57
|
+
# Users.histoy
|
58
|
+
:list_user_histories,
|
59
|
+
|
60
|
+
# Users.labels
|
61
|
+
:list_user_labels,
|
62
|
+
# :get_user_label,
|
63
|
+
:patch_user_label,
|
64
|
+
|
65
|
+
# Users.messages
|
66
|
+
:get_user_message,
|
67
|
+
:insert_user_message,
|
68
|
+
:list_user_messages,
|
69
|
+
:modify_message,
|
70
|
+
# :trash_user_message,
|
71
|
+
|
72
|
+
# Users.threads
|
73
|
+
:get_user_thread,
|
74
|
+
|
75
|
+
# Users getProfile
|
76
|
+
:get_user_profile
|
77
|
+
|
78
|
+
|
79
|
+
# Find nearby messages from pivot_message
|
80
|
+
# `Nearby' message:
|
81
|
+
# + has same From: address
|
82
|
+
# + has near Date: field (+-1day)
|
83
|
+
# with the pivot_message.
|
84
|
+
def nearby_mails(pivot_mail)
|
85
|
+
from = "from:#{pivot_mail.from}"
|
86
|
+
date1 = (pivot_mail.date.to_date - 1).strftime("after:%Y/%m/%d")
|
87
|
+
date2 = (pivot_mail.date.to_date + 1).strftime("before:%Y/%m/%d")
|
88
|
+
query = "#{from} -in:trash #{date1} #{date2}"
|
89
|
+
scan_batch("+all", query) do |mail|
|
90
|
+
next if pivot_mail.id == mail.id
|
91
|
+
yield mail
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# * message types by format:
|
96
|
+
# | field/fromat: | list | minimal | raw | value type |
|
97
|
+
# |-----------------+------+---------+-----+-----------------|
|
98
|
+
# | id | ○ | ○ | ○ | string |
|
99
|
+
# | threadId | ○ | ○ | ○ | string |
|
100
|
+
# | labelIds | | ○ | ○ | string[] |
|
101
|
+
# | snippet | | ○ | ○ | string |
|
102
|
+
# | historyId | | ○ | ○ | unsinged long |
|
103
|
+
# | internalDate | | ○ | ○ | long |
|
104
|
+
# | sizeEstimate | | ○ | ○ | int |
|
105
|
+
# |-----------------+------+---------+-----+-----------------|
|
106
|
+
# | payload | | | | object |
|
107
|
+
# | payload.headers | | | | key/value pairs |
|
108
|
+
# | raw | | | ○ | bytes |
|
109
|
+
#
|
110
|
+
def get_user_smart_message(id)
|
111
|
+
fmt = if @datastore.exist?(id) then "minimal" else "raw" end
|
112
|
+
|
113
|
+
mail = nil
|
114
|
+
@client.get_user_message(me, id, format: fmt) do |m, err|
|
115
|
+
mail = Glima::Resource::Mail.new(@datastore.update(m)) if m
|
116
|
+
yield(mail, err)
|
117
|
+
end
|
118
|
+
return mail
|
119
|
+
end
|
120
|
+
|
121
|
+
def online?
|
122
|
+
Socket.getifaddrs.select {|i|
|
123
|
+
i.addr.ipv4? and ! i.addr.ipv4_loopback?
|
124
|
+
}.map(&:addr).map(&:ip_address).length > 0
|
125
|
+
end
|
126
|
+
|
127
|
+
def initialize(config, datastore)
|
128
|
+
authorizer = Authorizer.new(config.client_id,
|
129
|
+
config.client_secret,
|
130
|
+
Google::Apis::GmailV1::AUTH_SCOPE,
|
131
|
+
config.token_store)
|
132
|
+
|
133
|
+
credentials = authorizer.credentials(config.default_user) ||
|
134
|
+
authorizer.auth_interactively(config.default_user)
|
135
|
+
@datastore = datastore
|
136
|
+
@client = Google::Apis::GmailV1::GmailService.new
|
137
|
+
@client.client_options.application_name = 'glima'
|
138
|
+
@client.authorization = credentials
|
139
|
+
@client.authorization.username = config.default_user # for IMAP
|
140
|
+
|
141
|
+
return @client
|
142
|
+
end
|
143
|
+
|
144
|
+
# label == nil means "[Gmail]/All Mail"
|
145
|
+
def wait(label = nil)
|
146
|
+
@imap ||= Glima::ImapWatch.new("imap.gmail.com", @client.authorization)
|
147
|
+
@imap.wait(label&.name)
|
148
|
+
end
|
149
|
+
|
150
|
+
def watch(label = nil, &block)
|
151
|
+
loop do
|
152
|
+
puts "tick"
|
153
|
+
|
154
|
+
curr_hid = get_user_profile(me).history_id.to_i
|
155
|
+
last_hid ||= curr_hid
|
156
|
+
|
157
|
+
# FIXME: if server is changed at this point, we will miss the event.
|
158
|
+
wait(label) if last_hid == curr_hid
|
159
|
+
|
160
|
+
each_events(since: last_hid) do |ev|
|
161
|
+
yield ev
|
162
|
+
last_hid = ev.history_id.to_i
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def each_events(since:)
|
168
|
+
options, response = {start_history_id: since}, nil
|
169
|
+
|
170
|
+
loop do
|
171
|
+
client.list_user_histories(me, options) do |res, err|
|
172
|
+
raise err if err
|
173
|
+
response = res
|
174
|
+
end
|
175
|
+
|
176
|
+
break unless response.history
|
177
|
+
|
178
|
+
response.history.each do |h|
|
179
|
+
Glima::Resource::History.new(h).to_events.each do |ev|
|
180
|
+
yield ev
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
break unless options[:page_token] = response.next_page_token
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def get_user_label(id, fields: nil, options: nil, &block)
|
189
|
+
client.get_user_label(me, id, fields: fields, options: options, &block)
|
190
|
+
end
|
191
|
+
|
192
|
+
def trash_user_message(id, fields: nil, options: nil, &block)
|
193
|
+
client.trash_user_message(me, id, fields: fields, options: options, &block)
|
194
|
+
end
|
195
|
+
|
196
|
+
def batch(options = nil)
|
197
|
+
@client.batch(options) do |batch_client|
|
198
|
+
begin
|
199
|
+
Thread.current[:glima_api_batch] = batch_client
|
200
|
+
yield self
|
201
|
+
ensure
|
202
|
+
Thread.current[:glima_api_batch] = nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def scan_batch(folder, search_or_range = nil, &block)
|
208
|
+
qp = Glima::QueryParameter.new(folder, search_or_range)
|
209
|
+
list_user_messages(me, qp.to_hash) do |res, error|
|
210
|
+
fail "#{error}" if error
|
211
|
+
ids = (res.messages || []).map(&:id)
|
212
|
+
unless ids.empty?
|
213
|
+
batch_on_messages(ids) do |message|
|
214
|
+
yield Glima::Resource::Mail.new(message) if block
|
215
|
+
end
|
216
|
+
# context.save_page_token(res.next_page_token)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
rescue Glima::QueryParameter::FormatError => e
|
220
|
+
STDERR.print "Error: " + e.message + "\n"
|
221
|
+
end
|
222
|
+
|
223
|
+
def find_messages(query)
|
224
|
+
qp = Glima::QueryParameter.new("+all", query)
|
225
|
+
list_user_messages(me, qp.to_hash) do |res, error|
|
226
|
+
STDERR.print "#{error}" if error
|
227
|
+
return (res.messages || []).map(&:id)
|
228
|
+
end
|
229
|
+
rescue Glima::QueryParameter::FormatError => e
|
230
|
+
STDERR.print "Error: " + e.message + "\n"
|
231
|
+
end
|
232
|
+
|
233
|
+
def labels
|
234
|
+
@labels ||= client.list_user_labels(me).labels
|
235
|
+
end
|
236
|
+
|
237
|
+
def label_by_name(label_name)
|
238
|
+
labels.find {|label| label.name == label_name}
|
239
|
+
end
|
240
|
+
|
241
|
+
def label_by_id(label_id)
|
242
|
+
labels.find {|label| label.id == label_id}
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def me
|
248
|
+
'me'
|
249
|
+
end
|
250
|
+
|
251
|
+
def client
|
252
|
+
Thread.current[:glima_api_batch] || @client
|
253
|
+
end
|
254
|
+
|
255
|
+
def batch_on_messages(ids, &block)
|
256
|
+
@client.batch do |batch_client|
|
257
|
+
ids.each do |id|
|
258
|
+
fmt = if @datastore.exist?(id) then "minimal" else "raw" end
|
259
|
+
|
260
|
+
batch_client.get_user_message(me, id, format: fmt) do |m, err|
|
261
|
+
fail "#{err}" if err
|
262
|
+
message = @datastore.update(m)
|
263
|
+
yield message
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
end # class GmailClient
|
270
|
+
end # module Glima
|
data/lib/glima/imap.rb
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require "net/imap"
|
3
|
+
require "pp"
|
4
|
+
|
5
|
+
module Glima
|
6
|
+
class IMAP < Net::IMAP
|
7
|
+
def initialize(host, port_or_options = {},
|
8
|
+
usessl = false, certs = nil, verify = true)
|
9
|
+
super
|
10
|
+
@parser = Glima::IMAP::ResponseParser.new
|
11
|
+
end
|
12
|
+
|
13
|
+
class Xoauth2Authenticator
|
14
|
+
def initialize(user, oauth2_token)
|
15
|
+
@user = user
|
16
|
+
@oauth2_token = oauth2_token
|
17
|
+
end
|
18
|
+
|
19
|
+
def process(data)
|
20
|
+
build_oauth2_string(@user, @oauth2_token)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
# https://developers.google.com/google-apps/gmail/xoauth2_protocol
|
25
|
+
def build_oauth2_string(user, oauth2_token)
|
26
|
+
str = "user=%s\1auth=Bearer %s\1\1".encode("us-ascii") % [user, oauth2_token]
|
27
|
+
return str
|
28
|
+
end
|
29
|
+
end
|
30
|
+
private_constant :Xoauth2Authenticator
|
31
|
+
add_authenticator('XOAUTH2', Xoauth2Authenticator)
|
32
|
+
|
33
|
+
class ResponseParser < Net::IMAP::ResponseParser
|
34
|
+
def msg_att(n = 0)
|
35
|
+
match(T_LPAR)
|
36
|
+
attr = {}
|
37
|
+
while true
|
38
|
+
token = lookahead
|
39
|
+
case token.symbol
|
40
|
+
when T_RPAR
|
41
|
+
shift_token
|
42
|
+
break
|
43
|
+
when T_SPACE
|
44
|
+
shift_token
|
45
|
+
next
|
46
|
+
end
|
47
|
+
case token.value
|
48
|
+
when /\A(?:ENVELOPE)\z/ni
|
49
|
+
name, val = envelope_data
|
50
|
+
when /\A(?:FLAGS)\z/ni
|
51
|
+
name, val = flags_data
|
52
|
+
when /\A(?:INTERNALDATE)\z/ni
|
53
|
+
name, val = internaldate_data
|
54
|
+
when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni
|
55
|
+
name, val = rfc822_text
|
56
|
+
when /\A(?:RFC822\.SIZE)\z/ni
|
57
|
+
name, val = rfc822_size
|
58
|
+
when /\A(?:BODY(?:STRUCTURE)?)\z/ni
|
59
|
+
name, val = body_data
|
60
|
+
when /\A(?:UID)\z/ni
|
61
|
+
name, val = uid_data
|
62
|
+
|
63
|
+
# Gmail extension additions.
|
64
|
+
# https://gist.github.com/WojtekKruszewski/1404434
|
65
|
+
when /\A(?:X-GM-LABELS)\z/ni
|
66
|
+
name, val = flags_data
|
67
|
+
when /\A(?:X-GM-MSGID)\z/ni
|
68
|
+
name, val = uid_data
|
69
|
+
when /\A(?:X-GM-THRID)\z/ni
|
70
|
+
name, val = uid_data
|
71
|
+
else
|
72
|
+
parse_error("unknown attribute `%s' for {%d}", token.value, n)
|
73
|
+
end
|
74
|
+
attr[name] = val
|
75
|
+
end
|
76
|
+
return attr
|
77
|
+
end
|
78
|
+
end # class ResponseParser
|
79
|
+
end # class IMAP
|
80
|
+
end # module Glima
|
81
|
+
|
82
|
+
module Glima
|
83
|
+
class ImapWatch
|
84
|
+
extend Forwardable
|
85
|
+
|
86
|
+
def_delegators :@imap,
|
87
|
+
:fetch,
|
88
|
+
:select,
|
89
|
+
:disconnect
|
90
|
+
|
91
|
+
def initialize(imap_server, authorization)
|
92
|
+
connect(imap_server, authorization)
|
93
|
+
end
|
94
|
+
|
95
|
+
def watch(folder, &block)
|
96
|
+
@imap.select(Net::IMAP.encode_utf7(folder))
|
97
|
+
|
98
|
+
@thread = Thread.new do
|
99
|
+
while true
|
100
|
+
begin
|
101
|
+
@imap.idle do |resp|
|
102
|
+
yield resp if resp.name == "EXISTS"
|
103
|
+
end
|
104
|
+
rescue Net::IMAP::Error => e
|
105
|
+
if e.inspect.include? "connection closed"
|
106
|
+
connect
|
107
|
+
else
|
108
|
+
raise
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def wait(folder = nil)
|
116
|
+
if folder
|
117
|
+
folder = Net::IMAP.encode_utf7(folder)
|
118
|
+
else
|
119
|
+
# select "[Gmail]/All Mail" or localized one like "[Gmail]/すべてのメール"
|
120
|
+
folder = @imap.list("", "[Gmail]/*").find {|f| f.attr.include?(:All)}.name
|
121
|
+
end
|
122
|
+
|
123
|
+
@imap.select(folder)
|
124
|
+
begin
|
125
|
+
@imap.idle do |resp|
|
126
|
+
puts "#{resp.name}"
|
127
|
+
@imap.idle_done # if resp.name == "EXISTS"
|
128
|
+
end
|
129
|
+
rescue Net::IMAP::Error => e
|
130
|
+
if e.inspect.include? "connection closed"
|
131
|
+
connect
|
132
|
+
else
|
133
|
+
raise
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Ruby IMAP IDLE concurrency - how to tackle? - Stack Overflow
|
139
|
+
# http://stackoverflow.com/questions/5604480/ruby-imap-idle-concurrency-how-to-tackle
|
140
|
+
# How bad is IMAP IDLE? Joshua Tauberer's Archived Blog
|
141
|
+
# https://joshdata.wordpress.com/2014/08/09/how-bad-is-imap-idle/
|
142
|
+
#
|
143
|
+
def watch_and_fetch(folder, &block)
|
144
|
+
@imap.select(folder)
|
145
|
+
num = @imap.responses["EXISTS"].last
|
146
|
+
|
147
|
+
last_uid = @imap.fetch(num, "UID").first.attr['UID']
|
148
|
+
puts "Last_UID: #{last_uid}"
|
149
|
+
|
150
|
+
while true
|
151
|
+
id = waitfor()
|
152
|
+
|
153
|
+
puts "*********** GET EXISTS: #{id}"
|
154
|
+
mails = @imap.uid_fetch(last_uid..-1, %w(UID X-GM-MSGID X-GM-THRID X-GM-LABELS))
|
155
|
+
|
156
|
+
mails.each do |mail|
|
157
|
+
next if mail.attr['UID'] <= last_uid
|
158
|
+
resp = yield mail
|
159
|
+
return unless resp
|
160
|
+
end
|
161
|
+
|
162
|
+
last_uid = mails.last.attr['UID']
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def waitfor
|
169
|
+
id = -1
|
170
|
+
begin
|
171
|
+
@imap.idle do |resp|
|
172
|
+
pp resp
|
173
|
+
if resp.name == "EXISTS"
|
174
|
+
@imap.idle_done
|
175
|
+
id = resp.data.to_i
|
176
|
+
else
|
177
|
+
# pp resp
|
178
|
+
end
|
179
|
+
end
|
180
|
+
rescue Net::IMAP::Error => e
|
181
|
+
if e.inspect.include? "connection closed"
|
182
|
+
connect
|
183
|
+
else
|
184
|
+
raise
|
185
|
+
end
|
186
|
+
end
|
187
|
+
return id
|
188
|
+
end
|
189
|
+
|
190
|
+
def connect(imap_server, authorization)
|
191
|
+
retry_count = 0
|
192
|
+
|
193
|
+
begin
|
194
|
+
username = authorization.username
|
195
|
+
access_token = authorization.access_token
|
196
|
+
|
197
|
+
@imap = Glima::IMAP.new(imap_server, 993, true)
|
198
|
+
@imap.authenticate('XOAUTH2', username, access_token)
|
199
|
+
puts "connected to imap server #{imap_server}."
|
200
|
+
|
201
|
+
rescue Net::IMAP::NoResponseError => e
|
202
|
+
if e.inspect.include? "Invalid credentials" && retry_count < 2
|
203
|
+
authorization.refresh!
|
204
|
+
retry_count += 1
|
205
|
+
retry
|
206
|
+
else
|
207
|
+
raise
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# def connect(imap_server, user_name, access_token)
|
213
|
+
# @imap = Glima::IMAP.new(imap_server, 993, true)
|
214
|
+
# # @imap.class.debug = true
|
215
|
+
# @imap.authenticate('XOAUTH2', user_name, access_token)
|
216
|
+
# puts "connected to imap server #{imap_server}."
|
217
|
+
# end
|
218
|
+
end # class ImapWatch
|
219
|
+
end # module Glima
|