imap_processor 1.1.1 → 1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -3
- data/.autotest +3 -18
- data/History.rdoc +103 -0
- data/Manifest.txt +18 -2
- data/{README.txt → README.rdoc} +35 -5
- data/Rakefile +8 -5
- data/bin/imap_archive +5 -0
- data/bin/imap_cleanse +5 -0
- data/bin/imap_flag +5 -0
- data/bin/imap_idle +6 -0
- data/bin/imap_learn +5 -0
- data/bin/imap_mkdir +5 -0
- data/lib/imap_processor.rb +304 -154
- data/lib/imap_processor/archive.rb +128 -0
- data/lib/imap_processor/cleanse.rb +67 -0
- data/lib/imap_processor/client.rb +145 -0
- data/lib/imap_processor/flag.rb +121 -0
- data/lib/imap_processor/idle.rb +74 -0
- data/lib/imap_processor/keywords.rb +9 -14
- data/lib/imap_processor/learn.rb +231 -0
- data/lib/imap_processor/mkdir.rb +25 -0
- data/lib/imap_sasl_plain.rb +1 -26
- data/lib/net/imap/date.rb +24 -0
- data/lib/net/imap/idle.rb +48 -0
- data/test/test_imap_processor.rb +185 -0
- metadata +120 -71
- metadata.gz.sig +0 -0
- data/History.txt +0 -22
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 632f5afa32e055708809e6817e2532f5a5f880b48d9bb808102e63e2bad6cff7
|
4
|
+
data.tar.gz: 145bf8485b69a4629b32b12748edc780899db542e6a180db88a61ee6fc93ba56
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e1c5b332b9d8e3dbb9f105d888cb796a1e901633f40147a241ed2ac0ec7f63664f8a9b78a0eed149d8653eace16de68190d2737462294192ecaaa0bf331d1746
|
7
|
+
data.tar.gz: 5561900484e14b7fe02508402af345a07a8999c56b2fdf05dd91989698b4b5a87bdd9f09acebf84232e7505b3a9ce8e57e8e9fc42041567555cf889592ec055c
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
CHANGED
@@ -1,3 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
��в��o��G���\i�֬�)a�(�5c͌S�@"�Ŭ�=�͞nM��H��|���.�poa�et'�ruCw��8ƌ�?Zz�C(�iA�0�`?��"L�cp�q��ŋ��3�-�@!�YЌժU�Dr-"&
|
1
|
+
C��>Ƙo�3����0��:��Oɩ��KiXZ��$���foL(�=��=��W-
|
2
|
+
5m���݆+cV�1���㹶-\�GLD��W���U�����0�&1-�͔C���*��0��n~��C��8�;)���8���x���.ϔ1�/�e����1��AN$�F�ͭDK9�6��V5��g�
|
data/.autotest
CHANGED
@@ -2,22 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'autotest/restart'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
# at.libs << ":../some/external"
|
9
|
-
#
|
10
|
-
# at.add_exception 'vendor'
|
11
|
-
#
|
12
|
-
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
-
# at.files_matching(/test_.*rb$/)
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# %w(TestA TestB).each do |klass|
|
17
|
-
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
-
# end
|
19
|
-
# end
|
5
|
+
Autotest.add_hook :initialize do |at|
|
6
|
+
at.testlib = 'minitest/autorun'
|
7
|
+
end
|
20
8
|
|
21
|
-
# Autotest.add_hook :run_command do |at|
|
22
|
-
# system "rake build"
|
23
|
-
# end
|
data/History.rdoc
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
=== 1.7 / 2020-06-04
|
2
|
+
|
3
|
+
* 4 minor enhancements:
|
4
|
+
|
5
|
+
* Added #noop? to improve readability.
|
6
|
+
* Documented how to use this with gmail.
|
7
|
+
* Improved imap_archive output by distinguishing hosts.
|
8
|
+
* Improved show_messages output to be cleaner and have better info (from/to).
|
9
|
+
|
10
|
+
* 2 bug fixes:
|
11
|
+
|
12
|
+
* Fixed --noop by covering all imap calls that modify w/ checks.
|
13
|
+
* Fixed option processing with multiple accounts by dup'ing args to OptionParser.
|
14
|
+
|
15
|
+
=== 1.6 / 2014-10-17
|
16
|
+
|
17
|
+
* 1 minor enhancement:
|
18
|
+
|
19
|
+
* Add XOAUTH2 authentication type. (mattbeedle)
|
20
|
+
|
21
|
+
=== 1.5 / 2014-08-06
|
22
|
+
|
23
|
+
* 3 major enhancements:
|
24
|
+
|
25
|
+
* IMAPProcessor#process_args now returns an array of option hashes.
|
26
|
+
* IMAPProcessor.run now enumerates the array returned from process_args.
|
27
|
+
* You can now specify multiple host configs w/ an array of hashes in your config files.
|
28
|
+
|
29
|
+
* 4 minor enhancements:
|
30
|
+
|
31
|
+
* Added --merge to imap_archive.
|
32
|
+
* Added --noop/-n to manually disable destructive actions. (needs to propagate down).
|
33
|
+
* Added imap_cleanse, imap_flag, imap_learn; migrated from IMAPCleanse.
|
34
|
+
* Added support for LOGIN. (bleything)
|
35
|
+
|
36
|
+
* 7 bug fixes:
|
37
|
+
|
38
|
+
* Fixed initializers in flag and cleanse.
|
39
|
+
* Fixed odd bug w/ running on empty folders. Never saw that before. odd...
|
40
|
+
* Handle unparsable date entries. Stupid spammers...
|
41
|
+
* Now calculating latest month when not splitting directly from the date
|
42
|
+
* Removed 1.9/2.0 warnings.
|
43
|
+
* Removed dead rubyforge setting in Rakefile
|
44
|
+
* Split was still defaulting to true.
|
45
|
+
|
46
|
+
=== 1.4 / 2011-01-10
|
47
|
+
|
48
|
+
* 6 minor enhancements:
|
49
|
+
|
50
|
+
* Added explicit help option (-h didn't work)
|
51
|
+
* Added folder separator support (osx server uses '.' not '/')
|
52
|
+
* Added imap_mkdir command
|
53
|
+
* Added opts_file_name class var so subclass option processing can refer to file
|
54
|
+
* Extended imap_archive to archive multiple months per box, as necessary. Allowing easy archiving of big mailboxes
|
55
|
+
* Handles server-provided CAPABILITY to avoid an extra round-trip
|
56
|
+
|
57
|
+
* 1 bug fix:
|
58
|
+
|
59
|
+
* Fixed doco.
|
60
|
+
|
61
|
+
=== 1.3 / 2009-08-04
|
62
|
+
|
63
|
+
* 1 major enhancement
|
64
|
+
* IMAP IDLE support now matches ruby trunk's support. See Net::IMAP#idle
|
65
|
+
and Net::IMAP#idle_done
|
66
|
+
|
67
|
+
=== 1.2 / 2009-06-02
|
68
|
+
|
69
|
+
* 2 major enhancements
|
70
|
+
* imap_archive which archives old mail to dated mailboxes
|
71
|
+
* imap_idle which lists messages that were added or expunged from a mailbox
|
72
|
+
|
73
|
+
* 4 minor enhancements
|
74
|
+
* Added IMAPProcessor#create_mailbox
|
75
|
+
* Added IMAPProcessor#delete_messages
|
76
|
+
* Added IMAPProcessor#move_messages
|
77
|
+
* Disabled verification of SSL certs for 1.9
|
78
|
+
|
79
|
+
* 1 bug fix
|
80
|
+
* Fixed options file names, they should be Symbol keys
|
81
|
+
|
82
|
+
=== 1.1.1 / 2009-05-19
|
83
|
+
|
84
|
+
* 1 bug fix
|
85
|
+
* Got the skip test backwards
|
86
|
+
|
87
|
+
=== 1.1 / 2009-05-18
|
88
|
+
|
89
|
+
* 1 minor enhancement
|
90
|
+
* IMAPProcessor#each_message allows messages to be omitted from the returned
|
91
|
+
uid list (skipped)
|
92
|
+
|
93
|
+
=== 1.0.1 / 2009-05-15
|
94
|
+
|
95
|
+
* 2 bug fix
|
96
|
+
* Show correct name of options file for --password help
|
97
|
+
* Fix --quiet
|
98
|
+
|
99
|
+
=== 1.0.0 / 2009-05-12
|
100
|
+
|
101
|
+
* 1 major enhancement
|
102
|
+
* Birthday!
|
103
|
+
|
data/Manifest.txt
CHANGED
@@ -1,9 +1,25 @@
|
|
1
1
|
.autotest
|
2
|
-
History.
|
2
|
+
History.rdoc
|
3
3
|
Manifest.txt
|
4
|
-
README.
|
4
|
+
README.rdoc
|
5
5
|
Rakefile
|
6
|
+
bin/imap_archive
|
7
|
+
bin/imap_cleanse
|
8
|
+
bin/imap_flag
|
9
|
+
bin/imap_idle
|
6
10
|
bin/imap_keywords
|
11
|
+
bin/imap_learn
|
12
|
+
bin/imap_mkdir
|
7
13
|
lib/imap_processor.rb
|
14
|
+
lib/imap_processor/archive.rb
|
15
|
+
lib/imap_processor/cleanse.rb
|
16
|
+
lib/imap_processor/client.rb
|
17
|
+
lib/imap_processor/flag.rb
|
18
|
+
lib/imap_processor/idle.rb
|
8
19
|
lib/imap_processor/keywords.rb
|
20
|
+
lib/imap_processor/learn.rb
|
21
|
+
lib/imap_processor/mkdir.rb
|
9
22
|
lib/imap_sasl_plain.rb
|
23
|
+
lib/net/imap/date.rb
|
24
|
+
lib/net/imap/idle.rb
|
25
|
+
test/test_imap_processor.rb
|
data/{README.txt → README.rdoc}
RENAMED
@@ -1,6 +1,7 @@
|
|
1
1
|
= imap_processor
|
2
2
|
|
3
|
-
|
3
|
+
home :: https://github.com/seattlerb/imap_processor
|
4
|
+
rdoc :: http://docs.seattlerb.org/imap_processor
|
4
5
|
|
5
6
|
== DESCRIPTION:
|
6
7
|
|
@@ -8,8 +9,16 @@ IMAPProcessor is a client for processing messages on an IMAP server. It
|
|
8
9
|
provides some basic mechanisms for connecting to an IMAP server, determining
|
9
10
|
capabilities and handling messages.
|
10
11
|
|
11
|
-
IMAPProcessor ships with
|
12
|
-
|
12
|
+
IMAPProcessor ships with several executables which can query and
|
13
|
+
manipulate IMAP mailboxes in several different ways:
|
14
|
+
|
15
|
+
imap_archive :: Archives old messages to a new dated mailbox.
|
16
|
+
imap_cleanse :: Delete messages older than a certain age in specified mailboxes.
|
17
|
+
imap_flag :: Flag messages to/from certain people.
|
18
|
+
imap_idle :: Shows new messages in a mailbox.
|
19
|
+
imap_keywords :: Queries an IMAP server for keywords set on messages
|
20
|
+
imap_learn :: Flags messages based on what you've flagged before.
|
21
|
+
imap_mkdir :: Ensures that certain mailboxes exist.
|
13
22
|
|
14
23
|
== FEATURES/PROBLEMS:
|
15
24
|
|
@@ -19,7 +28,28 @@ server for keywords set on messages in mailboxes.
|
|
19
28
|
|
20
29
|
== SYNOPSIS:
|
21
30
|
|
22
|
-
|
31
|
+
Run any command with --help for details.
|
32
|
+
|
33
|
+
== Google Mail:
|
34
|
+
|
35
|
+
This is kinda painful. You need to have Two Factor Authentication
|
36
|
+
enabled with google, then you need to create an app specific password
|
37
|
+
as described here: https://support.google.com/accounts/answer/185833
|
38
|
+
|
39
|
+
Then, your config needs to be set up like this:
|
40
|
+
|
41
|
+
- :Host: imap.googlemail.com
|
42
|
+
:SSL: true
|
43
|
+
:Auth: PLAIN
|
44
|
+
:Username: your.address@gmail.com
|
45
|
+
:Password: app-specific-password
|
46
|
+
|
47
|
+
Specifically, you need to set auth to PLAIN, and your password needs
|
48
|
+
to your app specific password. Run with --debug to help figure
|
49
|
+
problems out.
|
50
|
+
|
51
|
+
Google is threatening to turn this off at some point and require
|
52
|
+
oauth... at which point... I have no idea. I give up I guess.
|
23
53
|
|
24
54
|
== REQUIREMENTS:
|
25
55
|
|
@@ -33,7 +63,7 @@ See IMAPProcessor and IMAPProcessor::Keywords for details
|
|
33
63
|
|
34
64
|
(The MIT License)
|
35
65
|
|
36
|
-
Copyright (c)
|
66
|
+
Copyright (c) Eric Hodel, Ryan Davis, Seattle.rb
|
37
67
|
|
38
68
|
Permission is hereby granted, free of charge, to any person obtaining
|
39
69
|
a copy of this software and associated documentation files (the
|
data/Rakefile
CHANGED
@@ -2,12 +2,15 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'hoe'
|
5
|
-
$:.unshift 'lib'
|
6
|
-
require 'imap_processor'
|
7
5
|
|
8
|
-
Hoe.
|
9
|
-
|
10
|
-
|
6
|
+
Hoe.plugin :seattlerb
|
7
|
+
Hoe.plugin :rdoc
|
8
|
+
|
9
|
+
Hoe.spec 'imap_processor' do
|
10
|
+
developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
|
11
|
+
developer 'Eric Hodel', 'drbrain@segment7.net'
|
12
|
+
|
13
|
+
license "MIT"
|
11
14
|
end
|
12
15
|
|
13
16
|
# vim: syntax=Ruby
|
data/bin/imap_archive
ADDED
data/bin/imap_cleanse
ADDED
data/bin/imap_flag
ADDED
data/bin/imap_idle
ADDED
data/bin/imap_learn
ADDED
data/bin/imap_mkdir
ADDED
data/lib/imap_processor.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'optparse'
|
3
3
|
require 'net/imap'
|
4
|
+
require 'net/imap/date'
|
4
5
|
require 'imap_sasl_plain'
|
6
|
+
require 'yaml'
|
5
7
|
|
6
8
|
##
|
7
9
|
# IMAPProcessor is a client for processing messages on an IMAP server.
|
@@ -13,13 +15,24 @@ require 'imap_sasl_plain'
|
|
13
15
|
# * An initialize method that connects to an IMAP server and sets the @imap
|
14
16
|
# instance variable
|
15
17
|
# * A run method that uses the IMAP connection to process messages.
|
18
|
+
#
|
19
|
+
# Reference:
|
20
|
+
#
|
21
|
+
# email: http://www.ietf.org/rfc/rfc0822.txt
|
22
|
+
# imap: http://www.ietf.org/rfc/rfc3501.txt
|
16
23
|
|
17
24
|
class IMAPProcessor
|
18
25
|
|
19
26
|
##
|
20
27
|
# The version of IMAPProcessor you are using
|
21
28
|
|
22
|
-
VERSION =
|
29
|
+
VERSION = "1.7"
|
30
|
+
|
31
|
+
##
|
32
|
+
# Base IMAPProcessor error class
|
33
|
+
|
34
|
+
class Error < RuntimeError
|
35
|
+
end
|
23
36
|
|
24
37
|
##
|
25
38
|
# A Connection Struct that has +imap+ and +capability+ accessors
|
@@ -67,7 +80,7 @@ class IMAPProcessor
|
|
67
80
|
opts.on( "--move=MAILBOX",
|
68
81
|
"Mailbox to move message to",
|
69
82
|
"Default: #{options[:MoveTo].inspect}",
|
70
|
-
"Options file name: MoveTo") do |mailbox|
|
83
|
+
"Options file name: :MoveTo") do |mailbox|
|
71
84
|
options[:MoveTo] = mailbox
|
72
85
|
end
|
73
86
|
end
|
@@ -84,24 +97,28 @@ class IMAPProcessor
|
|
84
97
|
# required_options = {
|
85
98
|
# :MoveTo => [nil, "MoveTo not set"],
|
86
99
|
# }
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
100
|
+
#
|
101
|
+
# super __FILE__, args, required_options do |opts, options|
|
102
|
+
# opts.banner << "Explain my_processor's executable"
|
103
|
+
#
|
104
|
+
# opts.on( "--move=MAILBOX",
|
105
|
+
# "Mailbox to move message to",
|
106
|
+
# "Default: #{options[:MoveTo].inspect}",
|
107
|
+
# "Options file name: :MoveTo") do |mailbox|
|
108
|
+
# options[:MoveTo] = mailbox
|
109
|
+
# end
|
96
110
|
# end
|
97
111
|
# end
|
98
112
|
# end
|
113
|
+
#
|
114
|
+
# NOTE: You can add a --move option using ::add_move
|
99
115
|
|
100
116
|
def self.process_args(processor_file, args,
|
101
117
|
required_options = {}) # :yield: OptionParser
|
102
|
-
opts_file_name = File.basename processor_file, '.rb'
|
103
|
-
|
104
|
-
|
118
|
+
@@opts_file_name = File.basename processor_file, '.rb'
|
119
|
+
@@opts_file_name = "imap_#{@@opts_file_name}" unless
|
120
|
+
@@opts_file_name =~ /^imap_/
|
121
|
+
opts_file = File.expand_path "~/.#{@@opts_file_name}"
|
105
122
|
|
106
123
|
if required_options then
|
107
124
|
required_options.each do |option, (default, message)|
|
@@ -111,6 +128,8 @@ class IMAPProcessor
|
|
111
128
|
end
|
112
129
|
end
|
113
130
|
|
131
|
+
defaults = [{}]
|
132
|
+
|
114
133
|
if File.exist? opts_file then
|
115
134
|
unless File.stat(opts_file).mode & 077 == 0 then
|
116
135
|
$stderr.puts "WARNING! #{opts_file} is group/other readable or writable!"
|
@@ -118,165 +137,188 @@ class IMAPProcessor
|
|
118
137
|
exit 1
|
119
138
|
end
|
120
139
|
|
121
|
-
|
140
|
+
defaults = Array(YAML.load_file(opts_file))
|
122
141
|
end
|
123
142
|
|
124
|
-
|
125
|
-
|
126
|
-
options[:Root] ||= nil
|
127
|
-
options[:Verbose] ||= false
|
128
|
-
options[:Debug] ||= false
|
143
|
+
defaults.map { |default|
|
144
|
+
options = default.merge @@options.dup
|
129
145
|
|
130
|
-
|
131
|
-
options[
|
132
|
-
|
146
|
+
options[:SSL] = true unless options.key? :SSL
|
147
|
+
options[:Username] ||= ENV['USER']
|
148
|
+
options[:Root] ||= nil
|
149
|
+
options[:Verbose] ||= false
|
150
|
+
options[:Debug] ||= false
|
133
151
|
|
134
|
-
|
135
|
-
|
136
|
-
|
152
|
+
required_options.each do |k,(v,_)|
|
153
|
+
options[k] ||= v
|
154
|
+
end
|
137
155
|
|
138
|
-
|
139
|
-
|
156
|
+
op = OptionParser.new do |opts|
|
157
|
+
opts.program_name = File.basename $0
|
158
|
+
opts.banner = "Usage: #{opts.program_name} [options]\n\n"
|
140
159
|
|
141
|
-
|
142
|
-
|
143
|
-
"Default: #{options[:Host].inspect}",
|
144
|
-
"Options file name: Host") do |host|
|
145
|
-
options[:Host] = host
|
146
|
-
end
|
160
|
+
opts.separator ''
|
161
|
+
opts.separator 'Connection options:'
|
147
162
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
options[:Port] = port
|
153
|
-
end
|
163
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
164
|
+
puts opts
|
165
|
+
exit
|
166
|
+
end
|
154
167
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
168
|
+
opts.on("-H", "--host HOST",
|
169
|
+
"IMAP server host",
|
170
|
+
"Default: #{options[:Host].inspect}",
|
171
|
+
"Options file name: :Host") do |host|
|
172
|
+
options[:Host] = host
|
173
|
+
end
|
161
174
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
175
|
+
opts.on("-P", "--port PORT",
|
176
|
+
"IMAP server port",
|
177
|
+
"Default: The correct port SSL/non-SSL mode",
|
178
|
+
"Options file name: :Port") do |port|
|
179
|
+
options[:Port] = port
|
180
|
+
end
|
168
181
|
|
169
|
-
|
170
|
-
|
182
|
+
opts.on("-s", "--[no-]ssl",
|
183
|
+
"Use SSL for IMAP connection",
|
184
|
+
"Default: #{options[:SSL].inspect}",
|
185
|
+
"Options file name: :SSL") do |ssl|
|
186
|
+
options[:SSL] = ssl
|
187
|
+
end
|
171
188
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
189
|
+
opts.on( "--[no-]debug",
|
190
|
+
"Display Net::IMAP debugging info",
|
191
|
+
"Default: #{options[:Debug].inspect}",
|
192
|
+
"Options file name: :Debug") do |debug|
|
193
|
+
options[:Debug] = debug
|
194
|
+
end
|
178
195
|
|
179
|
-
|
180
|
-
|
181
|
-
"Default: Read from ~/.#{opts_file_name}",
|
182
|
-
"Options file name: Password") do |password|
|
183
|
-
options[:Password] = password
|
184
|
-
end
|
196
|
+
opts.separator ''
|
197
|
+
opts.separator 'Login options:'
|
185
198
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
"Default: #{options[:Auth].inspect}",
|
193
|
-
"Options file name: Auth") do |auth|
|
194
|
-
options[:Auth] = auth
|
195
|
-
end
|
199
|
+
opts.on("-u", "--username USERNAME",
|
200
|
+
"IMAP username",
|
201
|
+
"Default: #{options[:Username].inspect}",
|
202
|
+
"Options file name: :Username") do |username|
|
203
|
+
options[:Username] = username
|
204
|
+
end
|
196
205
|
|
197
|
-
|
198
|
-
|
206
|
+
opts.on("-p", "--password PASSWORD",
|
207
|
+
"IMAP password",
|
208
|
+
"Default: Read from ~/.#{@@opts_file_name}",
|
209
|
+
"Options file name: :Password") do |password|
|
210
|
+
options[:Password] = password
|
211
|
+
end
|
199
212
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
213
|
+
authenticators = Net::IMAP.send :class_variable_get, :@@authenticators
|
214
|
+
auth_types = authenticators.keys.sort.join ', '
|
215
|
+
opts.on("-a", "--auth AUTH", auth_types,
|
216
|
+
"IMAP authentication type override",
|
217
|
+
"Authentication type will be auto-",
|
218
|
+
"discovered",
|
219
|
+
"Default: #{options[:Auth].inspect}",
|
220
|
+
"Options file name: :Auth") do |auth|
|
221
|
+
options[:Auth] = auth
|
222
|
+
end
|
206
223
|
|
207
|
-
|
208
|
-
|
209
|
-
"to search",
|
210
|
-
"Default: #{options[:Boxes].inspect}",
|
211
|
-
"Options file name: Boxes") do |boxes|
|
212
|
-
options[:Boxes] = boxes
|
213
|
-
end
|
224
|
+
opts.separator ''
|
225
|
+
opts.separator "IMAP options:"
|
214
226
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
227
|
+
opts.on("-r", "--root ROOT",
|
228
|
+
"Root of mailbox hierarchy",
|
229
|
+
"Default: #{options[:Root].inspect}",
|
230
|
+
"Options file name: :Root") do |root|
|
231
|
+
options[:Root] = root
|
232
|
+
end
|
221
233
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
234
|
+
opts.on("-b", "--boxes BOXES", Array,
|
235
|
+
"Comma-separated list of mailbox names",
|
236
|
+
"to search",
|
237
|
+
"Default: #{options[:Boxes].inspect}",
|
238
|
+
"Options file name: :Boxes") do |boxes|
|
239
|
+
options[:Boxes] = boxes
|
240
|
+
end
|
226
241
|
|
227
|
-
|
228
|
-
|
229
|
-
|
242
|
+
opts.on("-v", "--[no-]verbose",
|
243
|
+
"Be verbose",
|
244
|
+
"Default: #{options[:Verbose].inspect}",
|
245
|
+
"Options file name: :Verbose") do |verbose|
|
246
|
+
options[:Verbose] = verbose
|
247
|
+
end
|
230
248
|
|
231
|
-
|
232
|
-
|
249
|
+
opts.on("-n", "--noop",
|
250
|
+
"Perform no destructive operations",
|
251
|
+
"Best used with the verbose option",
|
252
|
+
"Default: #{options[:Noop].inspect}",
|
253
|
+
"Options file name: Noop") do |noop|
|
254
|
+
options[:Noop] = noop
|
255
|
+
end
|
233
256
|
|
234
|
-
|
235
|
-
|
236
|
-
|
257
|
+
opts.on("-q", "--quiet",
|
258
|
+
"Be quiet") do
|
259
|
+
options[:Verbose] = false
|
260
|
+
end
|
237
261
|
|
238
|
-
|
262
|
+
if block_given? then
|
263
|
+
opts.separator ''
|
264
|
+
opts.separator "#{self} options:"
|
265
|
+
|
266
|
+
yield opts, options if block_given?
|
267
|
+
end
|
268
|
+
|
269
|
+
@@extra_options.each do |block|
|
270
|
+
block.call opts, options
|
271
|
+
end
|
272
|
+
|
273
|
+
opts.separator ''
|
239
274
|
|
240
|
-
|
275
|
+
opts.banner << <<-EOF
|
241
276
|
|
242
|
-
Options may also be set in the options file ~/.#{opts_file_name}
|
277
|
+
Options may also be set in the options file ~/.#{@@opts_file_name}
|
243
278
|
|
244
|
-
Example ~/.#{opts_file_name}:
|
279
|
+
Example ~/.#{@@opts_file_name}:
|
245
280
|
\tHost=mail.example.com
|
246
281
|
\tPassword=my password
|
247
282
|
|
248
|
-
|
249
|
-
|
283
|
+
EOF
|
284
|
+
|
285
|
+
end # OptionParser.new do
|
286
|
+
|
287
|
+
op.parse! args.dup
|
250
288
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
$stderr.puts missing_message if options[option_name].nil?
|
289
|
+
options[:Port] ||= options[:SSL] ? 993 : 143
|
290
|
+
|
291
|
+
# HACK: removed :Boxes -- push down
|
292
|
+
required_keys = [:Host, :Password] + required_options.keys
|
293
|
+
if required_keys.any? { |k| options[k].nil? } then
|
294
|
+
$stderr.puts op
|
295
|
+
$stderr.puts
|
296
|
+
$stderr.puts "Host name not set" if options[:Host].nil?
|
297
|
+
$stderr.puts "Password not set" if options[:Password].nil?
|
298
|
+
$stderr.puts "Boxes not set" if options[:Boxes].nil?
|
299
|
+
required_options.each do |option_name, (_, missing_message)|
|
300
|
+
$stderr.puts missing_message if options[option_name].nil?
|
301
|
+
end
|
302
|
+
exit 1
|
266
303
|
end
|
267
|
-
exit 1
|
268
|
-
end
|
269
304
|
|
270
|
-
|
305
|
+
options
|
306
|
+
} # defaults.map
|
271
307
|
end
|
272
308
|
|
273
309
|
##
|
274
310
|
# Sets up an IMAP processor's options then calls its \#run method.
|
275
311
|
|
276
312
|
def self.run(args = ARGV, &block)
|
277
|
-
|
278
|
-
|
279
|
-
|
313
|
+
client = nil
|
314
|
+
multi_options = process_args args
|
315
|
+
|
316
|
+
multi_options.each do |options|
|
317
|
+
client = new(options, &block)
|
318
|
+
client.run
|
319
|
+
end
|
320
|
+
rescue Interrupt
|
321
|
+
exit
|
280
322
|
rescue SystemExit
|
281
323
|
raise
|
282
324
|
rescue Exception => e
|
@@ -285,7 +327,7 @@ Example ~/.#{opts_file_name}:
|
|
285
327
|
|
286
328
|
exit 1
|
287
329
|
ensure
|
288
|
-
client.imap.logout if client
|
330
|
+
client.imap.logout if client and client.imap
|
289
331
|
end
|
290
332
|
|
291
333
|
##
|
@@ -299,34 +341,102 @@ Example ~/.#{opts_file_name}:
|
|
299
341
|
Net::IMAP.debug = options[:Debug]
|
300
342
|
end
|
301
343
|
|
344
|
+
##
|
345
|
+
# Extracts capability information for +imap+ from +res+ or by contacting the
|
346
|
+
# server.
|
347
|
+
|
348
|
+
def capability imap, res = nil
|
349
|
+
return imap.capability unless res
|
350
|
+
|
351
|
+
data = res.data
|
352
|
+
|
353
|
+
if data.code and data.code.name == 'CAPABILITY' then
|
354
|
+
data.code.data.split ' '
|
355
|
+
else
|
356
|
+
imap.capability
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
302
360
|
##
|
303
361
|
# Connects to IMAP server +host+ at +port+ using ssl if +ssl+ is true then
|
304
|
-
#
|
305
|
-
# work with PLAIN auth on SSL sockets.
|
362
|
+
# authenticates with +username+ and +password+. IMAPProcessor is only known
|
363
|
+
# to work with PLAIN auth on SSL sockets. IMAPProcessor does not support
|
364
|
+
# LOGIN.
|
306
365
|
#
|
307
366
|
# Returns a Connection object.
|
308
367
|
|
309
|
-
def connect(host
|
310
|
-
|
368
|
+
def connect(host = @options[:Host],
|
369
|
+
port = @options[:Port],
|
370
|
+
ssl = @options[:SSL],
|
371
|
+
username = @options[:Username],
|
372
|
+
password = @options[:Password],
|
373
|
+
auth = @options[:Auth]) # :yields: Connection
|
374
|
+
imap = Net::IMAP.new host, port, ssl, nil, false
|
311
375
|
log "Connected to imap://#{host}:#{port}/"
|
312
376
|
|
313
|
-
|
377
|
+
capabilities = capability imap, imap.greeting
|
314
378
|
|
315
|
-
log "Capabilities: #{
|
379
|
+
log "Capabilities: #{capabilities.join ', '}"
|
316
380
|
|
317
|
-
auth_caps =
|
381
|
+
auth_caps = capabilities.select { |c| c =~ /^AUTH/ }
|
318
382
|
|
319
383
|
if auth.nil? then
|
320
384
|
raise "Couldn't find a supported auth type" if auth_caps.empty?
|
321
385
|
auth = auth_caps.first.sub(/AUTH=/, '')
|
322
386
|
end
|
323
387
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
388
|
+
# Net::IMAP supports using AUTHENTICATE with LOGIN, PLAIN, and
|
389
|
+
# CRAM-MD5... if the server reports a different AUTH method, then we
|
390
|
+
# should fall back to using LOGIN
|
391
|
+
if %w( LOGIN PLAIN CRAM-MD5 XOAUTH2 ).include?( auth.upcase )
|
392
|
+
auth = auth.upcase
|
393
|
+
log "Trying #{auth} authentication"
|
394
|
+
res = imap.authenticate auth, username, password
|
395
|
+
log "Logged in as #{username} using AUTHENTICATE"
|
396
|
+
else
|
397
|
+
log "Trying to authenticate via LOGIN"
|
398
|
+
res = imap.login username, password
|
399
|
+
log "Logged in as #{username} using LOGIN"
|
400
|
+
end
|
401
|
+
|
402
|
+
# CAPABILITY may have changed
|
403
|
+
capabilities = capability imap, res
|
404
|
+
|
405
|
+
connection = Connection.new imap, capabilities
|
406
|
+
|
407
|
+
if block_given? then
|
408
|
+
begin
|
409
|
+
yield connection
|
410
|
+
ensure
|
411
|
+
connection.imap.logout
|
412
|
+
end
|
413
|
+
else
|
414
|
+
return connection
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
##
|
419
|
+
# Create the mailbox +name+ if it doesn't exist. Note that this will SELECT
|
420
|
+
# the mailbox if it exists.
|
421
|
+
|
422
|
+
def create_mailbox name
|
423
|
+
log "LIST #{name}"
|
424
|
+
list = imap.list '', name
|
425
|
+
return if list
|
426
|
+
log "CREATE #{name}"
|
427
|
+
imap.create name unless noop?
|
428
|
+
end
|
328
429
|
|
329
|
-
|
430
|
+
##
|
431
|
+
# Delete and +expunge+ the specified +uids+.
|
432
|
+
|
433
|
+
def delete_messages uids, expunge = true
|
434
|
+
log "DELETING [...#{uids.size} uids]"
|
435
|
+
imap.store uids, '+FLAGS.SILENT', [:Deleted] unless noop?
|
436
|
+
if expunge then
|
437
|
+
log "EXPUNGE"
|
438
|
+
imap.expunge unless noop?
|
439
|
+
end
|
330
440
|
end
|
331
441
|
|
332
442
|
##
|
@@ -347,8 +457,6 @@ Example ~/.#{opts_file_name}:
|
|
347
457
|
uids = []
|
348
458
|
|
349
459
|
each_part parts, true do |uid, message|
|
350
|
-
skip = false
|
351
|
-
|
352
460
|
mail = TMail::Mail.parse message
|
353
461
|
|
354
462
|
begin
|
@@ -379,7 +487,7 @@ Example ~/.#{opts_file_name}:
|
|
379
487
|
sequence.unshift "BODY[#{section}.MIME]" unless section == 'TEXT'
|
380
488
|
sequence.unshift 'BODY[HEADER]' if header
|
381
489
|
|
382
|
-
body =
|
490
|
+
body = imap.fetch(uid, sequence).first
|
383
491
|
|
384
492
|
sequence = sequence.map { |item| body.attr[item] }
|
385
493
|
|
@@ -411,7 +519,7 @@ Example ~/.#{opts_file_name}:
|
|
411
519
|
def mime_parts(uids, mime_type)
|
412
520
|
media_type, subtype = mime_type.upcase.split('/', 2)
|
413
521
|
|
414
|
-
structures =
|
522
|
+
structures = imap.fetch uids, 'BODYSTRUCTURE'
|
415
523
|
|
416
524
|
structures.zip(uids).map do |body, uid|
|
417
525
|
section = nil
|
@@ -436,6 +544,43 @@ Example ~/.#{opts_file_name}:
|
|
436
544
|
end.compact
|
437
545
|
end
|
438
546
|
|
547
|
+
##
|
548
|
+
# Move the specified +uids+ to a new +destination+ then delete and +expunge+
|
549
|
+
# them. Creates the destination mailbox if it doesn't exist.
|
550
|
+
|
551
|
+
def move_messages uids, destination, expunge = true
|
552
|
+
return if uids.empty?
|
553
|
+
log "COPY [...#{uids.size} uids]"
|
554
|
+
|
555
|
+
begin
|
556
|
+
imap.copy uids, destination unless noop?
|
557
|
+
rescue Net::IMAP::NoResponseError
|
558
|
+
unless noop? then
|
559
|
+
create_mailbox destination
|
560
|
+
imap.copy uids, destination
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
delete_messages uids, expunge
|
565
|
+
end
|
566
|
+
|
567
|
+
##
|
568
|
+
# Displays Date, Subject and Message-Id from messages in +uids+
|
569
|
+
|
570
|
+
def show_messages(uids)
|
571
|
+
return if uids.nil? or (Array === uids and uids.empty?)
|
572
|
+
|
573
|
+
fetch_data = 'BODY.PEEK[HEADER.FIELDS (DATE FROM TO SUBJECT)]'
|
574
|
+
messages = imap.fetch uids, fetch_data
|
575
|
+
fetch_data.sub! '.PEEK', '' # stripped by server
|
576
|
+
|
577
|
+
messages ||= []
|
578
|
+
|
579
|
+
messages.each do |res|
|
580
|
+
puts res.attr[fetch_data].delete("\r").gsub(/^/, " ")
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
439
584
|
##
|
440
585
|
# Did the user set --verbose?
|
441
586
|
|
@@ -443,5 +588,10 @@ Example ~/.#{opts_file_name}:
|
|
443
588
|
@verbose
|
444
589
|
end
|
445
590
|
|
446
|
-
|
591
|
+
##
|
592
|
+
# Did the user set --noop?
|
447
593
|
|
594
|
+
def noop?
|
595
|
+
options[:Noop]
|
596
|
+
end
|
597
|
+
end
|