imap_processor 1.3 → 1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,231 @@
1
+ require 'imap_processor/client'
2
+ require 'fileutils'
3
+
4
+ require 'rubygems'
5
+
6
+ begin
7
+ require 'rbayes'
8
+ rescue LoadError
9
+ # ignoring
10
+ class RBayes
11
+ def initialize *args
12
+ # nothing to do
13
+ end
14
+ end
15
+ end
16
+
17
+ ##
18
+ # IMAPLearn flags messages per-folder based on what you've flagged before.
19
+ #
20
+ # aka part three of my Plan for Total Email Domination.
21
+
22
+ class IMAPProcessor::Learn < IMAPProcessor::Client
23
+
24
+ ##
25
+ # IMAP keyword for learned messages
26
+
27
+ LEARN_KEYWORD = 'IMAPLEARN_FLAGGED'
28
+
29
+ ##
30
+ # IMAP keyword for tasty messages
31
+
32
+ TASTY_KEYWORD = LEARN_KEYWORD + '_TASTY'
33
+
34
+ ##
35
+ # IMAP keyword for bland messages
36
+
37
+ BLAND_KEYWORD = LEARN_KEYWORD + '_BLAND'
38
+
39
+ ##
40
+ # Handles processing of +args+.
41
+
42
+ def self.process_args(args)
43
+ @@options[:Threshold] = [0.85, 'Tastiness threshold not set']
44
+
45
+ super __FILE__, args, {} do |opts, options|
46
+ opts.on("-t", "--threshold THRESHOLD",
47
+ "Flag messages more tasty than THRESHOLD",
48
+ "Default: #{options[:Threshold].inspect}",
49
+ "Options file name: Threshold", Float) do |threshold|
50
+ options[:Threshold] = threshold
51
+ end
52
+ end
53
+ end
54
+
55
+ ##
56
+ # Creates a new IMAPLearn from +options+.
57
+ #
58
+ # Options include:
59
+ # +:Threshold+:: Tastiness threshold for flagging
60
+ #
61
+ # and all options from IMAPClient
62
+
63
+ def initialize(options)
64
+ super
65
+
66
+ @db_root = File.join '~', '.imap_learn',
67
+ "#{options[:User]}@#{options[:Host]}:#{options[:Port]}"
68
+ @db_root = File.expand_path @db_root
69
+
70
+ @threshold = options[:Threshold]
71
+
72
+ @classifiers = Hash.new do |h,k|
73
+ filter_db = File.join @db_root, "#{k}.db"
74
+ FileUtils.mkdir_p File.dirname(filter_db)
75
+ h[k] = RBayes.new filter_db
76
+ end
77
+
78
+ @unlearned_flagged = []
79
+ @tasty_unflagged = []
80
+ @bland_flagged = []
81
+ @tasty_unlearned = []
82
+ @bland_unlearned = []
83
+
84
+ @noop = false
85
+ end
86
+
87
+ ##
88
+ # Flags tasty messages from all selected mailboxes.
89
+
90
+ def run
91
+ log "Flagging tasty messages"
92
+
93
+ message_count = 0
94
+ mailboxes = find_mailboxes
95
+
96
+ mailboxes.each do |mailbox|
97
+ @mailbox = mailbox
98
+ @imap.select @mailbox
99
+ log "Selected #{@mailbox}"
100
+
101
+ message_count += process_unlearned_flagged
102
+ message_count += process_tasty_unflagged
103
+ message_count += process_bland_flagged
104
+ message_count += process_unlearned
105
+ end
106
+
107
+ log "Done. Found #{message_count} messages in #{mailboxes.length} mailboxes"
108
+ end
109
+
110
+ private
111
+
112
+ ##
113
+ # Returns an Array of tasty message sequence numbers.
114
+
115
+ def unlearned_flagged_in_curr
116
+ log "Finding unlearned, flagged messages"
117
+
118
+ @unlearned_flagged = @imap.search [
119
+ 'FLAGGED',
120
+ 'NOT', 'KEYWORD', LEARN_KEYWORD
121
+ ]
122
+
123
+ update_db @unlearned_flagged, :add_tasty
124
+
125
+ @unlearned_flagged.length
126
+ end
127
+
128
+ ##
129
+ # Returns an Array of message sequence numbers that should be marked as
130
+ # bland.
131
+
132
+ def tasty_unflagged_in_curr
133
+ log "Finding messages re-marked bland"
134
+
135
+ @bland_flagged = @imap.search [
136
+ 'NOT', 'FLAGGED',
137
+ 'KEYWORD', TASTY_KEYWORD
138
+ ]
139
+
140
+ update_db @tasty_unflagged, :remove_tasty, :add_bland
141
+
142
+ @bland_flagged.length
143
+ end
144
+
145
+ ##
146
+ # Returns an Array of tasty message sequence numbers that should be marked
147
+ # as tasty.
148
+
149
+ def bland_flagged_in_curr
150
+ log "Finding messages re-marked tasty"
151
+ @bland_flagged = @imap.search [
152
+ 'FLAGGED',
153
+ 'KEYWORD', BLAND_KEYWORD
154
+ ]
155
+
156
+ update_db @bland_flagged, :remove_bland, :add_tasty
157
+
158
+ @bland_flagged.length
159
+ end
160
+
161
+ ##
162
+ # Returns two Arrays, one of tasty message sequence numbers and one of bland
163
+ # message sequence numbers.
164
+
165
+ def unlearned_in_curr
166
+ log "Learning new, unmarked messages"
167
+ unlearned = @imap.search [
168
+ 'NOT', 'KEYWORD', LEARN_KEYWORD
169
+ ]
170
+
171
+ tasty = []
172
+ bland = []
173
+
174
+ chunk unlearned do |messages|
175
+ bodies = @imap.fetch messages, 'RFC822'
176
+ bodies.each do |body|
177
+ text = body.attr['RFC822']
178
+ bucket = classify(text) ? tasty : bland
179
+ bucket << body.seqno
180
+ end
181
+ end
182
+
183
+ update_db tasty, :add_tasty
184
+ update_db bland, :add_bland
185
+
186
+ tasty.length + bland.length
187
+ end
188
+
189
+ def chunk(messages, size = 20)
190
+ messages = messages.dup
191
+
192
+ until messages.empty? do
193
+ chunk = messages.slice! 0, size
194
+ yield chunk
195
+ end
196
+ end
197
+
198
+ ##
199
+ # Returns true if +text+ is "tasty"
200
+
201
+ def classify(text)
202
+ rating = @classifiers[@mailbox].rate text
203
+ rating > @threshold
204
+ end
205
+
206
+ def update_db(messages, *actions)
207
+ chunk messages do |chunk|
208
+ bodies = @imap.fetch chunk, 'RFC822'
209
+ bodies.each do |body|
210
+ text = body.attr['RFC822']
211
+ actions.each do |action|
212
+ @classifiers[@mailbox].update_db_with text, action
213
+ case action
214
+ when :add_bland then
215
+ @imap.store body.seqno, '+FLAG.SILENT',
216
+ [LEARN_KEYWORD, BLAND_KEYWORD]
217
+ when :add_tasty then
218
+ @imap.store body.seqno, '+FLAG.SILENT',
219
+ [:Flagged, LEARN_KEYWORD, TASTY_KEYWORD]
220
+ when :remove_bland then
221
+ @imap.store body.seqno, '-FLAG.SILENT', [BLAND_KEYWORD]
222
+ when :remove_tasty then
223
+ @imap.store body.seqno, '-FLAG.SILENT', [TASTY_KEYWORD]
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+ end
231
+
@@ -0,0 +1,25 @@
1
+ require 'imap_processor'
2
+
3
+ ##
4
+ # Creates folders in IMAP.
5
+
6
+ class IMAPProcessor::Mkdir < IMAPProcessor
7
+ attr_reader :sep
8
+
9
+ def self.process_args(args)
10
+ super __FILE__, args
11
+ end
12
+
13
+ def initialize(options)
14
+ super
15
+
16
+ @imap = connect.imap
17
+ end
18
+
19
+ def run
20
+ ARGV.each do |mailbox|
21
+ create_mailbox mailbox
22
+ end
23
+ end
24
+ end
25
+
@@ -1,3 +1,4 @@
1
+ require 'rubygems'
1
2
  require 'minitest/autorun'
