imap-filter 0.0.2

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,128 @@
1
+ # coding: utf-8
2
+ module ImapFilter
3
+ module Functionality
4
+ include Forwardable
5
+ include ImapFilter::DSL
6
+
7
+ FULL ='(UID RFC822.SIZE ENVELOPE BODY.PEEK[TEXT])'
8
+ BODYTEXT = 'BODY[TEXT]'
9
+ SUBJECTPEEKLIST = '(UID RFC822.SIZE BODY.PEEK[HEADER.FIELDS (SUBJECT)])'
10
+ SUBJECTLIST = 'BODY[HEADER.FIELDS (SUBJECT)]'
11
+ class FunctFilter
12
+ extend Forwardable
13
+
14
+ attr :dfilt, :seq, :acc
15
+
16
+ def_delegators :@dfilt, :mbox, :directives, :actions
17
+
18
+ def initialize filt
19
+ @dfilt = filt
20
+ @seq = nil
21
+ end
22
+
23
+ # These strings come from the DSL in the form of
24
+ # "acct:mbox_path". Reslove and return the actual
25
+ # account object and mbox path as [account_ob, mbox]
26
+ def parse_and_resolve_account_mbox_string ambox, default_account = nil
27
+ a, b = ambox.split ':'
28
+ a, b = [nil, a] if b.nil?
29
+ a = nil if a == ''
30
+ acc = a.nil? ? default_account : _accounts[a.to_sym]
31
+ mbox = b
32
+ [acc, mbox]
33
+ end
34
+
35
+ def select_email
36
+ @acc, box = parse_and_resolve_account_mbox_string mbox
37
+ acc.imap.select box
38
+ @seq = acc.imap.search directives
39
+ end
40
+
41
+ def ensure_mailbox account, mailbox
42
+ begin
43
+ account.imap.create mailbox
44
+ rescue Net::IMAP::NoResponseError => e
45
+ # we ignore this because it -- probably -- means the mailbox already exists.
46
+ puts " *** ignored mailbox error: #{e}".red unless _options[:verbose] < 1
47
+ end
48
+ end
49
+
50
+ def process_actions
51
+ actions.each do |action|
52
+ send *action
53
+ end
54
+ end
55
+
56
+ def subject_list
57
+ unless seq.empty?
58
+ acc.imap.fetch(seq, SUBJECTPEEKLIST).map do |subject|
59
+ subject.attr[SUBJECTLIST].to_s.strip.tr("\n\r", '')
60
+ end
61
+ else
62
+ []
63
+ end
64
+ end
65
+
66
+ def list *a, **h
67
+ subject_list.each do |subject|
68
+ puts subject.attr[subj].to_s.strip.tr("\n\r", '').light_yellow
69
+ end unless seq.empty?
70
+ end
71
+
72
+ def _cross_account_mvcp op, dest_acc, dest_mbox
73
+ ensure_mailbox dest_acc, dest_mbox
74
+ begin
75
+ acc.imap.fetch(seq, FULL).each do |fdat|
76
+ unless _options[:verbose] < 2
77
+ print " >>".yellow
78
+ puts " seq #{fdat.seqno} #{fdat.attr['ENVELOPE']['subject']} -> #{dest_acc.name}:#{dest_mbox}".light_blue
79
+ end
80
+ raw = fdat.attr['ENVELOPE'].email_header + fdat.attr[BODYTEXT]
81
+ dest_acc.imap.append dest_mbox, raw, fdat.attr['FLAGS']
82
+ end
83
+ rescue => e
84
+ puts "ERROR: #{e} -- perhaps you did a move or delete operation before copy?".light_red
85
+ exit 10
86
+ end
87
+ end
88
+
89
+ def _mvcp op, destination
90
+ raise "Illegal operation #{op}" unless [:copy, :move].member? op
91
+
92
+ dest_acc, dest_mbox = parse_and_resolve_account_mbox_string destination
93
+ ensure_mailbox dest_acc, dest_mbox
94
+
95
+ if dest_acc == acc # in-account move
96
+ dest_acc.imap.send op, seq, dest_mbox
97
+ else # move or copy to different account
98
+ _cross_account_mvcp op, dest_acc, dest_mbox
99
+ end unless seq.empty?
100
+ end
101
+
102
+ def move destination
103
+ puts " move from #{acc.name} to #{destination}".light_blue unless _options[:verbose] < 1
104
+ _mvcp :move, destination unless _options[:dryrun]
105
+ end
106
+
107
+ def copy destination
108
+ puts " copy from #{acc.name} to #{destination}".light_blue unless _options[:verbose] < 1
109
+ _mvcp :copy, destination unless _options[:dryrun]
110
+ end
111
+
112
+ def delete
113
+ puts " delete from #{acc.name}".light_blue unless _options[:verbose] < 1
114
+ mark :Deleted unless _options[:dryrun]
115
+ end
116
+
117
+ def mark *flags
118
+ puts " mark #{flags} in #{acc.name}".light_blue unless _options[:verbose] < 1
119
+ acc.imap.store seq, '+FLAGS.SILENT', flags unless seq.empty? or _options[:dryrun]
120
+ end
121
+
122
+ def unmark *flags
123
+ puts " unmark #{flags} in #{acc.name}".light_blue unless _options[:verbose] < 1
124
+ acc.imap.store seq, '-FLAGS.SILENT', flags unless seq.empty? or _options[:dryrun]
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,108 @@
1
+ # coding: utf-8
2
+ require 'imap-filter'
3
+
4
+ include ImapFilter::DSL
5
+
6
+ module ImapFilter
7
+ module Functionality
8
+ STATUS = {messages: 'MESSAGES', recent: 'RECENT', unseen: 'UNSEEN'}
9
+ ISTAT = STATUS.map{ |k, v| [v, k] }.to_h
10
+
11
+ def self.show_imap_plan
12
+ puts '====== Accounts'.light_yellow
13
+ _accounts.each do |name, account|
14
+ print " #{name}: ".light_green
15
+ print account.to_s.light_blue
16
+ puts
17
+ end
18
+ puts '====== Filters'.light_yellow
19
+ _filters.each do |name, filter|
20
+ print " #{name}: ".light_green
21
+ print filter.to_s.light_blue
22
+ puts
23
+ end
24
+ end
25
+
26
+ # List all mboxes of given account and their statuses
27
+ def self.list_mboxes account
28
+ account.imap.list('', '*')
29
+ .map { |m| [m['name'], m['attr']] }
30
+ .map { |mbox, attr|
31
+ begin
32
+ [mbox,
33
+ account.imap.status(mbox, STATUS.values)
34
+ .map{ |k, v| "#{ISTAT[k]}:#{v}" }
35
+ .join(' '),
36
+ attr]
37
+ rescue
38
+ nil
39
+ end }
40
+ .compact
41
+ end
42
+
43
+ def self.login_imap_accounts test: false
44
+ puts "====== #{test ? 'Test' : 'Login'} Accounts".light_yellow
45
+ _accounts.each do |name, account|
46
+ print " #{name}...".light_white
47
+ begin
48
+ account._open_connection
49
+ puts "SUCCESS, delim #{account.delim}".light_green
50
+ list_mboxes(account).each do |mbox, stat, attr|
51
+ print " #{mbox}".light_blue
52
+ print " #{stat}".light_red
53
+ puts " #{attr}".light_cyan
54
+ end unless _options[:verbose] < 2
55
+ rescue => e
56
+ puts "FAILED: #{e}".light_red
57
+ exit unless test
58
+ end
59
+ end
60
+ end
61
+
62
+ def self.list_of_filters_to_run
63
+ unless _options[:filters].nil?
64
+ _options[:filters].map{ |f| f.to_sym }
65
+ else
66
+ _filters.keys
67
+ end
68
+ end
69
+
70
+ # do the selection based on directives
71
+ # then perform the actions on the set selected.
72
+ # optimize for copy/moves that are to the same account.
73
+ def self.run_filter filt
74
+ f = FunctFilter.new _filters[filt]
75
+ f.select_email
76
+
77
+ unless _options[:verbose] < 1
78
+ puts "====== Email to be processed by #{filt}".light_yellow
79
+ f.subject_list.each do |subject|
80
+ print ' ##'.yellow
81
+ puts subject.light_blue
82
+ end
83
+ end
84
+
85
+ f.process_actions
86
+ f.acc.imap.expunge unless _options[:dryrun]
87
+ end
88
+
89
+ def self.execute_filters
90
+ #login_imap_accounts
91
+ list_of_filters_to_run.each do |f|
92
+ print "Running filter: ".light_white
93
+ puts "#{f}".light_yellow
94
+ run_filter f
95
+ end
96
+ end
97
+
98
+ def self.run_filters filters
99
+ show_imap_plan unless _options[:verbose] < 1
100
+ if _options[:test]
101
+ login_imap_accounts test: true
102
+ else
103
+ login_imap_accounts
104
+ execute_filters
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,37 @@
1
+ class Net::IMAP::Envelope
2
+ CRLF = "\r\n"
3
+ HEDCNS = [
4
+ ['Date:', ->(s){ s.date } ],
5
+ ['Subject:', ->(s){ s.subject } ],
6
+ ['Date:', ->(s){ s.date } ],
7
+ ['Message-ID:', ->(s){ s.message_id }],
8
+ ['Sender:', ->(s){
9
+ s.sender.map{ |f|
10
+ "#{f.name} <#{f.mailbox}@#{f.host}>"
11
+ }.join ',' unless s.sender.nil? } ],
12
+ ['From:', ->(s){
13
+ s.from.map{ |f|
14
+ "#{f.name} <#{f.mailbox}@#{f.host}>"
15
+ }.join ',' unless s.from.nil? } ],
16
+ ['To:', ->(s){
17
+ s.to.map{ |f|
18
+ "#{f.name} <#{f.mailbox}@#{f.host}>"
19
+ }.join ',' unless s.to.nil? } ],
20
+ ['Cc:', ->(s){
21
+ s.cc.map{ |f|
22
+ "#{f.name} <#{f.mailbox}@#{f.host}>"
23
+ }.join ',' unless s.cc.nil? } ],
24
+ ['Bcc:', ->(s){
25
+ s.bcc.map{ |f|
26
+ "#{f.name} <#{f.mailbox}@#{f.host}>"
27
+ }.join ',' unless s.bcc.nil? } ],
28
+ ['In-Reply-To:', ->(s){ s.in_reply_to }],
29
+ ].to_h
30
+
31
+ def email_header
32
+ HEDCNS.map{ |field, fun| "#{field} #{fun.(self)}" }
33
+ .join CRLF
34
+ end
35
+ end
36
+
37
+
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "ImapFilter" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ require 'simplecov'
2
+
3
+ module SimpleCov::Configuration
4
+ def clean_filters
5
+ @filters = []
6
+ end
7
+ end
8
+
9
+ SimpleCov.configure do
10
+ clean_filters
11
+ load_adapter 'test_frameworks'
12
+ end
13
+
14
+ ENV["COVERAGE"] && SimpleCov.start do
15
+ add_filter "/.rvm/"
16
+ end
17
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
18
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
19
+
20
+ require 'rspec'
21
+ require 'imap-filter'
22
+
23
+ # Requires supporting files with custom matchers and macros, etc,
24
+ # in ./support/ and its subdirectories.
25
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
26
+
27
+ RSpec.configure do |config|
28
+
29
+ end
metadata ADDED
@@ -0,0 +1,311 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: imap-filter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Fred Mitchell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: semver
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: awesome_print
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: text-table
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: colorize
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rdoc
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3'
125
+ - !ruby/object:Gem::Dependency
126
+ name: bundler
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1'
139
+ - !ruby/object:Gem::Dependency
140
+ name: juwelier
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: pry
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: pry-byebug
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '3'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '3'
195
+ - !ruby/object:Gem::Dependency
196
+ name: pry-doc
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: pry-remote
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: pry-rescue
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - "~>"
228
+ - !ruby/object:Gem::Version
229
+ version: '1'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - "~>"
235
+ - !ruby/object:Gem::Version
236
+ version: '1'
237
+ - !ruby/object:Gem::Dependency
238
+ name: pry-stack_explorer
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - "~>"
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - "~>"
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
251
+ description: |2-
252
+
253
+ imap-filter is a Ruby implementation of an IMAP filtering application.
254
+ it can handle multiple IMAP accounts, and create IMAP folders automatically
255
+ where none exists.
256
+
257
+ The imap-filter DSL makes it easy to filter. You can also do "dry-runs"
258
+ to make sure what happens is what is expected.
259
+ email: fred.mitchell@gmx.de
260
+ executables:
261
+ - imap-filter
262
+ extensions: []
263
+ extra_rdoc_files:
264
+ - LICENSE.txt
265
+ - README.org
266
+ files:
267
+ - ".document"
268
+ - ".rspec"
269
+ - ".ruby-version"
270
+ - ".semver"
271
+ - Gemfile
272
+ - Gemfile.lock
273
+ - LICENSE.txt
274
+ - README.org
275
+ - Rakefile
276
+ - bin/imap-filter
277
+ - examples/default.imap
278
+ - imap-filter.gemspec
279
+ - lib/imap-filter.rb
280
+ - lib/imap-filter/cli.rb
281
+ - lib/imap-filter/dsl.rb
282
+ - lib/imap-filter/functionality.rb
283
+ - lib/imap-filter/imap-filter.rb
284
+ - lib/imap-filter/monkeypatches.rb
285
+ - spec/imap-filter_spec.rb
286
+ - spec/spec_helper.rb
287
+ homepage: http://github.com/flajann2/imap-filter
288
+ licenses:
289
+ - MIT
290
+ metadata: {}
291
+ post_install_message:
292
+ rdoc_options: []
293
+ require_paths:
294
+ - lib
295
+ required_ruby_version: !ruby/object:Gem::Requirement
296
+ requirements:
297
+ - - ">="
298
+ - !ruby/object:Gem::Version
299
+ version: '2.2'
300
+ required_rubygems_version: !ruby/object:Gem::Requirement
301
+ requirements:
302
+ - - ">="
303
+ - !ruby/object:Gem::Version
304
+ version: '0'
305
+ requirements: []
306
+ rubyforge_project:
307
+ rubygems_version: 2.5.1
308
+ signing_key:
309
+ specification_version: 4
310
+ summary: IMAP Scriptable filter for one or multiple Email accounts.
311
+ test_files: []