imap_processor 1.3 → 1.5

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