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 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
@@ -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