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.
Files changed (4) hide show
  1. data/CHANGES.md +3 -0
  2. data/lib/cinch/plugins/imap.rb +255 -130
  3. metadata +27 -52
  4. data/README.md +0 -85
data/CHANGES.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # CHANGES
2
+ ## 0.1.0
3
+ - Switch IMAP poller to native Timer class (now requires cinch > 2.0)
4
+ - cleanup
2
5
  ## 0.0.8
3
6
  - autostart option added
4
7
  - test command added
@@ -1,134 +1,259 @@
1
1
  require 'net/imap'
2
+ require 'yaml'
3
+ require 'tmpdir'
2
4
 
3
- module Cinch
4
- module Plugins
5
- class Imap
6
-
7
- PLUGIN_VERSION='0.0.8'
8
-
9
- include Cinch::Plugin
10
-
11
- listen_to :join
12
-
13
- match /monitor (on|off)/, method: :monitor
14
- match /monitor test/, method: :imap_test
15
- match /monitor status/, method: :status
16
- match /monitor showconfig/, method: :showconfig
17
- match /monitor clear/, method: :clear
18
- match /monitor interval (\d+)/, method: :interval
19
- match /monitor version/, method: :about
20
- match /monitor$/, method: :usage
21
-
22
- def initialize(*args)
23
- super
24
- @mail_host = config[:host]
25
- @mail_user = config[:user]
26
- @mail_password = config[:password]
27
- @mail_folder = config[:folder] || 'INBOX'
28
- @mail_port = config[:port] || 143
29
- @mail_ssl = config[:ssl] || false
30
- @mark_as_read = config[:mark_as_read ] || true
31
- @interval = config[:interval] || 300
32
- @subject_matches = config[:subject_matches] || {}
33
- @from_rewrites = config[:from_rewrites] || {}
34
- @messages_seen = 0
35
- @started = Time.now
36
- @monitor = config[:autostart] || false
37
- end
38
-
39
- def about(m)
40
- m.reply "Looks like I'm on #{PLUGIN_VERSION} #{m.user.nick}"
41
- end
42
-
43
- def interval(m, seconds)
44
- @interval = seconds.to_i
45
- @monitor = @interval > 0
46
- end
47
-
48
- def monitor(m, option)
49
- @monitor = option == "on"
50
- end
51
-
52
- def clear
53
- @messages_seen = 0
54
- end
55
-
56
- def showconfig(m)
57
- config.each do |k, v|
58
- m.reply "#{k} is #{config[k]}" unless k == :password
59
- end
60
- end
61
-
62
- def status(m)
63
- now = Time.now
64
- days = ((now - @started)/(3600 * 24)).to_i
65
- hours = ((now - @started)/3600).to_i
66
- minutes = ((now - @started)/60).to_i
67
- m.reply "Poller: #{@monitor ? 'on' : 'off'} | Uptime: #{days} days, #{hours}:#{minutes} | Interval: #{@interval.to_s} | # Seen: #{@messages_seen}"
68
- end
69
-
70
- def usage(m)
71
- m.user.send "Usage: !monitor <command>"
72
- m.user.send "Commands: on, off, test, status, clear, interval <seconds>"
73
- end
74
-
75
- def imap_test(m)
76
- begin
77
- imap = imap_connect
78
- unseen = imap.search(["UNSEEN"]).length
79
- old = imap.search(["NOT", "NEW"]).length
80
- m.reply "Old: #{old} Unseen: #{unseen}"
81
- imap.disconnect
82
- end
83
- end
84
- def listen(m)
85
- background_job do
86
- imap = imap_connect
87
- imap_poll(m, imap)
88
- imap.disconnect
89
- end if (@monitor && m.user == @bot)
90
- end
91
- def get_messages(conn)
92
- conn.search(["UNSEEN"]).each do |message_id|
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
- prerelease: false
5
- segments:
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
- date: 2011-02-14 00:00:00 -05:00
18
- default_executable:
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
- prerelease: false
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
- segments:
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
- version_requirements: *id001
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
- segments:
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.3.7
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
-