imap-filter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []