cinch-imap 0.0.8 → 0.1.0
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/CHANGES.md +3 -0
- data/lib/cinch/plugins/imap.rb +255 -130
- metadata +27 -52
- data/README.md +0 -85
data/CHANGES.md
CHANGED
data/lib/cinch/plugins/imap.rb
CHANGED
@@ -1,134 +1,259 @@
|
|
1
1
|
require 'net/imap'
|
2
|
+
require 'yaml'
|
3
|
+
require 'tmpdir'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
envelope = conn.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
|
94
|
-
name = envelope.from[0].name
|
95
|
-
mailbox = envelope.from[0].mailbox
|
96
|
-
host = envelope.from[0].host
|
97
|
-
from = name.nil? ? host : name
|
98
|
-
subj = envelope.subject
|
99
|
-
conn.store(message_id, "+FLAGS", [:Seen]) if @mark_as_read
|
100
|
-
@messages_seen += 1
|
101
|
-
yield from, subj
|
102
|
-
end
|
103
|
-
end
|
104
|
-
def background_job
|
105
|
-
loop do
|
106
|
-
before = Time.now
|
107
|
-
yield if @monitor
|
108
|
-
remaining = @interval - (Time.now - before)
|
109
|
-
sleep(remaining) if remaining > 0
|
110
|
-
end
|
111
|
-
end
|
112
|
-
def imap_connect
|
113
|
-
connection = Net::IMAP.new(@mail_host, @mail_port, @mail_ssl)
|
114
|
-
connection.login(@mail_user, @mail_password)
|
115
|
-
connection.select(@mail_folder)
|
116
|
-
return connection
|
117
|
-
end
|
118
|
-
def imap_poll(m, connection)
|
119
|
-
get_messages(connection) do |from, subj|
|
120
|
-
message_from, message_prefix = from, nil
|
121
|
-
@from_rewrites.each do |k, v|
|
122
|
-
message_from = "#{v}" if from =~ /#{k}/
|
123
|
-
end
|
124
|
-
@subject_matches.each do |k, v|
|
125
|
-
message_prefix = "#{v}" if subj =~ /#{k}/
|
126
|
-
end
|
127
|
-
m.reply "#{message_prefix} #{message_from}: #{subj}"
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
5
|
+
class Imap
|
6
|
+
include Cinch::Plugin
|
7
|
+
|
8
|
+
PLUGIN_VERSION='0.1.0'
|
9
|
+
|
10
|
+
listen_to :join
|
11
|
+
|
12
|
+
def create_poller(m, seconds)
|
13
|
+
poller.stop if poller
|
14
|
+
@poller_created = Time.now
|
15
|
+
@poller_next = (Time.now + seconds).strftime('%T')
|
16
|
+
@poller = Timer(seconds) do
|
17
|
+
@poller_next = (Time.now + poller.interval).strftime('%T')
|
18
|
+
imap = imap_connect(m)
|
19
|
+
m.reply "DEBUG: connected to imap" if @monitor_debug
|
20
|
+
m.reply "DEBUG: imap is nil? %s" % imap.nil? if @monitor_debug
|
21
|
+
imap_poll(m, imap) unless imap.nil?
|
22
|
+
if maintenance_hour == Time.now.hour
|
23
|
+
imap_purge(m, imap, retain_days) if maintenance_needed
|
24
|
+
@maintenance_needed = false
|
25
|
+
else
|
26
|
+
@maintenance_needed = true
|
27
|
+
end
|
28
|
+
write(m, 'count') if count_incremented
|
29
|
+
imap.disconnect unless imap.nil?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def listen(m)
|
34
|
+
create_poller(m, interval) if autostart
|
35
|
+
end
|
36
|
+
|
37
|
+
match /monitor (start|stop|on|off)$/, method: :monitor
|
38
|
+
match /monitor debug (on|off)$/, method: :monitor_debug
|
39
|
+
match "monitor test", method: :imap_test
|
40
|
+
match "monitor clean", method: :imap_purge
|
41
|
+
match /monitor interval (\d+)/, method: :set_interval
|
42
|
+
match "monitor version", method: :about
|
43
|
+
match /monitor show (count|config|status)$/, method: :show
|
44
|
+
match /monitor show (count) (.+)/, method: :show
|
45
|
+
match /monitor write (count)/, method: :write
|
46
|
+
match "monitor help", method: :usage
|
47
|
+
match "monitor", method: :usage
|
48
|
+
|
49
|
+
attr_reader :interval, :poller, :poller_created, :poller_next, :autostart,
|
50
|
+
:maintenance_hour, :maintenance_needed, :retain_days, :mark_as_read,
|
51
|
+
:mail_host, :mail_user, :mail_password, :mail_folder, :mail_port, :mail_ssl,
|
52
|
+
:subject_matches, :from_rewrites, :count_incremented, :count_database, :count
|
53
|
+
|
54
|
+
def initialize(*args)
|
55
|
+
super
|
56
|
+
@monitor_debug = false
|
57
|
+
@mail_host = config[:host]
|
58
|
+
@mail_user = config[:user]
|
59
|
+
@mail_password = config[:password]
|
60
|
+
@mail_folder = config[:folder] || 'INBOX'
|
61
|
+
@mail_port = config[:port] || 143
|
62
|
+
@mail_ssl = config[:ssl] || false
|
63
|
+
@mark_as_read = config[:mark_as_read ] || true
|
64
|
+
@interval = config[:interval] || 300
|
65
|
+
@subject_matches = config[:subject_matches] || {}
|
66
|
+
@from_rewrites = config[:from_rewrites] || {}
|
67
|
+
@autostart = config[:autostart] || false
|
68
|
+
@retain_days = config[:retain_days] || 0
|
69
|
+
@maintenance_hour = config[:maintenance_hour] || 10
|
70
|
+
|
71
|
+
if config.has_key?(:count_database)
|
72
|
+
@count_database = config[:count_database]
|
73
|
+
else
|
74
|
+
@count_database = File.join(Dir.tmpdir, 'count.yml')
|
75
|
+
end
|
76
|
+
|
77
|
+
# Load or initialize a hash of counts
|
78
|
+
@count = load_count
|
79
|
+
end
|
80
|
+
|
81
|
+
def load_count(timestamp = year_and_month)
|
82
|
+
# load a hash where YYYY-MM is used for each key
|
83
|
+
YAML.load_file(count_database).fetch(timestamp)
|
84
|
+
rescue
|
85
|
+
if count.is_a?(Hash)
|
86
|
+
"No data found for #{timestamp}"
|
87
|
+
else
|
88
|
+
# initialize
|
89
|
+
Hash[:problem, 0, :recovery, 0, :acknowledged, 0, :other, 0]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
def year_and_month
|
93
|
+
"#{Time.now.year}-#{Time.now.strftime('%m')}"
|
94
|
+
end
|
133
95
|
|
96
|
+
# write to the file system
|
97
|
+
def write(m, command)
|
98
|
+
case command
|
99
|
+
when /count/
|
100
|
+
# read in existing data or create a new hash
|
101
|
+
data = YAML.load_file(count_database) || Hash.new
|
102
|
+
# add to it
|
103
|
+
data[year_and_month] = count
|
104
|
+
# write it out
|
105
|
+
open(count_database, 'w') { |f| f.puts data.to_yaml }
|
106
|
+
else
|
107
|
+
usage(m)
|
108
|
+
end
|
109
|
+
rescue => e
|
110
|
+
m.reply e
|
111
|
+
end
|
112
|
+
def about(m)
|
113
|
+
m.reply "Looks like I'm on version %s" % PLUGIN_VERSION
|
114
|
+
end
|
115
|
+
def set_interval(m, sec)
|
116
|
+
seconds = sec.to_i
|
117
|
+
create_poller(m, seconds)
|
118
|
+
end
|
119
|
+
def monitor(m, option)
|
120
|
+
action = case option
|
121
|
+
when "on", "start"
|
122
|
+
:start
|
123
|
+
when "off", "stop"
|
124
|
+
:stop
|
125
|
+
end
|
126
|
+
poller.send(action)
|
127
|
+
end
|
128
|
+
def monitor_debug(m, option)
|
129
|
+
@monitor_debug = option == "on"
|
130
|
+
end
|
131
|
+
def show(m, command, timestamp = year_and_month)
|
132
|
+
case command
|
133
|
+
when /config/
|
134
|
+
config.each do |k, v|
|
135
|
+
m.reply "#{k}: #{config[k]}" unless k == :password
|
136
|
+
end
|
137
|
+
when /count/
|
134
138
|
|
139
|
+
# Check formatting of argument
|
140
|
+
case timestamp
|
141
|
+
when /^\d{4}-\d{2}$/
|
142
|
+
|
143
|
+
# A past month may have been requested
|
144
|
+
data = timestamp == year_and_month ? count : load_count(timestamp)
|
145
|
+
counts = []
|
146
|
+
data.each do |k,v|
|
147
|
+
counts << "#{k.capitalize}: #{v}"
|
148
|
+
end if data.is_a?(Hash)
|
149
|
+
|
150
|
+
response = data.is_a?(Hash) ? counts.join(', ') : data
|
151
|
+
else
|
152
|
+
response = "Optional timestamp must be in YYYY-MM format."
|
153
|
+
end
|
154
|
+
|
155
|
+
m.reply response
|
156
|
+
when /status/
|
157
|
+
message = []
|
158
|
+
message << "Enabled: %s" % poller.started?
|
159
|
+
message << "Interval: %s" % poller.interval.to_i
|
160
|
+
message << "Next: %s" % poller_next if poller.started?
|
161
|
+
m.reply message.join(', ')
|
162
|
+
else
|
163
|
+
usage(m)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
def usage(m)
|
167
|
+
m.reply "Usage: !monitor <command>"
|
168
|
+
m.reply "Commands: start, stop, on, off, test, interval <seconds>, show <config|count [YYYY-MM]|status>"
|
169
|
+
end
|
170
|
+
def imap_test(m)
|
171
|
+
begin
|
172
|
+
imap = imap_connect(m)
|
173
|
+
status = []
|
174
|
+
status << "Retain Days: %s" % retain_days
|
175
|
+
status << "Before: %s" % imap.search(["BEFORE", get_old_date]).length
|
176
|
+
status << "Since: %s" % imap.search(["SINCE", get_old_date]).length
|
177
|
+
status << "Unseen: %s" % imap.search(["UNSEEN"]).length
|
178
|
+
status << "Seen: %s" % imap.search(["NOT", "NEW"]).length
|
179
|
+
imap.disconnect
|
180
|
+
m.reply status.join(', ')
|
181
|
+
end
|
182
|
+
end
|
183
|
+
def get_old_date(days = retain_days)
|
184
|
+
(Time.now - 60 * 60 * 24 * days).strftime("%d-%b-%Y")
|
185
|
+
end
|
186
|
+
|
187
|
+
def imap_purge(m, connection = imap_connect(m), retain = 0)
|
188
|
+
m.reply "Time to make the donuts!"
|
189
|
+
previous_date = get_old_date(retain)
|
190
|
+
connection.search(["BEFORE", get_old_date]).each do |message_id|
|
191
|
+
m.reply "Setting delete flag on #{message_id}"
|
192
|
+
envelope = connection.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
|
193
|
+
connection.store(message_id, "+FLAGS", [:Deleted])
|
194
|
+
end
|
195
|
+
m.reply "Running expunge"
|
196
|
+
connection.expunge
|
197
|
+
m.reply "Complete"
|
198
|
+
end
|
199
|
+
|
200
|
+
def get_messages(m, conn)
|
201
|
+
m.reply "DEBUG: in get_messages with %s " % conn if @monitor_debug
|
202
|
+
conn.search(["UNSEEN"]).each do |message_id|
|
203
|
+
envelope = conn.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
|
204
|
+
name = envelope.from[0].name
|
205
|
+
mailbox = envelope.from[0].mailbox
|
206
|
+
#reply_to = envelope.reply_to[0]
|
207
|
+
host = envelope.from[0].host
|
208
|
+
from = name.nil? ? host : name
|
209
|
+
subj = envelope.subject
|
210
|
+
conn.store(message_id, "+FLAGS", [:Seen]) if mark_as_read
|
211
|
+
m.reply("DEBUG: Found message") if @monitor_debug
|
212
|
+
yield from, host, subj
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def imap_connect(m)
|
217
|
+
connection = Net::IMAP.new(mail_host, mail_port, mail_ssl)
|
218
|
+
connection.login(mail_user, mail_password)
|
219
|
+
connection.select(mail_folder)
|
220
|
+
connection
|
221
|
+
rescue Net::IMAP::NoResponseError
|
222
|
+
m.reply "Mail server %s is offline" % mail_server
|
223
|
+
nil
|
224
|
+
end
|
225
|
+
|
226
|
+
def imap_poll(m, connection)
|
227
|
+
m.reply "DEBUG: in imap_poll with %s" % connection if @monitor_debug
|
228
|
+
|
229
|
+
# This is used by the write method.
|
230
|
+
# It will be set to true if anything is returned from the poll
|
231
|
+
@count_incremented = false
|
232
|
+
|
233
|
+
get_messages(m, connection) do |from, host, subj|
|
234
|
+
m.reply "DEBUG: returned from get_messages" if @monitor_debug
|
235
|
+
message_from, message_prefix = from, nil
|
236
|
+
from_rewrites.each do |k, v|
|
237
|
+
message_from = "#{v}" if from =~ /#{k}/ or host == k
|
238
|
+
end
|
239
|
+
subject_matches.each do |k, v|
|
240
|
+
message_prefix = "#{v}" if subj =~ /#{k}/
|
241
|
+
end
|
242
|
+
|
243
|
+
# for reporting
|
244
|
+
case subj
|
245
|
+
when /PROBLEM/
|
246
|
+
@count[:problem] += 1 if count.has_key?(:problem)
|
247
|
+
when /RECOVERY/
|
248
|
+
@count[:recovery] += 1 if count.has_key?(:recovery)
|
249
|
+
when /ACKNOWLEDGED/
|
250
|
+
@count[:acknowledged] += 1 if count.has_key?(:acknowledged)
|
251
|
+
else
|
252
|
+
@count[:other] += 1 if count.has_key?(:other)
|
253
|
+
end
|
254
|
+
m.reply "%s %s: %s" % [message_prefix, message_from, subj]
|
255
|
+
|
256
|
+
@count_incremented = true
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
metadata
CHANGED
@@ -1,84 +1,59 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: cinch-imap
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 0
|
8
|
-
- 8
|
9
|
-
version: 0.0.8
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- windowsrefund
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
dependencies:
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-06-24 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: cinch
|
22
|
-
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &14962700 !ruby/object:Gem::Requirement
|
24
17
|
none: false
|
25
|
-
requirements:
|
26
|
-
- -
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
- 1
|
30
|
-
- 1
|
31
|
-
- 1
|
32
|
-
version: 1.1.1
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.0.0
|
33
22
|
type: :runtime
|
34
|
-
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *14962700
|
35
25
|
description:
|
36
26
|
email: windowsrefund@gmail.com
|
37
27
|
executables: []
|
38
|
-
|
39
28
|
extensions: []
|
40
|
-
|
41
29
|
extra_rdoc_files: []
|
42
|
-
|
43
|
-
files:
|
30
|
+
files:
|
44
31
|
- LICENSE
|
45
32
|
- gpl.txt
|
46
|
-
- README.md
|
47
33
|
- CHANGES.md
|
48
34
|
- lib/cinch/plugins/imap.rb
|
49
|
-
has_rdoc: true
|
50
35
|
homepage:
|
51
36
|
licenses: []
|
52
|
-
|
53
37
|
post_install_message:
|
54
38
|
rdoc_options: []
|
55
|
-
|
56
|
-
require_paths:
|
39
|
+
require_paths:
|
57
40
|
- lib
|
58
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
42
|
none: false
|
60
|
-
requirements:
|
61
|
-
- -
|
62
|
-
- !ruby/object:Gem::Version
|
63
|
-
segments:
|
64
|
-
- 1
|
65
|
-
- 9
|
66
|
-
- 1
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
67
46
|
version: 1.9.1
|
68
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
48
|
none: false
|
70
|
-
requirements:
|
71
|
-
- -
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
|
74
|
-
- 0
|
75
|
-
version: "0"
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
76
53
|
requirements: []
|
77
|
-
|
78
54
|
rubyforge_project:
|
79
|
-
rubygems_version: 1.
|
55
|
+
rubygems_version: 1.8.10
|
80
56
|
signing_key:
|
81
57
|
specification_version: 3
|
82
58
|
summary: IMAP poller for Cinch Bot
|
83
59
|
test_files: []
|
84
|
-
|
data/README.md
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
# Cinch-Imap
|
2
|
-
|
3
|
-
The Cinch Imap Plugin. Poll an IMAP mailbox at a defined interval.
|
4
|
-
|
5
|
-
## Required Configuration
|
6
|
-
|
7
|
-
### :host
|
8
|
-
The IMAP server
|
9
|
-
### :user
|
10
|
-
The user id
|
11
|
-
### :password
|
12
|
-
The password
|
13
|
-
|
14
|
-
|
15
|
-
## Optional Configuration
|
16
|
-
|
17
|
-
### :port
|
18
|
-
The IMAP port. Default is 143.
|
19
|
-
### :ssl
|
20
|
-
Use SSL? Default is false.
|
21
|
-
### :interval
|
22
|
-
Number of seconds between polling. Default is 300.
|
23
|
-
### :mark_as_read
|
24
|
-
Sets the IMAP :Seen flag on polled messages. Default is true.
|
25
|
-
### :autostart
|
26
|
-
The bot will start polling when it joins the channel
|
27
|
-
|
28
|
-
## Commands
|
29
|
-
|
30
|
-
!monitor on/off
|
31
|
-
Enable/disable IMAP polling
|
32
|
-
!monitor status
|
33
|
-
Outputs status information to the channel
|
34
|
-
!monitor clear
|
35
|
-
Reset the number of messages seen to 0
|
36
|
-
!monitor interval [n]
|
37
|
-
Set polling interval in seconds. Default is 300.
|
38
|
-
!monitor showconfig
|
39
|
-
Outputs configuration to channel. The password attribute is skipped.
|
40
|
-
!monitor test
|
41
|
-
Poll the IMAP mailbox
|
42
|
-
|
43
|
-
## Example Configuration
|
44
|
-
|
45
|
-
> # mybot.rb
|
46
|
-
> require 'cinch'
|
47
|
-
> require 'cinch/plugins/imap'
|
48
|
-
>
|
49
|
-
> bot = Cinch::Bot.new do
|
50
|
-
> configure do |c|
|
51
|
-
> c.server = "my.ircserver.tld"
|
52
|
-
> c.nick = "cinch"
|
53
|
-
> c.channels = ["#mychannel"]
|
54
|
-
> c.plugins.plugins = [Cinch::Plugins::Imap]
|
55
|
-
> c.plugins.options[Cinch::Plugins::Imap] = {
|
56
|
-
:autostart => true,
|
57
|
-
> :host => 'my.imapserver.tld',
|
58
|
-
> :user => 'me@fqdn.tld',
|
59
|
-
> :password => "l3tm3out",
|
60
|
-
> :port => 993,
|
61
|
-
> :ssl => true,
|
62
|
-
> :subject_matches => {'ERROR' => '!!', 'SUCCESS' => '..'},
|
63
|
-
> :from_rewrites => {
|
64
|
-
> 'this@suchalong.silly.address' => 'foo',
|
65
|
-
> 'another@address.that.bugs.me' => 'bugger',
|
66
|
-
> },
|
67
|
-
> }
|
68
|
-
> end
|
69
|
-
> end
|
70
|
-
>
|
71
|
-
> bot.start
|
72
|
-
|
73
|
-
Now, run your bot.
|
74
|
-
|
75
|
-
> ruby mybot.rb
|
76
|
-
|
77
|
-
## WARNING
|
78
|
-
|
79
|
-
When enabled, this plugin will output message sender and subject data to the
|
80
|
-
channel. Do not enable this plugin on bots that are connected to public
|
81
|
-
channels if your email data is something you consider to meant for your
|
82
|
-
eyes only.
|
83
|
-
|
84
|
-
## TODO
|
85
|
-
|