2
3
  require 'imap_processor'
3
4
  require 'time'
@@ -41,6 +42,8 @@ class TestIMAPProcessor < MiniTest::Unit::TestCase
41
42
  @ip.instance_variable_set :@imap, @imap
42
43
 
43
44
  @delim = @imap.list('', 'INBOX').first.delim
45
+
46
+ @NI = Net::IMAP
44
47
  end
45
48
 
46
49
  def teardown
@@ -60,6 +63,40 @@ class TestIMAPProcessor < MiniTest::Unit::TestCase
60
63
  test.setup
61
64
  test.teardown
62
65
 
66
+ def test_capability
67
+ res =
68
+ @NI::UntaggedResponse.new('OK',
69
+ @NI::ResponseText.new(
70
+ @NI::ResponseCode.new('CAPABILITY',
71
+ 'IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE AUTH=PLAIN'),
72
+ 'Dovecot ready.'),
73
+ "OK [CAPABAILITY ...] Dovecot ready.\r\n")
74
+
75
+ cap = @ip.capability @imap, res
76
+
77
+ assert_equal \
78
+ %w[IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE AUTH=PLAIN],
79
+ cap
80
+ end
81
+
82
+ def test_capability_no_cap
83
+ res =
84
+ @NI::TaggedResponse.new('RUBY0001', 'OK',
85
+ @NI::ResponseText.new(nil,
86
+ 'User logged in'),
87
+ "RUBY0001 OK User logged in\r\n")
88
+
89
+ cap = @ip.capability @imap, res
90
+
91
+ assert_includes cap, 'IMAP4REV1'
92
+ end
93
+
94
+ def test_capability_no_res
95
+ cap = @ip.capability @imap
96
+
97
+ assert_includes cap, 'IMAP4REV1'
98
+ end
99
+
63
100
  def test_create_mailbox
