imap_processor 1.1.1 → 1.7
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.
- 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
|