bbrowning-ponder 0.0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2010, 2011 Tobias Bühlmann
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,334 @@
1
+ # Ponder
2
+
3
+ ## Description
4
+ Ponder (Stibbons) is a Domain Specific Language for writing IRC Bots using the [EventMachine](http://github.com/eventmachine/eventmachine "EventMachine") library.
5
+
6
+ ## Getting started
7
+ ### Installation
8
+ $ sudo gem install ponder
9
+
10
+ ### Configuring the Bot (Thaum!)
11
+ require 'rubygems'
12
+ require 'ponder'
13
+
14
+ @ponder = Ponder::Thaum.new
15
+
16
+ @ponder.configure do |c|
17
+ c.nick = 'Ponder'
18
+ c.server = 'irc.freenode.net'
19
+ c.port = 6667
20
+ end
21
+
22
+ ### Starting the Thaum
23
+ @ponder.connect
24
+
25
+ ### Event Handling
26
+ This naked Thaum will connect to the server and answer PING requests (and VERSION and TIME). If you want the Thaum to join a channel when it's connected, use the following:
27
+
28
+ @ponder.on :connect do
29
+ @ponder.join '#mended_drum'
30
+ end
31
+
32
+ If you want the Thaum to answer on specific channel messages, register this Event Handler:
33
+
34
+ @ponder.on :channel, /ponder/ do |event_data|
35
+ @ponder.message event_data[:channel], 'Heard my name!'
36
+ end
37
+
38
+ Now, if an incoming channel message contains the word "ponder", the Thaum will send the message "Heard my name!" to that specific channel. See the **Advanced Event Handling** chapter for more details on how to register Event Handlers and the `event_data` hash. For more examples, have a look at the examples directory.
39
+
40
+ ## Advanced Configuration
41
+ Besides the configuration for nick, server and port as shown in the **Getting Started** chapter, there are some more preferences Ponder accepts. All of them:
42
+
43
+ * `server`
44
+
45
+ The `server` variable describes the server the Thaum shall connect to. It defaults to `'localhost'`.
46
+
47
+ * `port`
48
+
49
+ `port` describes the port that is used for the connection. It defaults to `6667`.
50
+
51
+ * `nick`
52
+
53
+ `nick` describes the nick the Thaum will try to register when connecting to the server. It will not be updated if the Thaum changes its nick. It defaults to `'Ponder'`.
54
+
55
+ * `username`
56
+
57
+ `username` is used for describing the username. It defaults to `'Ponder'`.
58
+
59
+ * `real_name`
60
+
61
+ `real_name` is used for describing the real name. It defaults to `'Ponder'`.
62
+
63
+ * `verbose`
64
+
65
+ If `verbose` is set to `true`, all incoming and outgoing traffic will be put to the console. Plus exceptions raised in Callbacks (errors). It defaults to `true`.
66
+
67
+ * `logging`
68
+
69
+ If `logging` is set to `true`, all incoming and outgoing traffic will be logged to logs/traffic.log and errors will be logged to logs/error.log. If you just want to log errors, you can manipulate the traffic logger in the configure block with `c.traffic_logger = c.empty_logger`. A logger set to `empty_logger` will not log anything.
70
+
71
+ You can also define other loggers for traffic\_logger and error\_logger with `c.traffic_logger = @my_cool_logger` or `c.traffic_logger = Logger.new(...)`. Per default, there are just #info and #error called on the logger.
72
+
73
+ You can access the logger instances via `@ponder.traffic_logger` or `@ponder.error_logger`, so you could do: `@ponder.traffic_logger.info('I did this and that right now')`.
74
+
75
+ It defaults to `false`. (TODO: write about "other" logger methods)
76
+
77
+ * `reconnect`
78
+
79
+ If `reconnect` is set to `true`, the Thaum will try to reconnect after being disconnected from the server (netsplit, ...). It will not try to reconnect if you call `quit` on the Thaum. It defaults to `true`.
80
+
81
+ * `reconnect_interval`
82
+
83
+ If `reconnect` is set to `true`, `reconnect_interval` describes the time in seconds, which the Thaum will wait before trying to reconnect. It defaults to `30`.
84
+
85
+ For further information, have a look at the examples.
86
+
87
+ ## Advanced Event Handling
88
+ A Thaum can react on several events, so here is a list of handlers that can be used as argument in the `on` method:
89
+
90
+ * `join`
91
+
92
+ The `join` handler reacts, if an user joins a channel in which the Thaum is in. Example:
93
+
94
+ @ponder.on :join do
95
+ # ...
96
+ end
97
+
98
+ If using a block variable, you have access to a hash with detailed information about the event. Example:
99
+
100
+ @ponder.on :join do |event_data|
101
+ @ponder.message event_data[:channel], "Hello #{event_data[:nick]}! Welcome to #{event_data[:channel]}."
102
+ end
103
+
104
+ Which will greet a joined user with a channel message.
105
+
106
+ The hash contains data for the keys `:nick`, `:user`, `:host` and `:channel`.
107
+
108
+ * `part`
109
+
110
+ Similar to the `join` handler but reacts on parting users. The block variable hash contains data for the keys `:nick`, `:user`, `:host`, `:channel` and `:message`. The value for `:message` is the message the parting user leaves.
111
+
112
+ * `quit`
113
+
114
+ The `quit` handler reacts, if an user quits from the server (and the Thaum can see it in a channel). The block variable hash contains data for the keys `:nick`, `:user`, `:host` and `:message`.
115
+
116
+ * `channel`
117
+
118
+ If an user sends a message to a channel, you can react with the `channel` handler. Example (from above):
119
+
120
+ @ponder.on :channel, /ponder/ do |event_data|
121
+ @ponder.message event_data[:channel], 'Heard my name!'
122
+ end
123
+
124
+ The block variable hash contains data for the keys `:nick`, `:user`, `:host`, `:channel` and `:message`.
125
+
126
+ * `query`
127
+
128
+ The `query` handler is like the `channel` handler, but for queries. Same keys in the data hash but no `:channel`.
129
+
130
+ * `nickchange`
131
+
132
+ `nickchange` reacts on nickchanges. Data hash keys are `:nick`, `:user`, `:host` and `:new_nick`, where `nick` is the nick before renaming and `new_nick` the nick after renaming.
133
+
134
+ * `kick`
135
+
136
+ If an user is being kicked, the `kick` handler can handle that event. Data hash keys are: `:nick`, `:user`, `:host`, `:channel`, `:victim` and `:reason`.
137
+
138
+ * `topic`
139
+
140
+ `topic` is for reacting on topic changes. Data hash keys are: `:nick`, `:user`, `:host`, `:channel` and `:topic`, where `:topic` is the new topic. You can provide a Regexp to just react on specific patterns:
141
+
142
+ @ponder.on :topic, /foo/ do |event_data|
143
+ # ...
144
+ end
145
+
146
+ This will just work for topics that include the word "foo".
147
+
148
+ * `disconnect`
149
+
150
+ `disconnect` reacts on being disconnected from the server (netsplit, quit, ...). It does not react if you exit the program with ^C.
151
+
152
+ * Raw numerics
153
+
154
+ A Thaum can seperately react on events with raw numerics, too. So you could do:
155
+
156
+ @ponder.on 301 do |event_data|
157
+ # ...
158
+ end
159
+
160
+ The data hash will contain the `:params` key. The corresponding value is the complete traffic line that came in.
161
+
162
+ For all Event Handlers there is a `:type` key in the data hash (if the variable is specified). Its value gives the type of event, like `:channel`, `:join` or `301`.
163
+
164
+ You can even share handler bodies between different events. So you are able to do something like this:
165
+
166
+ @ponder.on [:join, :part, :quit] do |event_data|
167
+ # ...
168
+ end
169
+
170
+ or
171
+
172
+ @ponder.on [:channel, :query], /ponder/ do |event_data|
173
+ @ponder.message((event_data[:channel] || event_data[:nick]), 'Yes?')
174
+ end
175
+
176
+ or
177
+
178
+ @ponder.on [:channel, :nickchange], /foo/ do |event_data|
179
+ # ...
180
+ end
181
+
182
+ They are not really shared at all, Ponder will just copy the Callback, but it's comfortable.
183
+
184
+ ## Commanding the Thaum
185
+ Command the Thaum, very simple. Just call a method listed below on the Ponder object. I will keep this short, since I assume you're at least little experienced with IRC.
186
+
187
+ * `message(recipient, message)`
188
+ * `notice(recipient, message)`
189
+ * `mode(recipient, option)`
190
+ * `kick(channel, user, reason = nil)`
191
+ * `action(recipient, message)`
192
+ * `topic(channel, topic)`
193
+ * `join(channel, password = nil)`
194
+ * `part(channel, message = nil)`
195
+ * `quit(message = nil)`
196
+ * `rename(nick)`
197
+ * `away(message = nil)`
198
+ * `back`
199
+ * `invite(nick, channel)`
200
+ * `ban(channel, address)`
201
+
202
+ Last but not least some cool "give me something back" methods:
203
+
204
+ * `get_topic(channel)`
205
+
206
+ * Possible return values for `get_topic` are:
207
+
208
+ * `{:raw_numeric => 331, :message => 'No topic is set'}` if no topic is set
209
+ * `{:raw_numeric => 332, :message => message}` with `message` the topic message
210
+ * `{:raw_numeric => 403, :message => 'No such channel'}` if there is no such channel
211
+ * `{:raw_numeric => 442, :message => "You're not on that channel"}` if you cannot actually see the topic
212
+ * `false` if the request times out (30 seconds)
213
+
214
+ * `channel_info(channel)`
215
+
216
+ * Possible return values:
217
+
218
+ * If successful, a hash with keys:
219
+
220
+ * `:modes` (letters)
221
+ * `:channel_limit` (if channel limit is set)
222
+ * `:created_at` (Time object of the time the channel was created)
223
+
224
+ * `false`, if the request is not successful or times out (30 seconds)
225
+
226
+ * `whois(nick)`
227
+
228
+ * Possible return values:
229
+
230
+ * If successful, a hash with keys:
231
+
232
+ * `:nick`
233
+ * `:username`
234
+ * `:host`
235
+ * `:real_name`
236
+ * `:server` (a hash with the keys `:address` and `:name`)
237
+ * `:channels` (a hash like `{'#foo' => '@', '#bar' => nil}` where the values are user privileges)
238
+ * `:registered` (`true`, if registered, else `nil`)
239
+
240
+ * If not successful
241
+
242
+ * `false`
243
+
244
+ * If times out (30 seconds)
245
+
246
+ * `nil`
247
+
248
+ Example:
249
+
250
+ # Ponder, kick an user (and check if I'm allowed to command you)!
251
+ @ponder.on :channel, /^!kick \S+$/ do |event_data|
252
+ user_data = @ponder.whois(event_data[:nick])
253
+ if user_data[:registered] && (user_data[:channels][event_data[:channel]] == '@')
254
+ user_to_kick = event_data[:message].split(' ')[1]
255
+ @ponder.kick event_data[:channel], user_to_kick, 'GO!'
256
+ end
257
+ end
258
+
259
+ ## Filters
260
+ ### Before Filters
261
+ You can have Before Filters! They are called before each event handling process and can - among other things - manipulate the `event_data` hash. If a Before Filter returns `false`, no further filters (no After Filters either) are called and the event handling process won't fire up. Example:
262
+
263
+ @ponder.before_filter(:channel, /foo/) do
264
+ # ...
265
+ end
266
+
267
+ This Before Filter will be called, if a channel message with the word "foo" gets in. You can use all other event types (like :query, :kick, ...) as well. Also possible is an array notation like `before_filter([:query, :channel], /foo/) ...`. If you want the filter to work an all event types, you can simply use `:all`. Filters will be called in defining order; first defined, first called. Event specific filters are called before `:all` filters.
268
+
269
+ ### After Filters
270
+ After Filters work the same way as Before Filters do, just after the actual event handling process. An After Filter does not hinder later After Filters to fire up if it returns `false`. Example:
271
+
272
+ @ponder.after_filter(:all, //) do
273
+ # ...
274
+ end
275
+
276
+ ## Timers
277
+ If you need something in an event handling process to be time-displaced, you should not use `sleep`. I recommend using the comfortable timer methods EventMachine provides. A one shot timer looks like this:
278
+
279
+ EventMachine::Timer.new(10) do
280
+ # code to be run after 10 seconds
281
+ end
282
+
283
+ If you want the timer to be canceled before starting, you can do it like this:
284
+
285
+ timer = EventMachine::Timer.new(10) do
286
+ # code to be run after 10 seconds
287
+ end
288
+
289
+ # ...
290
+
291
+ timer.cancel
292
+
293
+ You can even have periodic timers which will fire up every n seconds:
294
+
295
+ EventMachine::PeriodicTimer.new(10) do
296
+ # code to be run every 10 seconds
297
+ end
298
+
299
+ A periodic timer can be canceled just like the other one.
300
+
301
+ ## Formatting
302
+ You can format your messages with colors, make it bold, italic or underlined. All of those formatting constants are availabe through `Ponder::Formatting`.
303
+
304
+ ### Colors
305
+ For coloring text you first set the color code with `Ponder::Formatting::COLOR_CODE` followed by a color followed by the text. For ending the colored text, set the uncolor code with `Ponder::Formatting::UNCOLOR`.
306
+
307
+ Availabe colors are white, black, blue, green, red, brown, purple, orange, yellow, lime, teal, cyan, royal, pink, gray and silver. You can set one with the `Ponder::Formatting::COLORS` hash. Example:
308
+
309
+ "This will be #{Ponder::Formatting::COLOR_CODE}#{Ponder::Formatting::COLORS[:red]}red#{Ponder::Formatting::UNCOLOR_CODE}. This not."
310
+
311
+ ### Font Styles
312
+ If you want to make a text bold, italic or underlined, use `Ponder::Formatting::BOLD`, `Ponder::Formatting::ITALIC` or `Ponder::Formatting::UNDERLINE`. After the text, close it with the same constant. Example:
313
+
314
+ "This will be #{Ponder::Formatting::UNDERLINE}underlined#{Ponder::Formatting::UNDERLINE}. This not."
315
+
316
+ ### Shortened Formatting
317
+ If you don't always want to use `Ponder::Formatting`, use `include Ponder::Formatting`. All constants will then be availabe without `Ponder::Formatting` in front.
318
+
319
+ ## Source
320
+ The source can be found at GitHub: [tbuehlmann/ponder](http://github.com/tbuehlmann/ponder "Ponder").
321
+
322
+ You can contact me through [GitHub](http://github.com/tbuehlmann/ "GitHub") and IRC (named tbuehlmann in the Freenode network).
323
+
324
+ ## Discworld Context
325
+ So, why all that silly names? Ponder Stibbons? Thaum? Twoflogger (referring to Twoflower), BlindIO? What's the Mended Drum? Who's the Librarian? Simply put, I freaking enshrine Terry Pratchett's Discworld Novels and there were no better name for this project than Ponder. Ponder Stibbons is the Head of Inadvisably Applied Magic at the Unseen University of Ankh Morpork. He researched the Thaum, like the atom, just for magic. And I just love that character, so there we are. If you're a fan too or want to talk about the Discworld, the framework, whatever, don't hesitate to contact me.
326
+
327
+ ## License
328
+ Copyright (c) 2010, 2011 Tobias Bühlmann
329
+
330
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
331
+
332
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
333
+
334
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ require 'pathname'
2
+ $LOAD_PATH.unshift Pathname.new(__FILE__).dirname.expand_path.join('..', 'lib')
3
+
4
+ require 'ponder'
5
+
6
+ # This Thaum will parrot all channel messages.
7
+ @ponder = Ponder::Thaum.new
8
+
9
+ @ponder.configure do |c|
10
+ c.server = 'chat.freenode.org'
11
+ c.port = 6667
12
+ c.nick = 'Ponder'
13
+ c.verbose = true
14
+ c.logging = false
15
+ end
16
+
17
+ @ponder.on :connect do
18
+ @ponder.join '#ponder'
19
+ end
20
+
21
+ @ponder.on :channel, // do |event_data|
22
+ @ponder.message event_data[:channel], event_data[:message]
23
+ end
24
+
25
+ @ponder.connect
@@ -0,0 +1,31 @@
1
+ require 'pathname'
2
+ $LOAD_PATH.unshift Pathname.new(__FILE__).dirname.expand_path.join('..', 'lib')
3
+
4
+ require 'ponder'
5
+ require 'rubygems'
6
+ require 'nokogiri'
7
+ require 'open-uri'
8
+
9
+ # This Thaum answers the channel message "blog?" with the title of the newest github blog entry.
10
+ @ponder = Ponder::Thaum.new
11
+
12
+ @ponder.configure do |c|
13
+ c.server = 'chat.freenode.org'
14
+ c.port = 6667
15
+ c.nick = 'Ponder'
16
+ c.verbose = true
17
+ c.logging = false
18
+ end
19
+
20
+ @ponder.on :connect do
21
+ @ponder.join '#ponder'
22
+ end
23
+
24
+ @ponder.on :channel, /^blog\?$/ do |event_data|
25
+ doc = Nokogiri::HTML(open('http://github.com/blog'))
26
+ title = doc.xpath('//html/body/div/div[2]/div/div/ul/li/h2/a')[0].text
27
+
28
+ @ponder.message event_data[:channel], "Newest Github Blog Post: #{title}"
29
+ end
30
+
31
+ @ponder.connect
@@ -0,0 +1,140 @@
1
+ # This Thaum remembers users' actions and is able to quote them.
2
+ # Go for it with `!seen <nick>`. You can even use wildcards with "*".
3
+ #
4
+ # The redis server version needs to be >= 1.3.10 for hash support.
5
+
6
+ require 'pathname'
7
+ $LOAD_PATH.unshift Pathname.new(__FILE__).dirname.expand_path.join('..', 'lib')
8
+ require 'ponder'
9
+ require 'rubygems'
10
+ require 'redis'
11
+
12
+ FORMAT = '%Y-%m-%d %H:%M:%S'
13
+
14
+ class String
15
+ def escape_redis
16
+ self.gsub(/([\|\[\]\?])/, '\\\\\1')
17
+ end
18
+ end
19
+
20
+ @last_seen = Redis.new(:thread_safe => true)
21
+
22
+ def remember(lowercase_nick, nick, user, host, channel, action,
23
+ action_content)
24
+ @last_seen.hmset(lowercase_nick,
25
+ 'nick', nick,
26
+ 'user', user,
27
+ 'host', host,
28
+ 'channel', channel,
29
+ 'action', action,
30
+ 'action_content', action_content,
31
+ 'updated_at', Time.now.to_i)
32
+ end
33
+
34
+ @ponder = Ponder::Thaum.new
35
+
36
+ @ponder.configure do |c|
37
+ c.server = 'chat.freenode.org'
38
+ c.port = 6667
39
+ c.nick = 'Ponder'
40
+ c.verbose = true
41
+ c.logging = false
42
+ end
43
+
44
+ @ponder.on :connect do
45
+ @ponder.join '#ponder'
46
+ end
47
+
48
+ @ponder.on :channel do |event_data|
49
+ remember(event_data[:nick].downcase, event_data[:nick], event_data[:user],
50
+ event_data[:host], event_data[:channel], event_data[:type],
51
+ event_data[:message])
52
+ end
53
+
54
+ @ponder.on [:join, :part, :quit] do |event_data|
55
+ remember(event_data[:nick].downcase, event_data[:nick], event_data[:user],
56
+ event_data[:host], event_data[:channel], event_data[:type],
57
+ (event_data[:message] || event_data[:channel]))
58
+ end
59
+
60
+ @ponder.on :nickchange do |event_data|
61
+ remember(event_data[:nick].downcase, event_data[:nick], event_data[:user],
62
+ event_data[:host], '', 'nickchange_old', event_data[:new_nick])
63
+
64
+ remember(event_data[:new_nick].downcase, event_data[:new_nick],
65
+ event_data[:user], event_data[:host], '', 'nickchange_new',
66
+ event_data[:nick])
67
+ end
68
+
69
+ @ponder.on :kick do |event_data|
70
+ remember(event_data[:nick].downcase, event_data[:nick], event_data[:user],
71
+ event_data[:host], event_data[:channel], 'kicker',
72
+ "#{event_data[:victim]} #{event_data[:reason]}")
73
+
74
+ remember(event_data[:victim].downcase, event_data[:victim],
75
+ '', '', event_data[:channel], 'victim',
76
+ "#{event_data[:nick]} #{event_data[:reason]}")
77
+ end
78
+
79
+ def last_seen(nick, event_data)
80
+ data = @last_seen.hgetall nick
81
+
82
+ case data['action']
83
+ when 'channel'
84
+ "#{data['nick']} wrote something in #{data['channel']} at #{Time.at(data['updated_at'].to_i).strftime(FORMAT)}."
85
+ when 'join'
86
+ "#{data['nick']} joined #{data['channel']} at #{Time.at(data['updated_at'].to_i).strftime(FORMAT)}."
87
+ when 'part'
88
+ "#{data['nick']} left #{data['channel']} at #{Time.at(data['updated_at'].to_i).strftime(FORMAT)} (#{data['action_content']})."
89
+ when 'quit'
90
+ "#{data['nick']} quit at #{Time.at(data['updated_at'].to_i).strftime(FORMAT)} (#{data['action_content']})."
91
+ when 'nickchange_old'
92
+ "#{data['nick']} renamed to #{data['action_content']}} at #{Time.at(data['updated_at'].to_i).strftime(FORMAT)}."
93
+ when 'nickchange_new'
94
+ "#{data['nick']} renamed to #{data['action_content']} at #{Time.at(data['updated_at'].to_i).strftime(FORMAT)}."
95
+ when 'kicker'
96
+ "#{data['nick']} kicked #{data['action_content'].split(' ')[0]} at #{Time.at(data['updated_at'].to_i).strftime(FORMAT)} from #{data['channel']} (#{data['action_content'].split(' ')[1]})."
97
+ when 'victim'
98
+ "#{data['nick']} was kicked by #{data['action_content'].split(' ')[0]} at #{Time.at(data['updated_at'].to_i).strftime(FORMAT)} from #{data['channel']} (#{data['action_content'].split(' ')[1]})."
99
+ end
100
+ end
101
+
102
+ @ponder.on :channel, /^!seen \S+$/ do |event_data|
103
+ nick = event_data[:message].split(' ')[1].downcase
104
+
105
+ # wildcards
106
+ if nick =~ /\*/
107
+ users = @last_seen.keys nick.escape_redis
108
+ results = users.length
109
+
110
+ case results
111
+ when 0
112
+ @ponder.message event_data[:channel], 'No such nick found.'
113
+ when 1
114
+ @ponder.message last_seen(users[0])
115
+ when 2..5
116
+ nicks = []
117
+ users.each do |user|
118
+ nicks << @last_seen.hgetall(user)['nick']
119
+ end
120
+ nicks = nicks.join(', ')
121
+ @ponder.message event_data[:channel], "#{results} nicks found (#{nicks})."
122
+ else
123
+ @ponder.message event_data[:channel], "Too many results (#{results})."
124
+ end
125
+ # single search
126
+ elsif @last_seen.exists nick
127
+ msg = last_seen(nick, event_data)
128
+ if online_nick = @ponder.whois(nick)
129
+ msg = "#{online_nick[:nick]} is online. (#{msg})"
130
+ end
131
+ @ponder.message event_data[:channel], msg
132
+ elsif online_nick = @ponder.whois(nick)
133
+ @ponder.message event_data[:channel], "#{online_nick[:nick]} is online."
134
+ else
135
+ @ponder.message event_data[:channel], "#{nick} not found."
136
+ end
137
+ end
138
+
139
+ @ponder.connect
140
+