64
101
  @imap.create "directory#{@delim}"
65
102
 
metadata CHANGED
@@ -1,114 +1,163 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: imap_processor
3
- version: !ruby/object:Gem::Version
4
- version: "1.3"
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.5'
5
5
  platform: ruby
6
- authors:
6
+ authors:
7
7
  - Eric Hodel
8
+ - Ryan Davis
8
9
  autorequire:
9
10
  bindir: bin
10
- cert_chain:
11
+ cert_chain:
11
12
  - |
12
13
  -----BEGIN CERTIFICATE-----
13
- MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRAwDgYDVQQDDAdkcmJy
14
- YWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZFgNu
15
- ZXQwHhcNMDcxMjIxMDIwNDE0WhcNMDgxMjIwMDIwNDE0WjBBMRAwDgYDVQQDDAdk
16
- cmJyYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZ
17
- FgNuZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbbgLrGLGIDE76
18
- LV/cvxdEzCuYuS3oG9PrSZnuDweySUfdp/so0cDq+j8bqy6OzZSw07gdjwFMSd6J
19
- U5ddZCVywn5nnAQ+Ui7jMW54CYt5/H6f2US6U0hQOjJR6cpfiymgxGdfyTiVcvTm
20
- Gj/okWrQl0NjYOYBpDi+9PPmaH2RmLJu0dB/NylsDnW5j6yN1BEI8MfJRR+HRKZY
21
- mUtgzBwF1V4KIZQ8EuL6I/nHVu07i6IkrpAgxpXUfdJQJi0oZAqXurAV3yTxkFwd
22
- g62YrrW26mDe+pZBzR6bpLE+PmXCzz7UxUq3AE0gPHbiMXie3EFE0oxnsU3lIduh
23
- sCANiQ8BAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
24
- BBS5k4Z75VSpdM0AclG2UvzFA/VW5DANBgkqhkiG9w0BAQUFAAOCAQEAHagT4lfX
25
- kP/hDaiwGct7XPuVGbrOsKRVD59FF5kETBxEc9UQ1clKWngf8JoVuEoKD774dW19
26
- bU0GOVWO+J6FMmT/Cp7nuFJ79egMf/gy4gfUfQMuvfcr6DvZUPIs9P/TlK59iMYF
27
- DIOQ3DxdF3rMzztNUCizN4taVscEsjCcgW6WkUJnGdqlu3OHWpQxZBJkBTjPCoc6
28
- UW6on70SFPmAy/5Cq0OJNGEWBfgD9q7rrs/X8GGwUWqXb85RXnUVi/P8Up75E0ag
29
- 14jEc90kN+C7oI/AGCBN0j6JnEtYIEJZibjjDJTSMWlUKKkj30kq7hlUC2CepJ4v
30
- x52qPcexcYZR7w==
14
+ MIIDPjCCAiagAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMRMwEQYDVQQDDApyeWFu
15
+ ZC1ydWJ5MRkwFwYKCZImiZPyLGQBGRYJemVuc3BpZGVyMRMwEQYKCZImiZPyLGQB
16
+ GRYDY29tMB4XDTEzMDkxNjIzMDQxMloXDTE0MDkxNjIzMDQxMlowRTETMBEGA1UE
17
+ AwwKcnlhbmQtcnVieTEZMBcGCgmSJomT8ixkARkWCXplbnNwaWRlcjETMBEGCgmS
18
+ JomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALda
19
+ b9DCgK+627gPJkB6XfjZ1itoOQvpqH1EXScSaba9/S2VF22VYQbXU1xQXL/WzCkx
20
+ taCPaLmfYIaFcHHCSY4hYDJijRQkLxPeB3xbOfzfLoBDbjvx5JxgJxUjmGa7xhcT
21
+ oOvjtt5P8+GSK9zLzxQP0gVLS/D0FmoE44XuDr3iQkVS2ujU5zZL84mMNqNB1znh
22
+ GiadM9GHRaDiaxuX0cIUBj19T01mVE2iymf9I6bEsiayK/n6QujtyCbTWsAS9Rqt
23
+ qhtV7HJxNKuPj/JFH0D2cswvzznE/a5FOYO68g+YCuFi5L8wZuuM8zzdwjrWHqSV
24
+ gBEfoTEGr7Zii72cx+sCAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAw
25
+ HQYDVR0OBBYEFEfFe9md/r/tj/Wmwpy+MI8d9k/hMA0GCSqGSIb3DQEBBQUAA4IB
26
+ AQCFZ7JTzoy1gcG4d8A6dmOJy7ygtO5MFpRIz8HuKCF5566nOvpy7aHhDDzFmQuu
27
+ FX3zDU6ghx5cQIueDhf2SGOncyBmmJRRYawm3wI0o1MeN6LZJ/3cRaOTjSFy6+S6
28
+ zqDmHBp8fVA2TGJtO0BLNkbGVrBJjh0UPmSoGzWlRhEVnYC33TpDAbNA+u39UrQI
29
+ ynwhNN7YbnmSR7+JU2cUjBFv2iPBO+TGuWC+9L2zn3NHjuc6tnmSYipA9y8Hv+As
30
+ Y4evBVezr3SjXz08vPqRO5YRdO3zfeMT8gBjRqZjWJGMZ2lD4XNfrs7eky74CyZw
31
+ xx3n58i0lQkBE1EpKE0lFu/y
31
32
  -----END CERTIFICATE-----
