glima 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ module Glima
2
+ module Command
3
+ class Push < Base
4
+
5
+ def initialize(email_file, date, thread, labels)
6
+ label_ids = labels.map(&:id) + ["INBOX", "UNREAD"]
7
+
8
+ File.open(email_file) do |source|
9
+ client.insert_user_message(
10
+ 'me',
11
+ Google::Apis::GmailV1::Message.new(label_ids: label_ids, thread_id: thread),
12
+ content_type: "message/rfc822",
13
+ internal_date_source: date,
14
+ upload_source: source) do |msg, err|
15
+ if msg
16
+ puts "pushed to: #{msg.id}"
17
+ else
18
+ STDERR.puts "Error: #{err}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ end # class Push
25
+ end # module Command
26
+ end # module Glima
@@ -0,0 +1,65 @@
1
+ module Glima
2
+ module Command
3
+ class Relabel < Base
4
+
5
+ def initialize(source_name, dest_name, dry_run)
6
+
7
+ all_labels = client.list_user_labels('me').labels.sort_by(&:name)
8
+
9
+ if /\/$/ =~ dest_name
10
+ move_to_dir = true
11
+ dest_name = dest_name.sub(/\/$/, '')
12
+ else
13
+ move_to_dir = false
14
+ end
15
+
16
+ source_labels = all_labels.find_all {|x| File.fnmatch(source_name, x.name, File::FNM_PATHNAME)}
17
+ dest_label = all_labels.find {|x| x.name == dest_name}
18
+
19
+ if source_labels.empty?
20
+ puts "Error: source #{source_name} not found"
21
+ return nil
22
+ end
23
+
24
+ if dest_label && !move_to_dir
25
+ puts "Error: dest #{dest_name} already exists"
26
+ return nil
27
+ end
28
+
29
+ if !dest_label && move_to_dir
30
+ puts "Error: dest #{dest_name} not found"
31
+ return nil
32
+ end
33
+
34
+ source_labels.each do |source_label|
35
+ dirtop = File.dirname(source_label.name)
36
+ sub_labels = all_labels.find_all {|x| File.fnmatch(source_label.name + '/*', x.name)}
37
+
38
+ ([source_label] + sub_labels).each do |label|
39
+ src = label.name
40
+ dst = dest_name + '/' + (label.name.sub(/^#{dirtop}\//, ''))
41
+
42
+ if all_labels.find {|x| x.name == dst}
43
+ puts "Error: relabel #{src} -> #{dst}: Destination already exists"
44
+ next
45
+ else
46
+ puts "relabel #{src} -> #{dst}"
47
+ end
48
+
49
+ unless dry_run
50
+ label_obj = Google::Apis::GmailV1::Label.new(id: label.id, name: dst)
51
+ client.patch_user_label('me', label.id, label_obj) do |response, err|
52
+ if response
53
+ # puts dump_label(response)
54
+ else
55
+ puts "Error: #{err}"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ end # class Relabel
64
+ end # module Command
65
+ end # module Glima
@@ -0,0 +1,32 @@
1
+ module Glima
2
+ module Command
3
+ class Scan < Base
4
+
5
+ def initialize(folder, format, search_or_range)
6
+
7
+ index = 1
8
+ client.scan_batch(folder, search_or_range) do |mail|
9
+ case format
10
+ when :mew
11
+ puts mail.format_mew(index)
12
+ when :archive
13
+ puts format_archive_friendly(mail)
14
+ else
15
+ puts mail.format_summary(index)
16
+ end
17
+ index += 1
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def format_archive_friendly(mail)
24
+ date = mail.date.strftime("%Y-%m-%d-%H%M%S")
25
+ # Replace unsafe chars for filename
26
+ subject = mail.subject.tr('!/:*?"<>|\\', '!/:*?″<>|\').gsub(/[\s ]/, '')
27
+ return "#{mail.id} #{date}-#{mail.id}-#{subject}.eml"
28
+ end
29
+
30
+ end # class Scan
31
+ end # module Command
32
+ end # module Glima
@@ -0,0 +1,22 @@
1
+ module Glima
2
+ module Command
3
+ class Trash < Base
4
+
5
+ def initialize(message_ids)
6
+
7
+ client.batch do |batch_client|
8
+ message_ids.each do |id|
9
+ batch_client.trash_user_message(id) do |res, err|
10
+ if res
11
+ puts "Trash #{id} successfully."
12
+ else
13
+ puts "Error: #{err}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ end # class Trash
21
+ end # module Command
22
+ end # module Glima
@@ -0,0 +1,45 @@
1
+ module Glima
2
+ module Command
3
+ class Watch < Base
4
+
5
+ def initialize(queue_label = nil, mark_label = nil)
6
+
7
+ # Watch "[Gmail]/All Mail" by IMAP idle
8
+ client.watch(nil) do |ev|
9
+ next unless ev.type == :added
10
+
11
+ # Scan messages in queue_label or new message itself.
12
+ #
13
+ # If Xzip process is successful, remove queue_label
14
+ # from the source message.
15
+ #
16
+ if queue_label
17
+ target = "label:#{queue_label.name}"
18
+ target += " -label:#{mark_label.name}" if mark_label
19
+ del_labels = [queue_label]
20
+ else
21
+ target = ev.message.id
22
+ del_labels = []
23
+ end
24
+
25
+ # Also, mark_label will be added to the xzipped message.
26
+ # It is for avoidance of infinite loop.
27
+ #
28
+ if mark_label
29
+ add_labels = [mark_label]
30
+ else
31
+ add_labels = []
32
+ end
33
+
34
+ logger.info "Xzip #{target}"
35
+
36
+ Glima::Command::Xzip.new(client, logger, target,
37
+ add_dst_labels: add_labels,
38
+ del_dst_labels: del_labels,
39
+ del_src_labels: del_labels)
40
+ end
41
+ end
42
+
43
+ end # class Watch
44
+ end # module Command
45
+ end # module Glima
@@ -0,0 +1,103 @@
1
+ module Glima
2
+ module Command
3
+ class Xzip < Base
4
+ def initialize(target,
5
+ add_src_labels: [],
6
+ del_src_labels: [],
7
+ add_dst_labels: [],
8
+ del_dst_labels: [])
9
+
10
+ add_src_label_ids = add_src_labels.map(&:id)
11
+ del_src_label_ids = del_src_labels.map(&:id)
12
+ add_dst_label_ids = add_dst_labels.map(&:id)
13
+ del_dst_label_ids = del_dst_labels.map(&:id)
14
+
15
+ ids = if target =~ /^[\da-fA-F]{16}$/
16
+ [target]
17
+ else
18
+ client.find_messages(target)
19
+ end
20
+
21
+ ids.each do |message_id|
22
+ # get target mail
23
+ mail = client.get_user_smart_message(message_id) do |m, err|
24
+ if err
25
+ puts "Error: #{err}"
26
+ next
27
+ end
28
+ end
29
+
30
+ # find password candidates from nearby mails
31
+ password_candidates = []
32
+ client.nearby_mails(mail) do |nm|
33
+ logger.info "Passwordish mail: " + nm.format_summary
34
+ password_candidates += nm.find_passwordish_strings
35
+ end
36
+
37
+ # try to unlock zip attachments
38
+ unless mail.unlock_zip!(password_candidates, logger)
39
+ puts "Password unlock failed."
40
+ next
41
+ end
42
+
43
+ # push back unlocked mail to server
44
+ unless push_mail(mail, "dateHeader", add_dst_label_ids, del_dst_label_ids)
45
+ puts "Push mail failed."
46
+ next
47
+ end
48
+
49
+ # add/remove labels from the target
50
+ if add_src_label_ids.empty? && del_src_label_ids.empty?
51
+ next
52
+ end
53
+
54
+ req = {}
55
+ req[:add_label_ids] = add_src_label_ids
56
+ req[:remove_label_ids] = del_src_label_ids
57
+
58
+ req = Google::Apis::GmailV1::ModifyMessageRequest.new(req)
59
+
60
+ client.modify_message('me', message_id, req) do |res,err|
61
+ if res
62
+ puts "Update #{message_id} successfully."
63
+ else
64
+ puts "Error: #{err}"
65
+ end
66
+ end
67
+ end
68
+ end # def initialize
69
+
70
+ private
71
+
72
+ def push_mail(mail, date_source = "receivedTime", add_label_ids = [], del_label_ids = [])
73
+ label_ids = (mail.label_ids +
74
+ add_label_ids -
75
+ del_label_ids +
76
+ ["INBOX", "UNREAD"]).uniq
77
+ thid = mail.thread_id
78
+
79
+ unless date_source == "dateHeader" || date_source == "receivedTime"
80
+ raise "Unknown date type: #{date_source}"
81
+ end
82
+
83
+ mail.header["X-Glima-Processed"] = DateTime.now.rfc2822
84
+
85
+ client.insert_user_message(
86
+ 'me',
87
+ Google::Apis::GmailV1::Message.new(label_ids: label_ids, thread_id: thid),
88
+ content_type: "message/rfc822",
89
+ internal_date_source: date_source,
90
+ upload_source: StringIO.new(mail.to_s)) do |msg, err|
91
+ if msg
92
+ puts "pushed to: #{msg.id}"
93
+ return true
94
+ else
95
+ STDERR.puts "Error: #{err}"
96
+ return false
97
+ end
98
+ end
99
+ end
100
+
101
+ end # class Xzip
102
+ end # module Command
103
+ end # module Glima
@@ -0,0 +1,205 @@
1
+ require 'yaml'
2
+ require 'pp'
3
+
4
+ module Glima
5
+ class Config
6
+ # Syntax table manipulation
7
+ class Syntax
8
+ def initialize(syntax_config)
9
+ @syntax_config = syntax_config
10
+ end
11
+
12
+ def keyword_symbols
13
+ @syntax_config.keys
14
+ end
15
+
16
+ def keywords
17
+ keyword_symbols.map {|sym| sym.to_s.upcase }
18
+ end
19
+
20
+ def keyword?(word)
21
+ if word.is_a?(Symbol)
22
+ keyword_symbols.member?(word)
23
+ else
24
+ # String
25
+ keywords.member?(word)
26
+ end
27
+ end
28
+
29
+ def instance_variable_name(word)
30
+ return nil unless keyword?(word)
31
+ return '@' + as_symbol(word).to_s
32
+ end
33
+
34
+ def item_class(word)
35
+ return nil unless keyword?(word)
36
+ @syntax_config[as_symbol(word)]
37
+ end
38
+
39
+ private
40
+ def as_symbol(word)
41
+ word.to_s.downcase.sub(/^@+/, "").to_sym
42
+ end
43
+ end # class Syntax
44
+
45
+ # Parse Key-Value object in YAML
46
+ class Base
47
+ # attr_accessor :name
48
+
49
+ def self.create_from_yaml_file(yaml_file)
50
+ yaml_string = File.open(File.expand_path(yaml_file)).read
51
+ return create_from_yaml_string(yaml_string, yaml_file)
52
+ end
53
+
54
+ def self.create_from_yaml_string(yaml_string, filename = nil)
55
+ hash = YAML.load(yaml_string, filename) || {}
56
+ return new(hash)
57
+ end
58
+
59
+ def self.define_syntax(config)
60
+ @syntax = Syntax.new(config)
61
+ @syntax.keyword_symbols.each do |sym|
62
+ attr_accessor sym # XXX: attr_reader is enough?
63
+ end
64
+ end
65
+
66
+ def self.syntax
67
+ return @syntax
68
+ end
69
+
70
+ def initialize(hash = {})
71
+ @original_hash = hash
72
+ (hash || {}).each do |key, val|
73
+ raise Glima::ConfigurationError, "config syntax error (#{key})" unless syntax.keyword?(key)
74
+ var = syntax.instance_variable_name(key)
75
+ obj = create_subnode(key, val)
76
+ instance_variable_set(var, obj)
77
+ end
78
+ end
79
+
80
+ attr_reader :original_hash
81
+
82
+ def get_value(dot_separated_string = nil)
83
+ if dot_separated_string.to_s == ""
84
+ return original_hash
85
+ end
86
+
87
+ key, subkey = dot_separated_string.to_s.upcase.split(".", 2)
88
+ subnode = get_subnode(key)
89
+
90
+ if subnode.respond_to?(:get_value)
91
+ return subnode.get_value(subkey)
92
+ else
93
+ return subnode.to_s
94
+ end
95
+ end
96
+
97
+ def to_yaml
98
+ return self.to_hash.to_yaml
99
+ end
100
+
101
+ def to_hash
102
+ hash = {}
103
+ syntax.keywords.each do |key|
104
+ var = syntax.instance_variable_name(key)
105
+ obj = instance_variable_get(var)
106
+ obj = obj.respond_to?(:to_hash) ? obj.to_hash : obj.to_s
107
+ hash[key] = obj
108
+ end
109
+ return hash
110
+ end
111
+
112
+ private
113
+ def syntax
114
+ self.class.syntax
115
+ end
116
+
117
+ def get_subnode(key)
118
+ raise Glima::ConfigurationError, "Invalid key: #{key}" unless syntax.keyword?(key)
119
+ return instance_variable_get(syntax.instance_variable_name(key))
120
+ end
121
+
122
+ def create_subnode(keyword, value)
123
+ item_class = syntax.item_class(keyword)
124
+ if item_class.is_a?(Array)
125
+ return List.new(item_class.first, value)
126
+ elsif item_class == String
127
+ return value.to_s
128
+ else
129
+ return item_class.new(value)
130
+ end
131
+ end
132
+
133
+ end # class Base
134
+
135
+ # Parse Array object in YAML
136
+ class List < Base
137
+ include Enumerable
138
+
139
+ def initialize(item_class, array = [])
140
+ @original_hash = array
141
+ @configs = []
142
+ (array || []).each do |value|
143
+ item = item_class.new(value)
144
+ @configs << item
145
+ end
146
+ end
147
+
148
+ def [](key)
149
+ @configs.find {|c| c.name == key}
150
+ end
151
+
152
+ alias_method :get_subnode, :[]
153
+
154
+ def <<(conf)
155
+ @configs << conf
156
+ end
157
+
158
+ def to_hash # XXX: actually, it returns a Array
159
+ return @configs.map {|c| c.respond_to?(:to_hash) ? c.to_hash : c.to_s}
160
+ end
161
+
162
+ def each
163
+ @configs.each do |conf|
164
+ yield conf
165
+ end
166
+ end
167
+ end # List
168
+
169
+ ## concrete config classes
170
+
171
+ class General < Base
172
+ define_syntax :client_id => String,
173
+ :client_secret => String,
174
+ :token_store => String,
175
+ :context_store => String,
176
+ :zip_passwords_file => String,
177
+ :default_user => String
178
+ end # class General
179
+
180
+ # Top-Level Config
181
+ class Top < Base
182
+ define_syntax :general => General
183
+ end # class Top
184
+
185
+ def self.create_from_file(file_name)
186
+ unless File.exists?(File.expand_path(file_name))
187
+ raise Glima::ConfigurationError, "config file '#{file_name}' not found"
188
+ end
189
+ begin
190
+ return Top.create_from_yaml_file(file_name)
191
+ rescue Psych::SyntaxError => e
192
+ raise Glima::ConfigurationError, e.message
193
+ end
194
+ end
195
+
196
+ def self.create_from_string(string)
197
+ begin
198
+ return Top.create_from_yaml_string(string)
199
+ rescue Psych::SyntaxError => e
200
+ raise Glima::ConfigurationError, e.message
201
+ end
202
+ end
203
+
204
+ end # class Config
205
+ end # module Glima