sbfaulkner-net-irc 0.9.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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 unwwwired.net
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,22 @@
1
+ # Net::IRC
2
+
3
+ A ruby implementation of the IRC client protocol.
4
+
5
+ ## Installation
6
+
7
+ Run the following if you haven't already:
8
+
9
+ $ gem sources -a http://gems.github.com
10
+
11
+ Install the gem(s):
12
+
13
+ $ sudo gem install -r sbfaulkner-net-irc
14
+
15
+ ## Example
16
+
17
+ See the examples folder for how to use.
18
+
19
+ ## Legal
20
+
21
+ **Author:** S. Brent Faulkner <brentf@unwwwired.net>
22
+ **License:** Copyright &copy; 2008 unwwwired.net, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ Gem::manage_gems
3
+ require 'rake/gempackagetask'
4
+
5
+ load File.join(File.dirname(__FILE__),'net-irc.gemspec')
6
+
7
+ Rake::GemPackageTask.new(SPEC) do |pkg|
8
+ end
9
+
10
+ task :default => :package
data/lib/net/irc.rb ADDED
@@ -0,0 +1,793 @@
1
+ require 'net/protocol'
2
+ require 'strscan'
3
+ require 'yaml'
4
+ require 'logger'
5
+
6
+ module Net
7
+ class IRC < Protocol
8
+ include Enumerable
9
+
10
+ class << self
11
+ def logger
12
+ @logger ||= Logger.new('net-irc.log')
13
+ end
14
+
15
+ def logger=(logger)
16
+ @logger = logger
17
+ end
18
+ end
19
+
20
+ USER_MODE_DEFAULT = 0
21
+ USER_MODE_RECEIVE_WALLOPS = 4
22
+ USER_MODE_INVISIBLE = 8
23
+
24
+ PORT_DEFAULT = 6667
25
+
26
+ VERSION = "0.9.1"
27
+
28
+ class CTCP
29
+ attr_accessor :source, :target, :keyword, :parameters
30
+
31
+ CTCP_REGEX = /\001(.*?)\001/
32
+
33
+ def initialize(keyword, *parameters)
34
+ @source = nil
35
+ @keyword = keyword
36
+ @parameters = parameters
37
+ end
38
+
39
+ def to_s
40
+ str = "\001#{keyword}"
41
+ str << parameters.collect { |p| " #{p}"}.join
42
+ str << "\001"
43
+ end
44
+
45
+ class << self
46
+ def parse(text)
47
+ [
48
+ text.gsub(CTCP_REGEX, ''),
49
+ text.scan(CTCP_REGEX).flatten.collect do |message|
50
+ parameters = message.split(' ')
51
+ case keyword = parameters.shift
52
+ when 'VERSION'
53
+ CTCPVersion.new(*parameters)
54
+ when 'PING'
55
+ CTCPPing.new(*parameters)
56
+ when 'CLIENTINFO'
57
+ CTCPClientinfo.new(*parameters)
58
+ when 'ACTION'
59
+ CTCPAction.new(*parameters)
60
+ when 'FINGER'
61
+ CTCPFinger.new(*parameters)
62
+ when 'TIME'
63
+ CTCPTime.new(*parameters)
64
+ when 'DCC'
65
+ CTCPDcc.new(*parameters)
66
+ when 'ERRMSG'
67
+ CTCPErrmsg.new(*parameters)
68
+ when 'PLAY'
69
+ CTCPPlay.new(*parameters)
70
+ else
71
+ CTCP.new(keyword, *parameters)
72
+ end
73
+ end
74
+ ]
75
+ end
76
+ end
77
+ end
78
+
79
+ class CTCPVersion < CTCP
80
+ def initialize(*parameters)
81
+ super('VERSION', parameters)
82
+ end
83
+ end
84
+
85
+ class CTCPPing < CTCP
86
+ attr_accessor :arg
87
+
88
+ def initialize(arg = nil)
89
+ if @arg = arg
90
+ super('PING', arg)
91
+ else
92
+ super('PING')
93
+ end
94
+ end
95
+ end
96
+
97
+ class CTCPClientinfo < CTCP
98
+ def initialize(*keywords)
99
+ super('CLIENTINFO', *keywords)
100
+ end
101
+ end
102
+
103
+ class CTCPAction < CTCP
104
+ attr_accessor :text
105
+
106
+ def initialize(*parameters)
107
+ @text = parameters.join(' ')
108
+
109
+ super('ACTION', *parameters)
110
+ end
111
+ end
112
+
113
+ class CTCPFinger < CTCP
114
+ def initialize(text = nil)
115
+ super('FINGER', text)
116
+ end
117
+ end
118
+
119
+ class CTCPTime < CTCP
120
+ attr_accessor :time
121
+ def initialize(*parameters)
122
+ @time = parameters.join(' ')
123
+
124
+ super('TIME', *parameters)
125
+ end
126
+ end
127
+
128
+ class CTCPDcc < CTCP
129
+ def initialize(type, protocol, ip, port, *args)
130
+ super('DCC', type, protocol, ip, port, *args)
131
+ end
132
+ end
133
+
134
+ class CTCPErrmsg < CTCP
135
+ def initialize(keyword, text = nil)
136
+ super('ERRMSG', keyword, text)
137
+ end
138
+ end
139
+
140
+ class CTCPPlay < CTCP
141
+ def initialize(filename, mime_type)
142
+ super('PLAY', filename, mime_type)
143
+ end
144
+ end
145
+
146
+ class Message
147
+ attr_reader :prefix
148
+ attr_accessor :command, :parameters
149
+
150
+ COMMAND_MAPS = %w(rfc1459 rfc2812 isupport hybrid ircu)
151
+
152
+ def initialize(*args)
153
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 2)" if args.size < 2
154
+
155
+ # puts ">>>>> args=#{args.inspect}"
156
+
157
+ @prefix, @command, *parameters = args
158
+ # puts ">>>>> @prefix=#{@prefix.inspect}, command=#{@command.inspect}, parameters=#{parameters.inspect}"
159
+ @parameters = Array(parameters)
160
+ end
161
+
162
+ class Prefix
163
+ attr_accessor :prefix
164
+
165
+ PREFIX_REGEX = /^([^!@]+)(?:(?:!([^@]+))?@(.+))?/
166
+
167
+ def initialize(prefix)
168
+ @prefix = prefix
169
+ @matches = prefix.match(PREFIX_REGEX)
170
+ end
171
+
172
+ def server
173
+ @prefix
174
+ end
175
+
176
+ def nickname
177
+ @matches[1]
178
+ end
179
+
180
+ def user
181
+ @matches[2]
182
+ end
183
+
184
+ def host
185
+ @matches[3]
186
+ end
187
+
188
+ def to_s
189
+ @prefix
190
+ end
191
+ end
192
+
193
+ def prefix=(value)
194
+ @prefix = value && Prefix.new(value)
195
+ end
196
+
197
+ def prefix?
198
+ @prefix
199
+ end
200
+
201
+ def to_s
202
+ # puts ">>>>> prefix=#{prefix.inspect}, command=#{command.inspect}, parameters=#{parameters.inspect}"
203
+ str = prefix ? ":#{prefix} " : ""
204
+ str << command
205
+ if ! parameters.empty?
206
+ parameters[0..-2].each do |param|
207
+ str << " #{param}"
208
+ end
209
+ if parameters.last =~ /^:| /
210
+ str << " :#{parameters.last}"
211
+ else
212
+ str << " #{parameters.last}"
213
+ end
214
+ end
215
+ str
216
+ end
217
+
218
+ def write(socket)
219
+ line = to_s
220
+ IRC.logger.debug ">>>>> #{line.inspect}"
221
+ socket.writeline(line)
222
+ end
223
+
224
+ class << self
225
+ def parse(line)
226
+ scanner = StringScanner.new(line)
227
+
228
+ prefix = scanner.scan(/:([^ ]+) /) && scanner[1]
229
+ command = scanner.scan(/[[:alpha:]]+|\d{3}/)
230
+ params = []
231
+ 14.times do
232
+ break if ! scanner.scan(/ ([^ :][^ ]*)/)
233
+ params << scanner[1]
234
+ end
235
+ params << scanner[1] if scanner.scan(/ :(.+)/)
236
+
237
+ message = nil
238
+ command_name = command.to_i > 0 ? command_for_number(command.to_i) : command
239
+
240
+ if command_name
241
+ message_type = "#{command_name.downcase.split('_').collect { |w| w.capitalize }.join}"
242
+ if Net::IRC.const_defined?(message_type)
243
+ message_type = Net::IRC.const_get(message_type)
244
+ message = message_type.new(*params)
245
+ message.prefix = prefix
246
+ end
247
+ end
248
+
249
+ message ||= Message.new(prefix, command_name || command, *params)
250
+ end
251
+
252
+ def command_for_number(number)
253
+ @command_map ||= COMMAND_MAPS.inject({}) { |merged,map| merged.merge!(YAML.load_file("#{File.dirname(__FILE__)}/#{map}.yml")) }
254
+ @command_map[number]
255
+ end
256
+ end
257
+ end
258
+
259
+ class Reply < Message
260
+ attr_accessor :text
261
+
262
+ def initialize(prefix, command, *args)
263
+ args.pop unless @text = args.last
264
+ super(nil, command, *args)
265
+ end
266
+ end
267
+
268
+ class ReplyWithTarget < Reply
269
+ attr_accessor :target
270
+
271
+ def initialize(prefix, command, target, *args)
272
+ @target = target
273
+ super(prefix, command, @target, *args)
274
+ end
275
+ end
276
+
277
+ # 001 <target> :Welcome to the Internet Relay Network <nick>!<user>@<host>
278
+ class RplWelcome < ReplyWithTarget
279
+ def initialize(target, text)
280
+ super(nil, 'RPL_WELCOME', target, text)
281
+ end
282
+ end
283
+
284
+ # 002 <target> :Your host is <servername>, running version <ver>
285
+ class RplYourhost < Reply
286
+ def initialize(target, text)
287
+ super(nil, 'RPL_YOURHOST', target, text)
288
+ end
289
+ end
290
+
291
+ # 003 <target> :This server was created <date>
292
+ class RplCreated < ReplyWithTarget
293
+ def initialize(target, text)
294
+ super(nil, 'RPL_CREATED', target, text)
295
+ end
296
+ end
297
+
298
+ # 004 <target> <servername> <version> <available user modes> <available channel modes>
299
+ class RplMyinfo < ReplyWithTarget
300
+ attr_accessor :servername, :version, :available_user_modes, :available_channel_modes
301
+
302
+ def initialize(target, servername, version, available_user_modes, available_channel_modes)
303
+ @servername = servername
304
+ @version = version
305
+ @available_user_modes = available_user_modes
306
+ @available_channel_modes = available_channel_modes
307
+
308
+ super(nil, 'RPL_MYINFO', target, servername, version, available_user_modes, available_channel_modes, nil)
309
+ end
310
+ end
311
+
312
+ # 005 <target> ( [ "-" ] <parameter> ) | ( <parameter> "=" [ <value> ] ) *( ( [ "-" ] <parameter> ) | ( <parameter> "=" [ <value> ] ) ) :are supported by this server
313
+ class RplIsupport < ReplyWithTarget
314
+ class Parameter
315
+ PARAMETER_REGEX = /^(-)?([[:alnum:]]{1,20})(?:=(.*))?/
316
+
317
+ def initialize(param)
318
+ @param = param
319
+ @matches = param.match(PARAMETER_REGEX)
320
+ end
321
+
322
+ def name
323
+ @matches[2]
324
+ end
325
+
326
+ def value
327
+ @matches[3] || @matches[1].nil?
328
+ end
329
+ end
330
+
331
+ def initialize(target, *args)
332
+ raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 3)" if args.size < 2
333
+
334
+ @parameters = args[0..-2].collect { |p| Parameter.new(p) }
335
+
336
+ super(nil, 'RPL_ISUPPORT', target, *args)
337
+ end
338
+ end
339
+
340
+ # 250 <target> :<text>
341
+ class RplStatsconn < ReplyWithTarget
342
+ def initialize(target, text)
343
+ super(nil, 'RPL_LSTATSCONN', target, text)
344
+ end
345
+ end
346
+
347
+ # 251 <target> :<text>
348
+ class RplLuserclient < ReplyWithTarget
349
+ def initialize(target, text)
350
+ super(nil, 'RPL_LUSERCLIENT', target, text)
351
+ end
352
+ end
353
+
354
+ class ReplyWithCount < ReplyWithTarget
355
+ attr_accessor :count
356
+
357
+ def initialize(prefix, command, target, count, text)
358
+ @count = count
359
+ super(prefix, command, target, count, text)
360
+ end
361
+ end
362
+
363
+ # 252 <target> <count> :<text>
364
+ class RplLuserop < ReplyWithCount
365
+ def initialize(target, count, text)
366
+ super(nil, 'RPL_LUSEROP', target, count, text)
367
+ end
368
+ end
369
+
370
+ # 254 <target> <count> :<text>
371
+ class RplLuserchannels < ReplyWithCount
372
+ def initialize(target, count, text)
373
+ super(nil, 'RPL_LUSERCHANNELS', target, count, text)
374
+ end
375
+ end
376
+
377
+ # 255 <target> :<text>
378
+ class RplLuserme < ReplyWithTarget
379
+ def initialize(target, text)
380
+ super(nil, 'RPL_LUSERME', target, text)
381
+ end
382
+ end
383
+
384
+ # 265 <target> :<text>
385
+ class RplLocalusers < ReplyWithTarget
386
+ def initialize(target, text)
387
+ super(nil, 'RPL_LOCALUSERS', target, text)
388
+ end
389
+ end
390
+
391
+ # 266 <target> :<text>
392
+ class RplGlobalusers < ReplyWithTarget
393
+ def initialize(target, text)
394
+ super(nil, 'RPL_GLOBALUSERS', target, text)
395
+ end
396
+ end
397
+
398
+ class ReplyWithChannel < ReplyWithTarget
399
+ attr_accessor :channel
400
+
401
+ def initialize(prefix, command, target, channel, *args)
402
+ @channel = channel
403
+ super(prefix, command, target, @channel, *args)
404
+ end
405
+ end
406
+
407
+ # 332 <target> <target> <channel> :<text>
408
+ class RplTopic < ReplyWithChannel
409
+ def initialize(target, channel, text)
410
+ super(nil, 'RPL_TOPIC', target, channel, text)
411
+ end
412
+ end
413
+
414
+ # 333 <target> <channel> <nickname> <time>
415
+ class RplTopicwhotime < ReplyWithChannel
416
+ attr_accessor :nickname, :time
417
+
418
+ def initialize(target, channel, nickname, time)
419
+ @nickname = nickname
420
+ @time = Time.at(time.to_i)
421
+ super(nil, 'RPL_TOPICWHOTIME', target, channel, nickname, time, nil)
422
+ end
423
+ end
424
+
425
+ # 353 <target> ( "=" | "*" | "@" ) <channel> :[ "@" | "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
426
+ class RplNamreply < Reply
427
+ attr_accessor :channel_type, :channel, :names
428
+
429
+ def initialize(target, channel_type, channel, names)
430
+ @channel_type = channel_type
431
+ @channel = channel
432
+ @names = names.split(' ')
433
+ super(nil, 'RPL_NAMREPLY', target, @channel_type, @channel, names, nil)
434
+ end
435
+ end
436
+
437
+ # 366 <target> <channel> :End of /NAMES list
438
+ class RplEndofnames < ReplyWithChannel
439
+ def initialize(target, channel, text)
440
+ super(nil, 'RPL_ENDOFNAMES', target, channel, text)
441
+ end
442
+ end
443
+
444
+ # 372 <target> :- <text>
445
+ class RplMotd < Reply
446
+ def initialize(target, text)
447
+ super(nil, 'RPL_MOTD', target, text)
448
+ end
449
+ end
450
+
451
+ # 375 <target> :- <server> Message of the day -
452
+ class RplMotdstart < Reply
453
+ def initialize(target, text)
454
+ super(nil, 'RPL_MOTDSTART', target, text)
455
+ end
456
+ end
457
+
458
+ # 376 <target> :End of MOTD command.
459
+ class RplEndofmotd < Reply
460
+ def initialize(target, text)
461
+ super(nil, 'RPL_ENDOFMOTD', target, text)
462
+ end
463
+ end
464
+
465
+ class Error < ReplyWithTarget
466
+ end
467
+
468
+ # 422 <target> <nickname> :Nickname is already in use.
469
+ class ErrNicknameinuse < Error
470
+ attr_accessor :nickname
471
+
472
+ def initialize(target, nickname, text)
473
+ @nickname = nickname
474
+
475
+ super(nil, 'ERR_NICKNAMEINUSE', target, @nickname, text)
476
+ end
477
+ end
478
+
479
+ # 477 <target> <channel> :<text>
480
+ class ErrNeedreggednick < Error
481
+ attr_accessor :channel
482
+
483
+ def initialize(target, channel, text)
484
+ @channel = channel
485
+
486
+ super(nil, 'ERR_NEEDREGGEDNICK', target, channel, text)
487
+ end
488
+ end
489
+
490
+ # JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] )
491
+ # / "0"
492
+ class Join < Message
493
+ attr_accessor :channels, :keys
494
+
495
+ def initialize(channels, keys = nil)
496
+ @channels = channels.split(',')
497
+ @keys = keys && keys.split(',')
498
+
499
+ if keys
500
+ super(nil, 'JOIN', channels, keys)
501
+ else
502
+ super(nil, 'JOIN', channels)
503
+ end
504
+ end
505
+ end
506
+
507
+ # MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
508
+ class Mode < Message
509
+ attr_accessor :channel, :modes
510
+
511
+ def initialize(channel, *parameters)
512
+ @channel = channel
513
+ @modes = parameters.join(' ')
514
+
515
+ super(nil, 'MODE', channel, *parameters)
516
+ end
517
+ end
518
+
519
+ # NICK <nickname>
520
+ class Nick < Message
521
+ attr_accessor :nickname
522
+
523
+ def initialize(nickname)
524
+ @nickname = nickname
525
+
526
+ super(nil, 'NICK', @nickname)
527
+ end
528
+ end
529
+
530
+ # NOTICE <target> <text>
531
+ class Notice < Message
532
+ attr_accessor :target, :text, :ctcp
533
+
534
+ def initialize(target, text)
535
+ @target = target
536
+ @text, @ctcp = CTCP.parse(text)
537
+
538
+ super(nil, 'NOTICE', @target, text)
539
+ end
540
+ end
541
+
542
+ # PART <channel> *( "," <channel> ) [ <text> ]
543
+ class Part < Message
544
+ attr_accessor :channels, :text
545
+
546
+ def initialize(channels, message = nil)
547
+ @channels = channels.split(',')
548
+
549
+ if message
550
+ super(nil, 'PART', channels, message)
551
+ else
552
+ super(nil, 'PART', channels)
553
+ end
554
+ end
555
+ end
556
+
557
+ # PASS <password>
558
+ class Pass < Message
559
+ attr_accessor :password
560
+
561
+ def initialize(password)
562
+ @password = password
563
+
564
+ super(nil, 'PASS', @password)
565
+ end
566
+ end
567
+
568
+ # PING <server> [ <target> ]
569
+ class Ping < Message
570
+ attr_accessor :server, :target
571
+
572
+ def initialize(server, target = nil)
573
+ @server = server
574
+
575
+ if @target = target
576
+ super(nil, 'PING', @server, @target)
577
+ else
578
+ super(nil, 'PING', @server)
579
+ end
580
+ end
581
+ end
582
+
583
+ # PONG <server> [ <target> ]
584
+ class Pong < Message
585
+ attr_accessor :server, :target
586
+
587
+ def initialize(server, target = nil)
588
+ @server = server
589
+
590
+ if @target = target
591
+ super(nil, 'PONG', @server, @target)
592
+ else
593
+ super(nil, 'PONG', @server)
594
+ end
595
+ end
596
+ end
597
+
598
+ # PRIVMSG <target> <text>
599
+ class Privmsg < Message
600
+ attr_accessor :target, :text, :ctcp
601
+
602
+ def initialize(target, text)
603
+ @target = target
604
+ @text, @ctcp = CTCP.parse(text)
605
+
606
+ super(nil, 'PRIVMSG', @target, text)
607
+ end
608
+ end
609
+
610
+ # QUIT [ <text> ]
611
+ class Quit < Message
612
+ attr_accessor :text
613
+
614
+ def initialize(text = nil)
615
+ if @text = text
616
+ super(nil, 'QUIT', @text)
617
+ else
618
+ super(nil, 'QUIT')
619
+ end
620
+ end
621
+ end
622
+
623
+ # USER <user> <mode> <unused> <realname>
624
+ class User < Message
625
+ attr_accessor :user, :realname, :mode
626
+
627
+ def initialize(*args)
628
+ # puts ">>>>> User#initialize(#{args.inspect})"
629
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 2)" if args.size < 2
630
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 4)" if args.size > 4
631
+
632
+ @user = args.shift
633
+
634
+ # treat mode and "unused" as optional for convenience
635
+ @mode = args.size > 1 && args.shift || USER_MODE_DEFAULT
636
+
637
+ args.shift if args.size > 1
638
+
639
+ @realname = args.shift
640
+
641
+ # puts ">>>>> @user=#{@user.inspect}, @mode=#{@mode.inspect}, unused=#{unused.inspect}, @realname=#{@realname.inspect}"
642
+ super(nil, 'USER', @user, @mode, '*', @realname)
643
+ # puts ">>>>> prefix=#{prefix.inspect}, command=#{command.inspect}, parameters=#{parameters.inspect}"
644
+ end
645
+ end
646
+
647
+ class << self
648
+ def start(user, realname, address, port = nil, &block)
649
+ new(address, port).start(user, realname, &block)
650
+ end
651
+ end
652
+
653
+ def initialize(address, port = nil)
654
+ @address = address
655
+ @port = port || PORT_DEFAULT
656
+ @started = false
657
+ @socket = nil
658
+ end
659
+
660
+ def started?
661
+ @started
662
+ end
663
+
664
+ def start(user, realname, nickname = nil)
665
+ raise IOError, 'IRC session already started' if started?
666
+
667
+ if block_given?
668
+ begin
669
+ do_start(user, realname, nickname)
670
+ return yield(self)
671
+ ensure
672
+ do_finish
673
+ end
674
+ else
675
+ do_start(user, realname, nickname)
676
+ return self
677
+ end
678
+ end
679
+
680
+ def finish
681
+ raise IOError, 'IRC session not yet started' if ! started?
682
+ end
683
+
684
+ def each
685
+ while line = @socket.readline
686
+ IRC.logger.debug "<<<<< #{line.inspect}"
687
+
688
+ message = Message.parse(line.chomp)
689
+
690
+ if message.respond_to? :ctcp
691
+ message.ctcp.each do |ctcp|
692
+ ctcp.source = message.prefix.nickname
693
+ ctcp.target = message.target
694
+
695
+ yield ctcp
696
+ end
697
+ next if message.text.empty?
698
+ end
699
+
700
+ case message
701
+ when Net::IRC::Ping
702
+ pong message.server
703
+ else
704
+ yield message
705
+ end
706
+ end
707
+ rescue IOError
708
+ raise if started?
709
+ end
710
+
711
+ def ctcp(target, text)
712
+ privmsg(target, "\001#{text}\001")
713
+ end
714
+
715
+ def ctcp_version(target, *parameters)
716
+ notice(target, CTCPVersion.new(*parameters).to_s)
717
+ end
718
+
719
+ def ctcp_ping(target, arg = nil)
720
+ notice(target, CTCPPing.new(arg).to_s)
721
+ end
722
+
723
+ def ctcp_time(target, time = nil)
724
+ time ||= Time.now
725
+ differential = '%.2d%.2d' % (time.utc_offset / 60).divmod(60)
726
+ notice(target, CTCPTime.new(time.strftime("%a, %d %b %Y %H:%M #{differential}")).to_s)
727
+ end
728
+
729
+ def join(channels = nil)
730
+ case channels
731
+ when NilClass
732
+ Join.new('0')
733
+ when Hash
734
+ Join.new(channels.keys.join(','), channels.values.join(','))
735
+ when Array
736
+ Join.new(channels.join(','))
737
+ else
738
+ Join.new(channels.to_s)
739
+ end.write(@socket)
740
+ end
741
+
742
+ def nick(nickname)
743
+ Nick.new(nickname).write(@socket)
744
+ end
745
+
746
+ def notice(target, text)
747
+ Notice.new(target, text).write(@socket)
748
+ end
749
+
750
+ def part(channels, message = nil)
751
+ if message
752
+ Part.new(Array(channels).join(','), message)
753
+ else
754
+ Part.new(Array(channels).join(','))
755
+ end.write(@socket)
756
+ end
757
+
758
+ def pong(server, target = nil)
759
+ Pong.new(server, target).write(@socket)
760
+ end
761
+
762
+ def privmsg(target, text)
763
+ Privmsg.new(target, text).write(@socket)
764
+ end
765
+
766
+ def quit(text = nil)
767
+ Quit.new(text).write(@socket)
768
+ end
769
+
770
+ def user(user, realname, mode = nil)
771
+ User.new(user, mode || USER_MODE_DEFAULT, realname).write(@socket)
772
+ end
773
+
774
+ private
775
+ def do_start(user, realname, nickname = nil)
776
+ @socket = InternetMessageIO.old_open(@address, @port)
777
+ # TODO: Pass.new(password).write(@socket)
778
+ nick(user)
779
+ user(user, realname)
780
+ @started = true
781
+ ensure
782
+ do_finish if ! started?
783
+ end
784
+
785
+ def do_finish
786
+ quit if started?
787
+ ensure
788
+ @started = false
789
+ @socket.close if @socket && ! @socket.closed?
790
+ @socket = nil
791
+ end
792
+ end
793
+ end
@@ -0,0 +1,29 @@
1
+ ---
2
+ 1: RPL_WELCOME
3
+ 2: RPL_YOURHOST
4
+ 3: RPL_CREATED
5
+ 4: RPL_MYINFO
6
+ 5: RPL_BOUNCE
7
+ 207: RPL_TRACESERVICE
8
+ 210: RPL_TRACERECONNECT
9
+ 240: RPL_STATSVLINE
10
+ 245: RPL_STATSSLINE
11
+ 246: RPL_STATSPING
12
+ 247: RPL_STATSBLINE
13
+ 250: RPL_STATSDLINE
14
+ 262: RPL_TRACEEND
15
+ 263: RPL_TRYAGAIN
16
+ 325: RPL_UNIQOPIS
17
+ 346: RPL_INVITELIST
18
+ 347: RPL_ENDOFINVITELIST
19
+ 348: RPL_EXCEPTLIST
20
+ 349: RPL_ENDOFEXCEPTLIST
21
+ 383: RPL_YOURESERVICE
22
+ 408: ERR_NOSUCHSERVICE
23
+ 415: ERR_BADMASK
24
+ 437: ERR_UNAVAILRESOURCE
25
+ 476: ERR_BADCHANMASK
26
+ 477: ERR_NOCHANMODES
27
+ 478: ERR_BANLISTFULL
28
+ 484: ERR_RESTRICTED
29
+ 485: ERR_UNIQOPPRIVSNEEDED
data/net-irc.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ SPEC = Gem::Specification.new do |s|
2
+ # identify the gem
3
+ s.name = "net-irc"
4
+ s.version = "0.9.1"
5
+ s.author = "S. Brent Faulkner"
6
+ s.email = "brentf@unwwwired.net"
7
+ s.homepage = "http://www.unwwwired.net"
8
+ # platform of choice
9
+ s.platform = Gem::Platform::RUBY
10
+ # description of gem
11
+ s.summary = "a ruby implementation of the IRC client protocol"
12
+ s.files = %w(examples/test.rb lib/net/irc.rb lib/net/rfc2812.yml MIT-LICENSE Rakefile README.markdown net-irc.gemspec)
13
+ s.require_path = "lib"
14
+ s.autorequire = "irc"
15
+ # s.test_file = "test/net-irc.rb"
16
+ s.has_rdoc = true
17
+ s.extra_rdoc_files = ["README.markdown"]
18
+ # s.add_dependency("fastercsv", ">= 1.2.3")
19
+ # s.executables = ["rsql"]
20
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sbfaulkner-net-irc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.1
5
+ platform: ruby
6
+ authors:
7
+ - S. Brent Faulkner
8
+ autorequire: irc
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: brentf@unwwwired.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - examples/test.rb
26
+ - lib/net/irc.rb
27
+ - lib/net/rfc2812.yml
28
+ - MIT-LICENSE
29
+ - Rakefile
30
+ - README.markdown
31
+ - net-irc.gemspec
32
+ has_rdoc: true
33
+ homepage: http://www.unwwwired.net
34
+ post_install_message:
35
+ rdoc_options: []
36
+
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: a ruby implementation of the IRC client protocol
58
+ test_files: []
59
+