32
-
33
- date: 2009-08-04 00:00:00 -07:00
34
- default_executable:
35
- dependencies:
36
- - !ruby/object:Gem::Dependency
33
+ date: 2014-08-07 00:00:00.000000000 Z
34
+ dependencies:
35
+ - !ruby/object:Gem::Dependency
36
+ name: minitest
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '5.4'
42
+ type: :development
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '5.4'
49
+ - !ruby/object:Gem::Dependency
50
+ name: rdoc
51
+ requirement: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '4.0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '4.0'
63
+ - !ruby/object:Gem::Dependency
37
64
  name: hoe
65
+ requirement: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '3.12'
38
70
  type: :development
39
- version_requirement:
40
- version_requirements: !ruby/object:Gem::Requirement
41
- requirements:
42
- - - ">="
43
- - !ruby/object:Gem::Version
44
- version: 2.3.2
45
- version:
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: '3.12'
46
77
  description: |-
47
78
  IMAPProcessor is a client for processing messages on an IMAP server. It
48
79
  provides some basic mechanisms for connecting to an IMAP server, determining
49
80
  capabilities and handling messages.
50
-
51
- IMAPProcessor ships with the executables imap_keywords which can query an IMAP
52
- server for keywords set on messages in mailboxes, imap_idle which can show new
53
- messages in a mailbox and imap_archive which will archive old messages to a
54
- new mailbox.
55
- email:
81
+
82
+
83
+ IMAPProcessor ships with several executables which can query and
84
+ manipulate IMAP mailboxes in several different ways:
85
+
86
+ imap_archive :: Archives old messages to a new dated mailbox.
87
+ imap_cleanse :: Delete messages older than a certain age in specified mailboxes.
88
+ imap_flag :: Flag messages to/from certain people.
89
+ imap_idle :: Shows new messages in a mailbox.
90
+ imap_keywords :: Queries an IMAP server for keywords set on messages
91
+ imap_learn :: Flags messages based on what you've flagged before.
92
+ imap_mkdir :: Ensures that certain mailboxes exist.
93
+ email:
56
94
  - drbrain@segment7.net
