rhuidean 0.2.1
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/README +17 -0
- data/lib/rhuidean.rb +11 -0
- data/lib/rhuidean/client.rb +445 -0
- data/lib/rhuidean/event.rb +113 -0
- data/lib/rhuidean/methods.rb +71 -0
- data/lib/rhuidean/numeric.rb +178 -0
- data/lib/rhuidean/timer.rb +103 -0
- data/rakefile +70 -0
- data/test/tc_client.rb +73 -0
- data/test/ts_rhuidean.rb +14 -0
- metadata +76 -0
data/README
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small IRC client library
|
|
3
|
+
# README: at-a-glance documentation
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2004-2009 Eric Will <rakaur@malkier.net>
|
|
6
|
+
# Copyright (c) 2003-2004 shrike development team
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
This repository contains a simple, quick IRC client library. If you have a
|
|
10
|
+
decent understanding of IRC and want to control most of the IRC events
|
|
11
|
+
yourself this is very easy and quick to pick up. The library handles all of
|
|
12
|
+
the socket code and sets up a simple event handling system for IRC events.
|
|
13
|
+
|
|
14
|
+
It's not very well documented yet, but I'm still working on it.
|
|
15
|
+
|
|
16
|
+
The code itself is documented. Run `rake rdoc` and check out the doc/ directory.
|
|
17
|
+
|
data/lib/rhuidean.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small, lightweight IRC client library
|
|
3
|
+
# lib/rhuidean.rb: IRC client library
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# Import required rhuidean modules.
|
|
9
|
+
%w(client event methods numeric timer).each do |m|
|
|
10
|
+
require 'rhuidean/' + m
|
|
11
|
+
end
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small, lightweight IRC client library
|
|
3
|
+
# lib/rhuidean/client.rb: IRC::Client class
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# Import required Ruby modules.
|
|
9
|
+
%w(logger socket).each { |m| require m }
|
|
10
|
+
|
|
11
|
+
module IRC
|
|
12
|
+
|
|
13
|
+
# The IRC::Client class acts as an abstract interface to the IRC protocol.
|
|
14
|
+
class Client
|
|
15
|
+
##
|
|
16
|
+
# constants
|
|
17
|
+
VERSION = '0.2.1'
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# instance attributes
|
|
21
|
+
attr_accessor :server, :port, :password, :debug,
|
|
22
|
+
:nickname, :username, :realname, :bind_to
|
|
23
|
+
|
|
24
|
+
# Our TCPSocket.
|
|
25
|
+
attr_reader :socket, :channels
|
|
26
|
+
|
|
27
|
+
# A simple Exeption class.
|
|
28
|
+
class Error < Exception
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# Creates a new IRC::Client object. If there is a block given, passes
|
|
33
|
+
# itself to the block for pretty attribute setting.
|
|
34
|
+
# ---
|
|
35
|
+
# <tt>client = IRC::Client.new do |c|
|
|
36
|
+
# c.server = 'irc.malkier.net'
|
|
37
|
+
# c.port = 6667
|
|
38
|
+
# c.password = 'partypants'
|
|
39
|
+
# c.nickname = 'rakaur'
|
|
40
|
+
# c.username = 'rakaur'
|
|
41
|
+
# c.realname = 'watching the weather change'
|
|
42
|
+
# c.bind_to = '10.0.1.20'
|
|
43
|
+
#
|
|
44
|
+
# c.logger = Logger.new($stderr)
|
|
45
|
+
# c.debug = false
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# t = Thread.new { client.io_loop }
|
|
49
|
+
# Thread.list.each { |t| t.join unless t == Thread.main } # or similar...
|
|
50
|
+
# [...]
|
|
51
|
+
# client.quit("IRC quit message!")
|
|
52
|
+
# client.exit
|
|
53
|
+
# ---
|
|
54
|
+
# returns:: +self+
|
|
55
|
+
#
|
|
56
|
+
def initialize
|
|
57
|
+
# Is our socket dead?
|
|
58
|
+
@dead = false
|
|
59
|
+
@connected = false
|
|
60
|
+
|
|
61
|
+
# List of channels we're on
|
|
62
|
+
@channels = []
|
|
63
|
+
|
|
64
|
+
# Received data waiting to be parsed.
|
|
65
|
+
@recvq = []
|
|
66
|
+
|
|
67
|
+
# Data waiting to be sent.
|
|
68
|
+
@sendq = []
|
|
69
|
+
|
|
70
|
+
# Our event queue.
|
|
71
|
+
@eventq = EventQueue.new
|
|
72
|
+
|
|
73
|
+
# Our Logger object.
|
|
74
|
+
@logger = Logger.new($stderr)
|
|
75
|
+
@debug = false
|
|
76
|
+
|
|
77
|
+
# If we have a block let it set up our instance attributes.
|
|
78
|
+
yield(self) if block_given?
|
|
79
|
+
|
|
80
|
+
# Set up event handlers.
|
|
81
|
+
set_default_handlers
|
|
82
|
+
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#######
|
|
87
|
+
private
|
|
88
|
+
#######
|
|
89
|
+
|
|
90
|
+
#
|
|
91
|
+
# Sets up some default event handlers to track various states and such.
|
|
92
|
+
# ---
|
|
93
|
+
# returns:: +self+
|
|
94
|
+
#
|
|
95
|
+
def set_default_handlers
|
|
96
|
+
on(:read_ready) { read }
|
|
97
|
+
on(:write_ready) { write }
|
|
98
|
+
on(:recvq_ready) { parse }
|
|
99
|
+
on(:dead) { self.dead = true }
|
|
100
|
+
|
|
101
|
+
on(Numeric::RPL_WELCOME) { log("connected to #@server:#@port") }
|
|
102
|
+
|
|
103
|
+
on(:PING) { |m| raw("PONG :#{m.target}") }
|
|
104
|
+
|
|
105
|
+
on(:JOIN) do |m|
|
|
106
|
+
@channels << m.target if m.origin =~ /^(#{@nickname})\!(.+)\@(.+)$/
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
on(:PART) do |m|
|
|
110
|
+
if m.origin =~ /^(#{@nickname})\!(.+)\@(.+)$/
|
|
111
|
+
@channels.delete(m.target)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
on(:KICK) do |m|
|
|
116
|
+
@channels.delete(m.target) if m.params[0] == @nickname
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
on(:exit) do |from|
|
|
120
|
+
log("exiting via #{from}...")
|
|
121
|
+
Thread.exit
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
self
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
#
|
|
128
|
+
# Verifies all required attributes are set.
|
|
129
|
+
# ---
|
|
130
|
+
# raises:: IRC::Client::Error if tests fail
|
|
131
|
+
# returns:: +self+ if tests pass
|
|
132
|
+
#
|
|
133
|
+
def verify_attributes
|
|
134
|
+
raise(Error, 'need a server to connect to') unless server
|
|
135
|
+
raise(Error, 'need a port to connect to') unless port
|
|
136
|
+
raise(Error, 'need a nickname to connect with') unless nickname
|
|
137
|
+
raise(Error, 'need a username to connect with') unless username
|
|
138
|
+
raise(Error, 'need a realname to connect with') unless realname
|
|
139
|
+
|
|
140
|
+
self
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
#
|
|
144
|
+
# Takes care of setting some stuff when we die.
|
|
145
|
+
# ---
|
|
146
|
+
# bool:: +true+ or +false+
|
|
147
|
+
# returns:: +nil+
|
|
148
|
+
#
|
|
149
|
+
def dead=(bool)
|
|
150
|
+
if bool == true
|
|
151
|
+
log("lost connection to #@server:#@port")
|
|
152
|
+
@dead = Time.now.to_i
|
|
153
|
+
@socket = nil
|
|
154
|
+
@connected = false
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
#
|
|
159
|
+
# Called when we're ready to read.
|
|
160
|
+
# ---
|
|
161
|
+
# returns:: +self+
|
|
162
|
+
#
|
|
163
|
+
def read
|
|
164
|
+
begin
|
|
165
|
+
ret = @socket.readpartial(8192)
|
|
166
|
+
rescue Errno::EAGAIN
|
|
167
|
+
retry
|
|
168
|
+
rescue EOFError
|
|
169
|
+
ret = nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
unless ret
|
|
173
|
+
@eventq.post(:dead)
|
|
174
|
+
return
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# This passes every "line" to our block, including the "\n".
|
|
178
|
+
ret.scan(/(.+\n?)/) do |line|
|
|
179
|
+
line = line[0]
|
|
180
|
+
|
|
181
|
+
# If the last line had no \n, add this one onto it.
|
|
182
|
+
if @recvq[-1] and @recvq[-1][-1].chr != "\n"
|
|
183
|
+
@recvq[-1] += line
|
|
184
|
+
else
|
|
185
|
+
@recvq << line
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
if @recvq[-1] and @recvq[-1][-1].chr == "\n"
|
|
190
|
+
@eventq.post(:recvq_ready)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
self
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
#
|
|
197
|
+
# Called when we're ready to write.
|
|
198
|
+
# ---
|
|
199
|
+
# returns:: +self+
|
|
200
|
+
#
|
|
201
|
+
def write
|
|
202
|
+
begin
|
|
203
|
+
# Use shift because we need it to fall off immediately.
|
|
204
|
+
while line = @sendq.shift
|
|
205
|
+
line += "\r\n"
|
|
206
|
+
debug(line)
|
|
207
|
+
@socket.write(line)
|
|
208
|
+
end
|
|
209
|
+
rescue Errno::EAGAIN
|
|
210
|
+
retry
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Note that this doesn't match *every* IRC message,
|
|
215
|
+
# just the ones we care about. It also doesn't match
|
|
216
|
+
# every IRC message in the way we want. We get what
|
|
217
|
+
# we need. The rest is ignored.
|
|
218
|
+
#
|
|
219
|
+
# Here's a compact version if you need it:
|
|
220
|
+
# ^(?:\:([^\s]+)\s)?(\w+)\s(?:([^\s\:]+)\s)?(?:\:?(.*))?$
|
|
221
|
+
|
|
222
|
+
IRC_RE = /
|
|
223
|
+
^ # beginning of string
|
|
224
|
+
(?: # non-capturing group
|
|
225
|
+
\: # if we have a ':' then we have an origin
|
|
226
|
+
([^\s]+) # get the origin without the ':'
|
|
227
|
+
\s # space after the origin
|
|
228
|
+
)? # close non-capturing group
|
|
229
|
+
(\w+) # must have a command
|
|
230
|
+
\s # and a space after it
|
|
231
|
+
(?: # non-capturing group
|
|
232
|
+
([^\s\:]+) # a target for the command
|
|
233
|
+
\s # and a space after it
|
|
234
|
+
)? # close non-capturing group
|
|
235
|
+
(?: # non-capturing group
|
|
236
|
+
\:? # if we have a ':' then we have freeform text
|
|
237
|
+
(.*) # get the rest as one string without the ':'
|
|
238
|
+
)? # close non-capturing group
|
|
239
|
+
$ # end of string
|
|
240
|
+
/x
|
|
241
|
+
|
|
242
|
+
#
|
|
243
|
+
# Parse any incoming data and generate IRC events.
|
|
244
|
+
# ---
|
|
245
|
+
# returns:: +self+
|
|
246
|
+
#
|
|
247
|
+
def parse
|
|
248
|
+
@recvq.each do |line|
|
|
249
|
+
line.chomp!
|
|
250
|
+
|
|
251
|
+
debug(line)
|
|
252
|
+
|
|
253
|
+
m = IRC_RE.match(line)
|
|
254
|
+
|
|
255
|
+
origin = m[1]
|
|
256
|
+
command = m[2]
|
|
257
|
+
target = m[3]
|
|
258
|
+
params = m[4]
|
|
259
|
+
|
|
260
|
+
if params and not target
|
|
261
|
+
target = params
|
|
262
|
+
params = nil
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
params = params.split if params
|
|
266
|
+
|
|
267
|
+
msg = Message.new(self, line, origin, target, params)
|
|
268
|
+
|
|
269
|
+
@eventq.post(command.upcase.to_sym, msg)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
@recvq.clear
|
|
273
|
+
|
|
274
|
+
self
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
######
|
|
278
|
+
public
|
|
279
|
+
######
|
|
280
|
+
|
|
281
|
+
#
|
|
282
|
+
# Registers Event handlers with our EventQueue.
|
|
283
|
+
# ---
|
|
284
|
+
# <tt>c.on(:PRIVMSG) do |m|
|
|
285
|
+
# if m.params =~ /\.die/ and m.origin == my_master
|
|
286
|
+
# c.quit(params)
|
|
287
|
+
# c.exit
|
|
288
|
+
# end</tt>
|
|
289
|
+
# ---
|
|
290
|
+
# event:: name of the event as a Symbol
|
|
291
|
+
# block:: block to call when Event is posted
|
|
292
|
+
# returns:: self
|
|
293
|
+
#
|
|
294
|
+
def on(event, &block)
|
|
295
|
+
@eventq.handle(event, &block)
|
|
296
|
+
|
|
297
|
+
self
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
#
|
|
301
|
+
# Schedules input/output and runs the EventQueue.
|
|
302
|
+
# ---
|
|
303
|
+
# returns:: never, thread dies on +:exit+
|
|
304
|
+
#
|
|
305
|
+
def io_loop
|
|
306
|
+
loop do
|
|
307
|
+
if dead?
|
|
308
|
+
sleep(30)
|
|
309
|
+
connect
|
|
310
|
+
next
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
connect unless connected?
|
|
314
|
+
|
|
315
|
+
# Run the event loop. These events will add IO, and possibly other
|
|
316
|
+
# events, so we keep running until it's empty.
|
|
317
|
+
@eventq.run while @eventq.needs_ran?
|
|
318
|
+
|
|
319
|
+
next if dead?
|
|
320
|
+
|
|
321
|
+
writefd = [@socket] unless @sendq.empty?
|
|
322
|
+
|
|
323
|
+
ret = IO.select([@socket], writefd, [], 10)
|
|
324
|
+
|
|
325
|
+
next unless ret
|
|
326
|
+
|
|
327
|
+
@eventq.post(:read_ready) unless ret[0].empty?
|
|
328
|
+
@eventq.post(:write_ready) unless ret[1].empty?
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
#
|
|
333
|
+
# Is the socket dead?
|
|
334
|
+
# ---
|
|
335
|
+
# returns:: +true+ or +false+
|
|
336
|
+
#
|
|
337
|
+
def dead?
|
|
338
|
+
@dead
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
#
|
|
342
|
+
# Are we connected?
|
|
343
|
+
# ---
|
|
344
|
+
# returns:: +true+ or +false+
|
|
345
|
+
#
|
|
346
|
+
def connected?
|
|
347
|
+
@connected
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
#
|
|
351
|
+
# Creates and connects our socket.
|
|
352
|
+
# ---
|
|
353
|
+
# returns:: +self+
|
|
354
|
+
#
|
|
355
|
+
def connect
|
|
356
|
+
verify_attributes
|
|
357
|
+
|
|
358
|
+
log("connecting to #@server:#@port")
|
|
359
|
+
|
|
360
|
+
begin
|
|
361
|
+
@socket = TCPSocket.new(@server, @port, @bind_to)
|
|
362
|
+
rescue Exception => err
|
|
363
|
+
@eventq.post(:dead)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
@dead = false
|
|
367
|
+
@connected = true
|
|
368
|
+
|
|
369
|
+
pass(@password) if @password
|
|
370
|
+
nick(@nickname)
|
|
371
|
+
user(@username, @server, @server, @realname)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
#
|
|
375
|
+
# Logs a regular message.
|
|
376
|
+
# ---
|
|
377
|
+
# message:: the string to log
|
|
378
|
+
# returns:: +self+
|
|
379
|
+
#
|
|
380
|
+
def log(message)
|
|
381
|
+
@logger.info(caller[0].split('/')[-1]) { message } if @logger
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
#
|
|
385
|
+
# Logs a debug message.
|
|
386
|
+
# ---
|
|
387
|
+
# message:: the string to log
|
|
388
|
+
# returns:: +self+
|
|
389
|
+
#
|
|
390
|
+
def debug(message)
|
|
391
|
+
return unless @logger
|
|
392
|
+
|
|
393
|
+
@logger.debug(caller[0].split('/')[-1]) { message } if @debug
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
#
|
|
397
|
+
# Sets the logging object to use.
|
|
398
|
+
# If it quacks like a Logger object, it should work.
|
|
399
|
+
# ---
|
|
400
|
+
# logger:: the Logger to use
|
|
401
|
+
# returns:: +self+
|
|
402
|
+
#
|
|
403
|
+
def logger=(logger)
|
|
404
|
+
@logger = logger
|
|
405
|
+
|
|
406
|
+
# Set to false/nil to disable logging...
|
|
407
|
+
return unless @logger
|
|
408
|
+
|
|
409
|
+
@logger.progname = 'irc'
|
|
410
|
+
@logger.datetime_format = '%b %d %H:%M:%S '
|
|
411
|
+
|
|
412
|
+
# We only have 'logging' and 'debugging', so just set the
|
|
413
|
+
# object to show all levels. I might change this someday.
|
|
414
|
+
@logger.level = Logger::DEBUG
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
#
|
|
418
|
+
# Forces the Client's Thread to die. If it's the main thread, the
|
|
419
|
+
# application goes with it.
|
|
420
|
+
# ---
|
|
421
|
+
# returns:: nope!
|
|
422
|
+
#
|
|
423
|
+
def exit(from = 'exit')
|
|
424
|
+
@eventq.post(:exit, from)
|
|
425
|
+
@eventq.run
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# A simple data-holding class.
|
|
430
|
+
class Message
|
|
431
|
+
##
|
|
432
|
+
# instance attributes
|
|
433
|
+
attr_reader :client, :raw, :origin, :target, :params
|
|
434
|
+
|
|
435
|
+
#
|
|
436
|
+
# Creates a new Message. We use these to represent the old
|
|
437
|
+
# style of (char *origin, char *target, char *parv[]) in C.
|
|
438
|
+
#
|
|
439
|
+
def initialize(client, raw, origin, target, params)
|
|
440
|
+
@client, @raw, @origin = client, raw, origin
|
|
441
|
+
@target, @params = target, params
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
end # module IRC
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small, lightweight IRC client library
|
|
3
|
+
# lib/rhuidean/event.rb: IRC events
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
module IRC
|
|
9
|
+
|
|
10
|
+
# Contains information about a posted event.
|
|
11
|
+
class Event
|
|
12
|
+
attr_reader :event, :args
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
# Creates a new Event.
|
|
16
|
+
# ---
|
|
17
|
+
# event:: event name as a Symbol
|
|
18
|
+
# args:: list of arguments to pass to handler
|
|
19
|
+
# returns:: +self+
|
|
20
|
+
#
|
|
21
|
+
def initialize(event, *args)
|
|
22
|
+
@event = event
|
|
23
|
+
@args = args
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# A queue of events, with handlers. One per object.
|
|
28
|
+
class EventQueue
|
|
29
|
+
attr_reader :queue, :handlers
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# Create a new EventQueue.
|
|
33
|
+
# ---
|
|
34
|
+
# returns:: +self+
|
|
35
|
+
#
|
|
36
|
+
def initialize
|
|
37
|
+
@queue = []
|
|
38
|
+
@handlers = {}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
######
|
|
42
|
+
public
|
|
43
|
+
######
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Post a new event to the queue to be handled.
|
|
47
|
+
# ---
|
|
48
|
+
# event:: event name as a Symbol
|
|
49
|
+
# args:: list of arguments to pass to handler
|
|
50
|
+
# returns:: +self+
|
|
51
|
+
#
|
|
52
|
+
def post(event, *args)
|
|
53
|
+
# Only one post per event per loop, otherwise we end up trying
|
|
54
|
+
# to read from a socket that has no data, or stuff like that.
|
|
55
|
+
return if m = @queue.find { |q| q.event == event }
|
|
56
|
+
|
|
57
|
+
@queue << Event.new(event, *args)
|
|
58
|
+
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#
|
|
63
|
+
# Register a handler for an event.
|
|
64
|
+
# ---
|
|
65
|
+
# event:: event name as a Symbol
|
|
66
|
+
# block:: block to call to handle event
|
|
67
|
+
# returns:: +self+
|
|
68
|
+
#
|
|
69
|
+
def handle(event, &block)
|
|
70
|
+
(@handlers[event] ||= []) << block
|
|
71
|
+
|
|
72
|
+
self
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
# Does the event queue have anything in it?
|
|
77
|
+
# ---
|
|
78
|
+
# returns:: +true+ or +false+
|
|
79
|
+
#
|
|
80
|
+
def needs_ran?
|
|
81
|
+
@queue.empty? ? false : true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
#
|
|
85
|
+
# Goes through the event queue and runs the handlers.
|
|
86
|
+
# ---
|
|
87
|
+
# returns:: +self+
|
|
88
|
+
#
|
|
89
|
+
def run
|
|
90
|
+
needs_exit = false
|
|
91
|
+
|
|
92
|
+
while e = @queue.shift
|
|
93
|
+
next unless @handlers[e.event]
|
|
94
|
+
|
|
95
|
+
# If there's an :exit event in the queue wait until we're
|
|
96
|
+
# all the way done before we handle it.
|
|
97
|
+
if e.event == :exit
|
|
98
|
+
needs_exit = e
|
|
99
|
+
next
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
@handlers[e.event].each { |block| block.call(*e.args) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Now we can exit... any events that got added by handling routines
|
|
106
|
+
# just don't happen. This is arguably a bug.
|
|
107
|
+
@handlers[:exit].each { |b| b.call(*needs_exit.args) } if needs_exit
|
|
108
|
+
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end # module IRC
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small, lightweight IRC client library
|
|
3
|
+
# lib/rhuidean/client.rb: IRC::Client class
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
module IRC
|
|
9
|
+
|
|
10
|
+
class Client
|
|
11
|
+
######
|
|
12
|
+
public
|
|
13
|
+
######
|
|
14
|
+
|
|
15
|
+
# Sends text directly to the server.
|
|
16
|
+
def raw(message)
|
|
17
|
+
@sendq << message
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Sends an IRC NICK command.
|
|
21
|
+
def nick(nick)
|
|
22
|
+
@sendq << "NICK #{nick}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Sends an IRC USER command.
|
|
26
|
+
def user(username, server, host, realname)
|
|
27
|
+
@sendq << "USER #{username} #{server} #{host} :#{realname}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Sends an IRC PASS command.
|
|
31
|
+
def pass(password)
|
|
32
|
+
@sendq << "PASS #{password}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Sends an IRC PRIVMSG command.
|
|
36
|
+
def privmsg(to, message)
|
|
37
|
+
@sendq << "PRIVMSG #{to} :#{message}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Sends an IRC NOTICE command.
|
|
41
|
+
def notice(to, message)
|
|
42
|
+
@sendq << "NOTICE #{to} :#{message}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Sends an IRC JOIN command.
|
|
46
|
+
def join(channel, key = '')
|
|
47
|
+
@sendq << "JOIN #{channel} #{key}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Sends an IRC PART command.
|
|
51
|
+
def part(channel, message = '')
|
|
52
|
+
@sendq << "PART #{channel} :#{message}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Sends an IRC MODE command.
|
|
56
|
+
def umode(mode)
|
|
57
|
+
@sendq << "MODE #@nickname #{mode}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Sends an IRC MODE command.
|
|
61
|
+
def mode(target, mode)
|
|
62
|
+
@sendq << "MODE #{target} #{mode}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Send an IRC QUIT command.
|
|
66
|
+
def quit(message = '')
|
|
67
|
+
@sendq << "QUIT :#{message}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end # module IRC
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small, lightweight IRC client library
|
|
3
|
+
# lib/rhuidean/numeric.rb: IRC numeric list
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
module IRC
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# A list of all IRC numerics, as per RFC 1459.
|
|
12
|
+
# Stolen from GStage by Stephen Belcher <sycobuny@malkier.net>.
|
|
13
|
+
#
|
|
14
|
+
module Numeric
|
|
15
|
+
|
|
16
|
+
# WELCOME
|
|
17
|
+
RPL_WELCOME = :'001'
|
|
18
|
+
RPL_YOURHOST = :'002'
|
|
19
|
+
RPL_CREATED = :'003'
|
|
20
|
+
RPL_MYINFO = :'004'
|
|
21
|
+
RPL_BOUNCE = :'005'
|
|
22
|
+
|
|
23
|
+
# ERRORS
|
|
24
|
+
ERR_NOSUCHNICK = :'401'
|
|
25
|
+
ERR_NOSUCHSERVER = :'402'
|
|
26
|
+
ERR_NOSUCHCHANNEL = :'403'
|
|
27
|
+
ERR_CANNOTSENDTOCHAN = :'404'
|
|
28
|
+
ERR_TOOMANYCHANNELS = :'405'
|
|
29
|
+
ERR_WASNOSUCHNICK = :'406'
|
|
30
|
+
ERR_TOOMANYTARGETS = :'407'
|
|
31
|
+
ERR_NOORIGIN = :'409'
|
|
32
|
+
ERR_NORECIPIENT = :'411'
|
|
33
|
+
ERR_NOTEXTTOSEND = :'412'
|
|
34
|
+
ERR_NOTOPLEVEL = :'413'
|
|
35
|
+
ERR_WILDTOPLEVEL = :'414'
|
|
36
|
+
ERR_UNKNOWNCOMMAND = :'421'
|
|
37
|
+
ERR_NOMOTD = :'422'
|
|
38
|
+
ERR_NOADMININFO = :'423'
|
|
39
|
+
ERR_FILEERROR = :'424'
|
|
40
|
+
ERR_NONICKNAMEGIVEN = :'431'
|
|
41
|
+
ERR_ERRONEOUSNICKNAME = :'432'
|
|
42
|
+
ERR_NICKNAMEINUSE = :'433'
|
|
43
|
+
ERR_NICKCOLLISION = :'436'
|
|
44
|
+
ERR_USERNOTINCHANNEL = :'441'
|
|
45
|
+
ERR_NOTONCHANNEL = :'442'
|
|
46
|
+
ERR_USERONCHANNEL = :'443'
|
|
47
|
+
ERR_NOLOGIN = :'444'
|
|
48
|
+
ERR_SUMMONDISABLED = :'445'
|
|
49
|
+
ERR_USERSDISABLED = :'446'
|
|
50
|
+
ERR_NOTREGISTERED = :'451'
|
|
51
|
+
ERR_NEEDMOREPARAMS = :'461'
|
|
52
|
+
ERR_ALREADYREGISTERED = :'462'
|
|
53
|
+
ERR_NOPERFORMHOST = :'463'
|
|
54
|
+
ERR_PASSWDMISTMATCH = :'464'
|
|
55
|
+
ERR_YOUREBANNEDCREEP = :'465'
|
|
56
|
+
ERR_KEYSET = :'467'
|
|
57
|
+
ERR_CHANNELISFULL = :'471'
|
|
58
|
+
ERR_UNKNOWNMODE = :'472'
|
|
59
|
+
ERR_INVITEONLYCHAN = :'473'
|
|
60
|
+
ERR_BANNEDFROMCHAN = :'474'
|
|
61
|
+
ERR_BADCHANNELKEY = :'475'
|
|
62
|
+
ERR_NOPRIVILEGES = :'481'
|
|
63
|
+
ERR_CHANOPRIVSNEEDED = :'482'
|
|
64
|
+
ERR_CANTKILLSERVER = :'483'
|
|
65
|
+
ERR_NOOPERHOST = :'491'
|
|
66
|
+
ERR_UMODEUNKNOWNFLAG = :'501'
|
|
67
|
+
ERR_USERSDONTMATCH = :'502'
|
|
68
|
+
|
|
69
|
+
# COMMAND REPLIES
|
|
70
|
+
RPL_NONE = :'300'
|
|
71
|
+
RPL_USERHOST = :'302'
|
|
72
|
+
RPL_ISON = :'303'
|
|
73
|
+
RPL_AWAY = :'301'
|
|
74
|
+
RPL_UNAWAY = :'305'
|
|
75
|
+
RPL_NOWAWAY = :'306'
|
|
76
|
+
RPL_WHOISUSER = :'311'
|
|
77
|
+
RPL_WHOISSERVER = :'312'
|
|
78
|
+
RPL_WHOISOPERATOR = :'313'
|
|
79
|
+
RPL_WHOISIDLE = :'317'
|
|
80
|
+
RPL_ENDOFWHOIS = :'318'
|
|
81
|
+
RPL_WHOISCHANNELS = :'319'
|
|
82
|
+
RPL_WHOWASUSER = :'314'
|
|
83
|
+
RPL_ENDOFWHOWAS = :'369'
|
|
84
|
+
RPL_LISTSTART = :'321'
|
|
85
|
+
RPL_LIST = :'322'
|
|
86
|
+
RPL_LISTEND = :'323'
|
|
87
|
+
RPL_CHANNELMODEIS = :'324'
|
|
88
|
+
RPL_NOTOPIC = :'331'
|
|
89
|
+
RPL_TOPIC = :'332'
|
|
90
|
+
RPL_INVITING = :'341'
|
|
91
|
+
RPL_SUMMONING = :'342'
|
|
92
|
+
RPL_VERSION = :'351'
|
|
93
|
+
RPL_WHOREPLY = :'352'
|
|
94
|
+
RPL_ENDOFWHO = :'315'
|
|
95
|
+
RPL_NAMEREPLY = :'353'
|
|
96
|
+
RPL_ENDOFNAMES = :'366'
|
|
97
|
+
RPL_LINKS = :'364'
|
|
98
|
+
RPL_ENDOFLINKS = :'365'
|
|
99
|
+
RPL_BANLIST = :'367'
|
|
100
|
+
RPL_ENDOFBANLIST = :'368'
|
|
101
|
+
RPL_INFO = :'371'
|
|
102
|
+
RPL_ENDOFINFO = :'374'
|
|
103
|
+
RPL_MOTDSTART = :'375'
|
|
104
|
+
RPL_MOTD = :'372'
|
|
105
|
+
RPL_ENDOFMOTD = :'376'
|
|
106
|
+
RPL_YOUREOPER = :'381'
|
|
107
|
+
RPL_REHASHING = :'382'
|
|
108
|
+
RPL_TIME = :'391'
|
|
109
|
+
RPL_USERSSTART = :'392'
|
|
110
|
+
RPL_USERS = :'393'
|
|
111
|
+
RPL_ENDOFUSERS = :'394'
|
|
112
|
+
RPL_NOUSERS = :'395'
|
|
113
|
+
RPL_TRACELINK = :'200'
|
|
114
|
+
RPL_TRACECONNECTING = :'201'
|
|
115
|
+
RPL_TRACEHANDSHAKE = :'202'
|
|
116
|
+
RPL_TRACEUNKNOWN = :'203'
|
|
117
|
+
RPL_TRACEOPERATOR = :'204'
|
|
118
|
+
RPL_TRACEUSER = :'205'
|
|
119
|
+
RPL_TRACESERVER = :'206'
|
|
120
|
+
RPL_TRACENEWTYPE = :'208'
|
|
121
|
+
RPL_TRACELOG = :'261'
|
|
122
|
+
RPL_STATSLINKINFO = :'211'
|
|
123
|
+
RPL_STATSCOMMANDS = :'212'
|
|
124
|
+
RPL_STATSCLINE = :'213'
|
|
125
|
+
RPL_STATSNLINE = :'214'
|
|
126
|
+
RPL_STATSILINE = :'215'
|
|
127
|
+
RPL_STATSKLINE = :'216'
|
|
128
|
+
RPL_STATSYLINE = :'218'
|
|
129
|
+
RPL_ENDOFSTATS = :'219'
|
|
130
|
+
RPL_STATSLLINE = :'241'
|
|
131
|
+
RPL_STATSUPTIME = :'242'
|
|
132
|
+
RPL_STATSOLINE = :'243'
|
|
133
|
+
RPL_STATSHLINE = :'244'
|
|
134
|
+
RPL_UMODEIS = :'221'
|
|
135
|
+
RPL_LUSERCLIENT = :'251'
|
|
136
|
+
RPL_LUSEROP = :'252'
|
|
137
|
+
RPL_LUSERUNKNOWN = :'253'
|
|
138
|
+
RPL_LUSERCHANNELS = :'254'
|
|
139
|
+
RPL_LUSERME = :'255'
|
|
140
|
+
RPL_ADMINME = :'256'
|
|
141
|
+
RPL_ADMINLOC1 = :'257'
|
|
142
|
+
RPL_ADMINLOC2 = :'258'
|
|
143
|
+
RPL_ADMINEMAIL = :'259'
|
|
144
|
+
|
|
145
|
+
# RESERVED
|
|
146
|
+
RPL_TRACECLASS = :'209'
|
|
147
|
+
RPL_STATSQLINE = :'217'
|
|
148
|
+
RPL_SERVICEINFO = :'231'
|
|
149
|
+
RPL_ENDOFSERVICES = :'232'
|
|
150
|
+
RPL_SERVICE = :'233'
|
|
151
|
+
RPL_SERVLIST = :'234'
|
|
152
|
+
RPL_SERVICELISTEND = :'235'
|
|
153
|
+
RPL_WHOISCHANOP = :'316'
|
|
154
|
+
RPL_KILLDONE = :'361'
|
|
155
|
+
RPL_CLOSING = :'362'
|
|
156
|
+
RPL_CLOSEEND = :'363'
|
|
157
|
+
RPL_INFOSTART = :'373'
|
|
158
|
+
RPL_MYPORTIS = :'384'
|
|
159
|
+
ERR_YOUWILLBEBANNED = :'466'
|
|
160
|
+
ERR_BADCHANMASK = :'476'
|
|
161
|
+
ERR_NOSERVICEHOST = :'492'
|
|
162
|
+
|
|
163
|
+
@@table = {}
|
|
164
|
+
constants.each { |c| @@table[const_get(c)] = c }
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
# Given a symbol, returns the constant name.
|
|
168
|
+
# ---
|
|
169
|
+
# num:: Symbol
|
|
170
|
+
# returns:: constant
|
|
171
|
+
#
|
|
172
|
+
def Numeric.num2const(num)
|
|
173
|
+
@@table[num]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
end # module Numeric
|
|
177
|
+
|
|
178
|
+
end # module IRC
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small, lightweight IRC client library
|
|
3
|
+
# lib/rhuidean/timer.rb: timed code execution
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
module IRC
|
|
9
|
+
|
|
10
|
+
# Allows for code to be executed on a timed basis.
|
|
11
|
+
class Timer
|
|
12
|
+
##
|
|
13
|
+
# class attributes
|
|
14
|
+
@@timers = []
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# instance attributes
|
|
18
|
+
attr_reader :time, :repeat
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Creates a new timer to be executed within +10 seconds of +time+.
|
|
22
|
+
# ---
|
|
23
|
+
# time:: time in seconds
|
|
24
|
+
# repeat:: +true+ or +false+, keep executing +block+ every +time+?
|
|
25
|
+
# block:: execute the given block
|
|
26
|
+
# returns:: +self+
|
|
27
|
+
#
|
|
28
|
+
def initialize(time, repeat = false, &block)
|
|
29
|
+
@time = time.to_i
|
|
30
|
+
@repeat = repeat
|
|
31
|
+
@block = block
|
|
32
|
+
|
|
33
|
+
@@timers << self
|
|
34
|
+
|
|
35
|
+
@thread = Thread.new { start }
|
|
36
|
+
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
######
|
|
41
|
+
public
|
|
42
|
+
######
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# A wrapper for initialize. Sets up the block to repeat.
|
|
46
|
+
# --
|
|
47
|
+
# time:: repeat how often, in seconds?
|
|
48
|
+
# returns:: +self+
|
|
49
|
+
#
|
|
50
|
+
def Timer.every(time, &block)
|
|
51
|
+
new(time, true, &block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# A wrapper for initialize. Sets up so the block doesn't repeat.
|
|
56
|
+
# ---
|
|
57
|
+
# time:: execute block after how long, in seconds?
|
|
58
|
+
# returns:: self
|
|
59
|
+
#
|
|
60
|
+
def Timer.after(time, &block)
|
|
61
|
+
new(time, false, &block)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Stops all timers.
|
|
66
|
+
# ---
|
|
67
|
+
# returns:: nothing
|
|
68
|
+
#
|
|
69
|
+
def Timer.stop
|
|
70
|
+
@@timers.each { |t| t.stop }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#
|
|
74
|
+
# Kills the thread we're in.
|
|
75
|
+
# ---
|
|
76
|
+
# returns:: nothing
|
|
77
|
+
#
|
|
78
|
+
def stop
|
|
79
|
+
@@timers.delete(self)
|
|
80
|
+
@thread.exit
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#######
|
|
84
|
+
private
|
|
85
|
+
#######
|
|
86
|
+
|
|
87
|
+
#
|
|
88
|
+
# Starts the loop, always in a thread.
|
|
89
|
+
# ---
|
|
90
|
+
# returns:: nothing
|
|
91
|
+
#
|
|
92
|
+
def start
|
|
93
|
+
loop do
|
|
94
|
+
sleep(@time)
|
|
95
|
+
@block.call
|
|
96
|
+
break unless @repeat
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
@@timers.delete(self)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end # module IRC
|
data/rakefile
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small, lightweight IRC client library
|
|
3
|
+
# rakefile: ruby makefile
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# Import required Ruby modules.
|
|
9
|
+
%w(rdoctask testtask gempackagetask packagetask).each do |m|
|
|
10
|
+
require 'rake/' + m
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Default task - unit tests.
|
|
15
|
+
#
|
|
16
|
+
# $ rake
|
|
17
|
+
#
|
|
18
|
+
task :default => [:test]
|
|
19
|
+
|
|
20
|
+
Rake::TestTask.new do |t|
|
|
21
|
+
t.libs << 'test'
|
|
22
|
+
t.test_files = %w(test/ts_rhuidean.rb)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
# Documentation generation.
|
|
27
|
+
#
|
|
28
|
+
# $ rake rdoc
|
|
29
|
+
#
|
|
30
|
+
Rake::RDocTask.new do |r|
|
|
31
|
+
r.rdoc_dir = 'doc/rdoc'
|
|
32
|
+
r.options << '--line-numbers' << '--inline-source'
|
|
33
|
+
r.rdoc_files.include('lib/rhuidean.rb lib/rhuidean/**/*')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Package generation.
|
|
38
|
+
#
|
|
39
|
+
# $ rake package
|
|
40
|
+
#
|
|
41
|
+
PKG_FILES = FileList['README', 'rakefile',
|
|
42
|
+
'lib/rhuidean.rb', 'lib/rhuidean/**/*.rb',
|
|
43
|
+
'test/*.rb']
|
|
44
|
+
|
|
45
|
+
Rake::PackageTask.new('package') do |p|
|
|
46
|
+
p.name = 'rhuidean'
|
|
47
|
+
p.version = '0.2.1'
|
|
48
|
+
p.need_tar = false
|
|
49
|
+
p.need_zip = false
|
|
50
|
+
p.package_files = PKG_FILES
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
spec = Gem::Specification.new do |s|
|
|
54
|
+
s.name = 'rhuidean'
|
|
55
|
+
s.version = '0.2.1'
|
|
56
|
+
s.author = 'Eric Will'
|
|
57
|
+
s.email = 'rakaur@malkier.net'
|
|
58
|
+
s.homepage = 'http://github.com/rakaur/rhuidean/'
|
|
59
|
+
s.platform = Gem::Platform::RUBY
|
|
60
|
+
s.summary = 'a small, lightweight IRC client library'
|
|
61
|
+
s.files = PKG_FILES.to_a
|
|
62
|
+
|
|
63
|
+
s.require_paths = %w(lib)
|
|
64
|
+
s.test_file = 'test/ts_rhuidean.rb'
|
|
65
|
+
s.has_rdoc = true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
Rake::GemPackageTask.new(spec) do
|
|
69
|
+
end
|
|
70
|
+
|
data/test/tc_client.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small, lightweight IRC client library
|
|
3
|
+
# test/tc_client.rb: unit testing
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
warn <<-end
|
|
9
|
+
WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
|
|
10
|
+
|
|
11
|
+
You have 15 seconds to cancel (CTRL+C).
|
|
12
|
+
|
|
13
|
+
These tests connect to irc.malkier.net:6667 and join #malkier for a brief
|
|
14
|
+
period of time (a few seconds), and posts the client version and platform.
|
|
15
|
+
If you do not want your machine to do this, do not run these tests!
|
|
16
|
+
|
|
17
|
+
You have 15 seconds to cancel (CTRL+C).
|
|
18
|
+
|
|
19
|
+
WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
$stdout.sync = true
|
|
23
|
+
print "."*16
|
|
24
|
+
15.downto(0) do
|
|
25
|
+
print "\b \b"
|
|
26
|
+
sleep(1)
|
|
27
|
+
end
|
|
28
|
+
puts
|
|
29
|
+
$stdout.sync = false
|
|
30
|
+
|
|
31
|
+
class TestClient < Test::Unit::TestCase
|
|
32
|
+
def test_001_connect
|
|
33
|
+
c = nil
|
|
34
|
+
|
|
35
|
+
assert_nothing_raised do
|
|
36
|
+
c = IRC::Client.new do |c|
|
|
37
|
+
c.server = 'irc.malkier.net'
|
|
38
|
+
c.port = 6667
|
|
39
|
+
|
|
40
|
+
c.nickname = "rhuidean#{rand(9999)}"
|
|
41
|
+
c.username = 'rhuidean'
|
|
42
|
+
c.realname = 'rhuidean unit tester'
|
|
43
|
+
|
|
44
|
+
c.logger = nil
|
|
45
|
+
c.debug = false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
assert_equal(6667, c.port)
|
|
50
|
+
assert_equal('irc.malkier.net', c.server)
|
|
51
|
+
assert_match(/^rhuidean\d+$/, c.nickname)
|
|
52
|
+
assert_equal('rhuidean', c.username)
|
|
53
|
+
|
|
54
|
+
welcome = false
|
|
55
|
+
str = "rhuidean-#{IRC::Client::VERSION} [#{RUBY_PLATFORM}]"
|
|
56
|
+
|
|
57
|
+
assert_nothing_raised do
|
|
58
|
+
c.on(IRC::Numeric::RPL_WELCOME) { welcome = true }
|
|
59
|
+
|
|
60
|
+
c.on(IRC::Numeric::RPL_ENDOFMOTD) do
|
|
61
|
+
c.join('#malkier')
|
|
62
|
+
c.privmsg('#malkier', str)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
assert_nothing_raised { c.connect }
|
|
67
|
+
|
|
68
|
+
t = Thread.new { c.io_loop }
|
|
69
|
+
|
|
70
|
+
sleep(1) until welcome
|
|
71
|
+
assert(true, welcome)
|
|
72
|
+
end
|
|
73
|
+
end
|
data/test/ts_rhuidean.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#
|
|
2
|
+
# rhuidean: a small, lightweight IRC client library
|
|
3
|
+
# test/ts_rhuidean.rb: unit testing
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
$: << 'lib'
|
|
9
|
+
|
|
10
|
+
require 'test/unit'
|
|
11
|
+
|
|
12
|
+
require 'rhuidean'
|
|
13
|
+
|
|
14
|
+
require 'test/tc_client.rb'
|
metadata
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rhuidean
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 21
|
|
5
|
+
prerelease: false
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 2
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.2.1
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Eric Will
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2010-09-08 00:00:00 -04:00
|
|
19
|
+
default_executable:
|
|
20
|
+
dependencies: []
|
|
21
|
+
|
|
22
|
+
description:
|
|
23
|
+
email: rakaur@malkier.net
|
|
24
|
+
executables: []
|
|
25
|
+
|
|
26
|
+
extensions: []
|
|
27
|
+
|
|
28
|
+
extra_rdoc_files: []
|
|
29
|
+
|
|
30
|
+
files:
|
|
31
|
+
- README
|
|
32
|
+
- rakefile
|
|
33
|
+
- lib/rhuidean.rb
|
|
34
|
+
- lib/rhuidean/methods.rb
|
|
35
|
+
- lib/rhuidean/numeric.rb
|
|
36
|
+
- lib/rhuidean/client.rb
|
|
37
|
+
- lib/rhuidean/timer.rb
|
|
38
|
+
- lib/rhuidean/event.rb
|
|
39
|
+
- test/tc_client.rb
|
|
40
|
+
- test/ts_rhuidean.rb
|
|
41
|
+
has_rdoc: true
|
|
42
|
+
homepage: http://github.com/rakaur/rhuidean/
|
|
43
|
+
licenses: []
|
|
44
|
+
|
|
45
|
+
post_install_message:
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
none: false
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
hash: 3
|
|
56
|
+
segments:
|
|
57
|
+
- 0
|
|
58
|
+
version: "0"
|
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
|
+
none: false
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
hash: 3
|
|
65
|
+
segments:
|
|
66
|
+
- 0
|
|
67
|
+
version: "0"
|
|
68
|
+
requirements: []
|
|
69
|
+
|
|
70
|
+
rubyforge_project:
|
|
71
|
+
rubygems_version: 1.3.7
|
|
72
|
+
signing_key:
|
|
73
|
+
specification_version: 3
|
|
74
|
+
summary: a small, lightweight IRC client library
|
|
75
|
+
test_files:
|
|
76
|
+
- test/ts_rhuidean.rb
|