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.
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