57
- executables:
95
+ - ryand-ruby@zenspider.com
96
+ executables:
58
97
  - imap_archive
98
+ - imap_cleanse
99
+ - imap_flag
59
100
  - imap_idle
60
101
  - imap_keywords
102
+ - imap_learn
103
+ - imap_mkdir
61
104
  extensions: []
62
-
63
- extra_rdoc_files:
64
- - History.txt
105
+ extra_rdoc_files:
106
+ - History.rdoc
65
107
  - Manifest.txt
66
- - README.txt
67
- files:
108
+ - README.rdoc
109
+ files:
68
110
  - .autotest
69
- - History.txt
111
+ - .gemtest
112
+ - History.rdoc
70
113
  - Manifest.txt
71
- - README.txt
114
+ - README.rdoc
72
115
  - Rakefile
73
116
  - bin/imap_archive
117
+ - bin/imap_cleanse
118
+ - bin/imap_flag
74
119
  - bin/imap_idle
75
120
  - bin/imap_keywords
121
+ - bin/imap_learn
122
+ - bin/imap_mkdir
76
123
  - lib/imap_processor.rb
77
124
  - lib/imap_processor/archive.rb
125
+ - lib/imap_processor/cleanse.rb
126
+ - lib/imap_processor/client.rb
127
+ - lib/imap_processor/flag.rb
78
128
  - lib/imap_processor/idle.rb
79
129
  - lib/imap_processor/keywords.rb
130
+ - lib/imap_processor/learn.rb
131
+ - lib/imap_processor/mkdir.rb
80
132
  - lib/imap_sasl_plain.rb
81
133
  - lib/net/imap/date.rb
82
134
  - lib/net/imap/idle.rb
83
135
  - test/test_imap_processor.rb
84
- has_rdoc: true
85
136
  homepage: http://seattlerb.rubyforge.org/imap_processor
86
- licenses: []
87
-
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
88
140
  post_install_message:
89
- rdoc_options:
141
+ rdoc_options:
90
142
  - --main
91
- - README.txt
92
- require_paths:
143
+ - README.rdoc
144
+ require_paths:
93
145
  - lib
94
- required_ruby_version: !ruby/object:Gem::Requirement
95
- requirements:
96
- - - ">="
97
- - !ruby/object:Gem::Version
98
- version: "0"
99
- version:
100
- required_rubygems_version: !ruby/object:Gem::Requirement
101
- requirements:
102
- - - ">="
103
- - !ruby/object:Gem::Version
104
- version: "0"
105
- version:
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
106
156
  requirements: []
107
-
108
- rubyforge_project: seattlerb
109
- rubygems_version: 1.3.5
157
+ rubyforge_project:
158
+ rubygems_version: 2.2.1
110
159
  signing_key:
111
- specification_version: 3
160
+ specification_version: 4
112
161
  summary: IMAPProcessor is a client for processing messages on an IMAP server
113
- test_files:
162
+ test_files:
114
163
  - test/test_imap_processor.rb