cinch-imap 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
-