imap-filter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+ require 'semver'
14
+
15
+ def s_version
16
+ SemVer.find.format "%M.%m.%p%s"
17
+ end
18
+
19
+ require 'juwelier'
20
+ Juwelier::Tasks.new do |gem|
21
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
22
+ gem.name = "imap-filter"
23
+ gem.homepage = "http://github.com/flajann2/imap-filter"
24
+ gem.license = "MIT"
25
+ gem.summary = %Q{IMAP Scriptable filter for one or multiple Email accounts.}
26
+ gem.description = %Q{
27
+ imap-filter is a Ruby implementation of an IMAP filtering application.
28
+ it can handle multiple IMAP accounts, and create IMAP folders automatically
29
+ where none exists.
30
+
31
+ The imap-filter DSL makes it easy to filter. You can also do "dry-runs"
32
+ to make sure what happens is what is expected.}
33
+
34
+ gem.email = "fred.mitchell@gmx.de"
35
+ gem.authors = ["Fred Mitchell"]
36
+ gem.version = s_version
37
+ gem.required_ruby_version = '>= 2.2'
38
+
39
+ # dependencies defined in Gemfile
40
+ end
41
+ Juwelier::RubygemsDotOrgTasks.new
42
+
43
+ require 'rspec/core'
44
+ require 'rspec/core/rake_task'
45
+ RSpec::Core::RakeTask.new(:spec) do |spec|
46
+ spec.pattern = FileList['spec/**/*_spec.rb']
47
+ end
48
+
49
+ desc "Code coverage detail"
50
+ task :simplecov do
51
+ ENV['COVERAGE'] = "true"
52
+ Rake::Task['spec'].execute
53
+ end
54
+
55
+ task :default => :spec
56
+
57
+ require 'yard'
58
+ YARD::Rake::YardocTask.new
data/bin/imap-filter ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ IMAP_PATH = File.expand_path '..', File.dirname(__FILE__)
4
+ IMAP_EXECUTABLE = File.expand_path 'bin/imap-filter', Dir.pwd
5
+
6
+ $:.unshift File.join([IMAP_PATH, 'lib'])
7
+
8
+ require 'imap-filter/cli'
9
+
10
+ ImapFilter::Cli::Main.start
11
+
@@ -0,0 +1,56 @@
1
+ # My Email accounts
2
+ account :gmx do
3
+ login ENV['GMX_EMAIL'], ENV['GMX_PASS']
4
+ serv "imap.gmx.net"
5
+ ssl true
6
+ auth :login
7
+ port 993
8
+ end
9
+
10
+ account :yahoo do
11
+ login ENV['YAHOO_EMAIL'], ENV['YAHOO_PASS']
12
+ serv 'imap.mail.yahoo.com'
13
+ auth :login
14
+ end
15
+
16
+ account :google do
17
+ login ENV['GOOGLE_EMAIL'], ENV['GOOGLE_PASS']
18
+ serv "imap.gmail.com"
19
+ auth :plain
20
+ end
21
+
22
+ filter :slashdot, 'gmx:INBOX', from: 'slashdot' do
23
+ mark :seen
24
+ cp 'google:INBOX'
25
+ mv 'gmx:Slashdot'
26
+ end
27
+
28
+ filter :github, 'gmx:INBOX' do
29
+ search do
30
+ from 'github.com'
31
+ unseen
32
+ end
33
+
34
+ cp 'google:news/Github'
35
+ mv 'gmx:Github'
36
+ end
37
+
38
+ filter :gitter, 'gmx:INBOX', from: 'gitter' do
39
+ mv 'gmx:Gitter'
40
+ end
41
+
42
+ filter :xing, 'gmx:INBOX', from: 'xing.com' do
43
+ mv 'gmx:XING'
44
+ unmark :seen
45
+ end
46
+
47
+ filter :neat, 'yahoo:NEAT', :unseen do
48
+ cp 'gmx:Lists/NEAT'
49
+ mark :seen
50
+ end
51
+
52
+ filter :spam, 'gmx:INBOX', from: ['news.brgmedia.com', 'travel.hoteltravel-email.com'] do
53
+ delete
54
+ end
55
+
56
+ activate :all
@@ -0,0 +1,110 @@
1
+ # Generated by juwelier
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+ # stub: imap-filter 0.0.2 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "imap-filter"
9
+ s.version = "0.0.2"
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
13
+ s.authors = ["Fred Mitchell"]
14
+ s.date = "2016-09-25"
15
+ s.description = "\n imap-filter is a Ruby implementation of an IMAP filtering application.\n it can handle multiple IMAP accounts, and create IMAP folders automatically\n where none exists.\n\n The imap-filter DSL makes it easy to filter. You can also do \"dry-runs\"\n to make sure what happens is what is expected."
16
+ s.email = "fred.mitchell@gmx.de"
17
+ s.executables = ["imap-filter"]
18
+ s.extra_rdoc_files = [
19
+ "LICENSE.txt",
20
+ "README.org"
21
+ ]
22
+ s.files = [
23
+ ".document",
24
+ ".rspec",
25
+ ".ruby-version",
26
+ ".semver",
27
+ "Gemfile",
28
+ "Gemfile.lock",
29
+ "LICENSE.txt",
30
+ "README.org",
31
+ "Rakefile",
32
+ "bin/imap-filter",
33
+ "examples/default.imap",
34
+ "imap-filter.gemspec",
35
+ "lib/imap-filter.rb",
36
+ "lib/imap-filter/cli.rb",
37
+ "lib/imap-filter/dsl.rb",
38
+ "lib/imap-filter/functionality.rb",
39
+ "lib/imap-filter/imap-filter.rb",
40
+ "lib/imap-filter/monkeypatches.rb",
41
+ "spec/imap-filter_spec.rb",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+ s.homepage = "http://github.com/flajann2/imap-filter"
45
+ s.licenses = ["MIT"]
46
+ s.required_ruby_version = Gem::Requirement.new(">= 2.2")
47
+ s.rubygems_version = "2.5.1"
48
+ s.summary = "IMAP Scriptable filter for one or multiple Email accounts."
49
+
50
+ if s.respond_to? :specification_version then
51
+ s.specification_version = 4
52
+
53
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
+ s.add_runtime_dependency(%q<semver>, ["~> 1"])
55
+ s.add_runtime_dependency(%q<awesome_print>, ["~> 1"])
56
+ s.add_runtime_dependency(%q<text-table>, ["~> 1"])
57
+ s.add_runtime_dependency(%q<thor>, ["~> 0"])
58
+ s.add_runtime_dependency(%q<colorize>, ["~> 0"])
59
+ s.add_development_dependency(%q<rspec>, ["~> 2"])
60
+ s.add_development_dependency(%q<yard>, ["~> 0"])
61
+ s.add_development_dependency(%q<rdoc>, ["~> 3"])
62
+ s.add_development_dependency(%q<bundler>, ["~> 1"])
63
+ s.add_development_dependency(%q<juwelier>, ["~> 2"])
64
+ s.add_development_dependency(%q<simplecov>, ["~> 0"])
65
+ s.add_development_dependency(%q<pry>, ["~> 0"])
66
+ s.add_development_dependency(%q<pry-byebug>, ["~> 3"])
67
+ s.add_development_dependency(%q<pry-doc>, ["~> 0"])
68
+ s.add_development_dependency(%q<pry-remote>, ["~> 0"])
69
+ s.add_development_dependency(%q<pry-rescue>, ["~> 1"])
70
+ s.add_development_dependency(%q<pry-stack_explorer>, ["~> 0"])
71
+ else
72
+ s.add_dependency(%q<semver>, ["~> 1"])
73
+ s.add_dependency(%q<awesome_print>, ["~> 1"])
74
+ s.add_dependency(%q<text-table>, ["~> 1"])
75
+ s.add_dependency(%q<thor>, ["~> 0"])
76
+ s.add_dependency(%q<colorize>, ["~> 0"])
77
+ s.add_dependency(%q<rspec>, ["~> 2"])
78
+ s.add_dependency(%q<yard>, ["~> 0"])
79
+ s.add_dependency(%q<rdoc>, ["~> 3"])
80
+ s.add_dependency(%q<bundler>, ["~> 1"])
81
+ s.add_dependency(%q<juwelier>, ["~> 2"])
82
+ s.add_dependency(%q<simplecov>, ["~> 0"])
83
+ s.add_dependency(%q<pry>, ["~> 0"])
84
+ s.add_dependency(%q<pry-byebug>, ["~> 3"])
85
+ s.add_dependency(%q<pry-doc>, ["~> 0"])
86
+ s.add_dependency(%q<pry-remote>, ["~> 0"])
87
+ s.add_dependency(%q<pry-rescue>, ["~> 1"])
88
+ s.add_dependency(%q<pry-stack_explorer>, ["~> 0"])
89
+ end
90
+ else
91
+ s.add_dependency(%q<semver>, ["~> 1"])
92
+ s.add_dependency(%q<awesome_print>, ["~> 1"])
93
+ s.add_dependency(%q<text-table>, ["~> 1"])
94
+ s.add_dependency(%q<thor>, ["~> 0"])
95
+ s.add_dependency(%q<colorize>, ["~> 0"])
96
+ s.add_dependency(%q<rspec>, ["~> 2"])
97
+ s.add_dependency(%q<yard>, ["~> 0"])
98
+ s.add_dependency(%q<rdoc>, ["~> 3"])
99
+ s.add_dependency(%q<bundler>, ["~> 1"])
100
+ s.add_dependency(%q<juwelier>, ["~> 2"])
101
+ s.add_dependency(%q<simplecov>, ["~> 0"])
102
+ s.add_dependency(%q<pry>, ["~> 0"])
103
+ s.add_dependency(%q<pry-byebug>, ["~> 3"])
104
+ s.add_dependency(%q<pry-doc>, ["~> 0"])
105
+ s.add_dependency(%q<pry-remote>, ["~> 0"])
106
+ s.add_dependency(%q<pry-rescue>, ["~> 1"])
107
+ s.add_dependency(%q<pry-stack_explorer>, ["~> 0"])
108
+ end
109
+ end
110
+
@@ -0,0 +1,17 @@
1
+ require 'thor'
2
+ require 'semver'
3
+ require 'pp'
4
+ require 'ap'
5
+ require 'colorize'
6
+ require 'awesome_print'
7
+ require 'net/imap'
8
+ require 'forwardable'
9
+
10
+ module ImapFilter
11
+ end
12
+
13
+ require_relative 'imap-filter/monkeypatches'
14
+ require_relative 'imap-filter/dsl'
15
+ require_relative 'imap-filter/functionality'
16
+ require_relative 'imap-filter/imap-filter'
17
+ require_relative 'imap-filter/cli'
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ require 'imap-filter'
3
+
4
+ include ImapFilter::DSL
5
+
6
+ module ImapFilter
7
+ module Cli
8
+ class Main < Thor
9
+ class_option :verbose, type: :numeric, banner: '[1|2|3|4]', aliases: '-v', default: 0
10
+ @@default_script = ENV['IMAPF_IMAP_FILE'] || 'default.imap'
11
+
12
+ desc 'filter [script]', "Run the powerplay script. Default #{@@default_script}"
13
+ option :dryrun, type: :boolean, aliases: '-u', desc: "Dry run, do not actually execute."
14
+ option :test, type: :boolean, aliases: '-t', desc: "Test IMAP accounts only."
15
+ option :filters, type: :array, aliases: '-f', banner: %(<FILT1>[ FILT2 FILT3...]),
16
+ desc: "Run specified filters. The default is to run all of them."
17
+ def filter(script = @@default_script)
18
+ _global[:options] = options
19
+ puts "script %s " % [script] if _options[:verbose] >= 1
20
+ load script, true
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,321 @@
1
+ # coding: utf-8
2
+ require 'imap-filter'
3
+
4
+ module ImapFilter
5
+ module DSL
6
+ @@global_config = {}
7
+
8
+ def _global
9
+ @@global_config
10
+ end
11
+
12
+ def _options
13
+ DSL::_global[:options]
14
+ end
15
+
16
+ def _accounts
17
+ DSL::_global[:accounts] ||= {}
18
+ end
19
+
20
+ def _filters
21
+ DSL::_global[:filters] ||= {}
22
+ end
23
+
24
+ class Dsl
25
+ attr :name, :desc
26
+
27
+ def initialize(name, desc=nil, &ignore)
28
+ @name = name
29
+ @desc = desc
30
+ end
31
+ end
32
+
33
+ class Account < Dsl
34
+ attr :name, :userid, :pass, :fqdn, :use_ssl, :use_port, :auth_type
35
+ attr :imap, :delim
36
+
37
+ def login userid, password
38
+ @userid = userid
39
+ @pass = password
40
+ @use_ssl = true
41
+ @use_port = nil
42
+ @auth_type = 'PLAIN'
43
+ end
44
+
45
+ def serv fqdn
46
+ @fqdn = fqdn
47
+ end
48
+
49
+ def ssl t
50
+ @use_ssl = t
51
+ end
52
+
53
+ def port p
54
+ @use_port = p
55
+ end
56
+
57
+ def auth type
58
+ @auth_type = type.to_s.upcase
59
+ end
60
+
61
+ def initialize(name, &block)
62
+ super
63
+ @name = name
64
+ instance_eval( &block )
65
+ _accounts[name] = self
66
+ end
67
+
68
+ def to_s
69
+ "SERV #{fqdn} USER #{userid} SSL #{use_ssl} PORT #{ use_port ? use_port : '<default>'} AUTH #{auth_type} >DELIM #{delim}"
70
+ end
71
+
72
+ # connects and logs in
73
+ def _open_connection
74
+ print "\n *** connect #{fqdn} port '#{use_port}' ssl #{use_ssl}".light_cyan unless _options[:verbose] < 2
75
+ unless use_port.nil?
76
+ @imap = Net::IMAP.new(fqdn, port: use_port, ssl: use_ssl)
77
+ else
78
+ @imap = Net::IMAP.new(fqdn, ssl: use_ssl)
79
+ end
80
+
81
+ print "\n *** auth #{userid} pass #{pass}...".light_cyan unless _options[:verbose] < 2
82
+ imap.authenticate(auth_type, userid, pass)
83
+ @delim = imap.list('', '').first.delim
84
+ end
85
+
86
+ def _close_connection
87
+ imap.close
88
+ end
89
+ end
90
+
91
+ class Filter < Dsl
92
+ attr :mbox, :directives, :actions
93
+ OPS = [:or, :not, :new]
94
+ MARKS = {
95
+ seen: :Seen,
96
+ read: :Seen,
97
+ unread: :Unseen,
98
+ unseen: :Unseen,
99
+ deleted: :Deleted,
100
+ flagged: :Flagged
101
+ }
102
+ DIRECTIVES = {
103
+ all: 'ALL',
104
+ new: 'NEW',
105
+ recent: 'RECENT',
106
+ seen: 'SEEN',
107
+ read: 'SEEN',
108
+ unseen: 'UNSEEN',
109
+ unread: 'UNSEEN',
110
+ answered: 'ANSWERED',
111
+ unanswered: 'UNANSWERED',
112
+ deleted: 'DELETED',
113
+ undeleted: 'UNDELETED',
114
+ draft: 'DRAFT',
115
+ undraft: 'UNDRAFT',
116
+ flagged: 'FLAGGED',
117
+ unflagged: 'UNFLAGGED',
118
+ }
119
+
120
+ def list *a, **h
121
+ @actions << [:list, a, h]
122
+ end
123
+
124
+ def move to_mbox
125
+ @actions << [:move, to_mbox]
126
+ end
127
+ alias mv move
128
+
129
+ def copy to_mbox
130
+ @actions << [:copy, to_mbox]
131
+ end
132
+ alias cp copy
133
+
134
+ def delete
135
+ @actions << [:delete]
136
+ end
137
+
138
+ def mark *flags, custom: false
139
+ flags.each do |f|
140
+ raise "Illegal flag #{f}" unless MARKS.member? f
141
+ end unless custom
142
+ @actions << [:mark] + flags.map{ |f| MARKS[f] || f }
143
+ end
144
+ alias store mark
145
+
146
+ def unmark *flags, custom: false
147
+ flags.each do |f|
148
+ raise "Illegal flag #{f}" unless MARKS.member? f
149
+ end unless custom
150
+ @actions << [:unmark] + flags.map{ |f| MARKS[f] || f }
151
+ end
152
+ alias unstore unmark
153
+
154
+ def search &block
155
+ def before d
156
+ directives << 'BEFORE' << d
157
+ end
158
+
159
+ def body s
160
+ directives << 'BODY'<< s
161
+ end
162
+
163
+ def cc s
164
+ directives << 'CC' << s
165
+ end
166
+
167
+ def bcc s
168
+ directives << 'BCC' << s
169
+ end
170
+
171
+ def from s
172
+ directives << 'FROM' << s
173
+ end
174
+
175
+ def op *a
176
+ a.each { |x|
177
+ raise "illegal operator #{x}" unless OPS.member? x
178
+ directives << x.to_s.upcase
179
+ }
180
+ end
181
+
182
+ def on d
183
+ directives << 'ON' << d
184
+ end
185
+
186
+ def since d
187
+ directives << 'SINCE' << d
188
+ end
189
+
190
+ def senton d
191
+ directives << 'SENTON' << d
192
+ end
193
+
194
+ def sentsince d
195
+ directives << 'SENTSINCE' << d
196
+ end
197
+
198
+ def sentbefore d
199
+ directives << 'SENTBEFORE' << d
200
+ end
201
+
202
+ def smaller n
203
+ directives << 'SMALLER' << n
204
+ end
205
+
206
+ def subject s
207
+ directives << 'SUBJECT' << s
208
+ end
209
+
210
+ def text s
211
+ directives << 'TEXT' << s
212
+ end
213
+
214
+ def to s
215
+ directives << 'TO' << s
216
+ end
217
+
218
+ def all
219
+ directives << 'ALL'
220
+ end
221
+
222
+ def answered
223
+ directives << 'ANSWERED'
224
+ end
225
+
226
+ def unanswered
227
+ directives << 'UNANSWERED'
228
+ end
229
+
230
+ def deleted
231
+ directives << 'DELETED'
232
+ end
233
+
234
+ def undeleted
235
+ directives << 'UNDELETED'
236
+ end
237
+
238
+ def draft
239
+ directives << 'DRAFT'
240
+ end
241
+
242
+ def undraft
243
+ directives << 'UNDRAFT'
244
+ end
245
+
246
+ def flagged
247
+ directives << 'FLAGGED'
248
+ end
249
+
250
+ def unflagged
251
+ directives << 'UNFLAGGED'
252
+ end
253
+
254
+ def seen
255
+ directives << 'SEEN'
256
+ end
257
+ alias red seen
258
+
259
+ def unseen
260
+ directives << 'UNSEEN'
261
+ end
262
+ alias unread unseen
263
+
264
+ def keyword key
265
+ directives << 'KEYWORD' << key
266
+ end
267
+
268
+ def unkeyword key
269
+ directives << 'UNKEYWORD' << key
270
+ end
271
+
272
+ instance_eval &block
273
+ end
274
+
275
+ def massage directives
276
+ if directives.is_a?(Hash)
277
+ directives.map{ |k, v|
278
+ unless v.is_a? Array
279
+ [k.to_s.upcase, v]
280
+ else
281
+ ['OR',
282
+ v.map{ |va|
283
+ [k.to_s.upcase, va]
284
+ }].flatten
285
+ end
286
+ }.flatten
287
+ elsif directives.is_a?(Symbol)
288
+ DIRECTIVES[directives]
289
+ else
290
+ directives
291
+ end
292
+ end
293
+
294
+ # note that directives can be either a hash or a single symbol
295
+ def initialize(name, mbox, directives=[], &block)
296
+ super(name)
297
+ @mbox = mbox
298
+ @directives = massage directives
299
+ @actions = []
300
+ instance_eval &block
301
+ _filters[name] = self
302
+ end
303
+
304
+ def to_s
305
+ "MBOX #{mbox} DIRECTIVES #{directives}"
306
+ end
307
+ end
308
+
309
+ def account name, &block
310
+ Account.new name, &block
311
+ end
312
+
313
+ def filter name, mbox, singledir=nil, **directives, &block
314
+ Filter.new name, mbox, (singledir || directives), &block
315
+ end
316
+
317
+ def activate filters
318
+ Functionality.run_filters filters
319
+ end
320
+ end
321
+ end