net-yail 1.3.5 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/YAIL-RDOC ADDED
@@ -0,0 +1,51 @@
1
+ github page: http://github.com/Nerdmaster/ruby-irc-yail
2
+
3
+ Latest stable release's documentation is always at http://ruby-irc-yail.nerdbucket.com/
4
+
5
+ Net::YAIL is a library built for dealing with IRC communications in Ruby.
6
+ This is a project I've been building for about three years, based
7
+ originally on the very messy initial release of IRCSocket (back when I first
8
+ started, that was the only halfway-decent IRC lib I found). I've put a lot
9
+ of time and effort into cleaning it up to make it better for my own uses,
10
+ and now it's almost entirely my code.
11
+
12
+ Some credit should also be given to Ruby-IRC, as I stole its eventmap.yml
13
+ file with very minor modifications.
14
+
15
+ This library may not be useful to everybody (or anybody other than myself,
16
+ for that matter), and Ruby-IRC or another lib may work for your situation
17
+ far better than this thing will, but the general design I built here has
18
+ just felt more natural to me than the other libraries I've looked at since
19
+ I started my project.
20
+
21
+ =Example Usage
22
+
23
+ For the nitty-gritty, you can see all this stuff in the Net::YAIL page, as
24
+ well as more complete documentation about the system. For a complete bot,
25
+ check out the IRCBot source code. Below is just a very simple example:
26
+
27
+ require 'rubygems'
28
+ require 'net/yail'
29
+
30
+ irc = Net::YAIL.new(
31
+ :address => 'irc.someplace.co.uk',
32
+ :username => 'Frakking Bot',
33
+ :realname => 'John Botfrakker',
34
+ :nicknames => ['bot1', 'bot2', 'bot3']
35
+ )
36
+
37
+ # Register a proc handler
38
+ irc.prepend_handler :incoming_welcome, proc {|text, args|
39
+ irc.join('#foo')
40
+ return false
41
+ }
42
+
43
+ # Register a block
44
+ irc.prepend_handler(:incoming_invite) {|full, user, channel| irc.join(channel) }
45
+
46
+ # Loops forever here until CTRL+C is hit.
47
+ irc.start_listening!
48
+
49
+ Now we've built a simple IRC listener that will connect to a (probably
50
+ invalid) network, identify itself, and sit around waiting for the welcome
51
+ message. After this has occurred, we join a channel and return false.
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'net/yail'
3
+ require 'getopt/long'
4
+
5
+ # User specifies channel and nick
6
+ opt = Getopt::Long.getopts(
7
+ ['--network', Getopt::REQUIRED],
8
+ ['--nick', Getopt::REQUIRED],
9
+ ['--loud', Getopt::BOOLEAN]
10
+ )
11
+
12
+ irc = Net::YAIL.new(
13
+ :address => opt['network'],
14
+ :username => 'Frakking Bot',
15
+ :realname => 'John Botfrakker',
16
+ :nicknames => [opt['nick']],
17
+ :loud => opt['loud']
18
+ )
19
+
20
+ # Register handlers
21
+ irc.prepend_handler(:incoming_welcome) {|text, args| irc.join('#bots') }
22
+ irc.prepend_handler(:incoming_invite) {|full, user, channel| irc.join(channel) }
23
+
24
+ # Start the bot and enjoy the endless loop
25
+ irc.start_listening!
data/lib/net/yail.rb CHANGED
@@ -9,8 +9,11 @@ require 'net/yail/magic_events'
9
9
  require 'net/yail/default_events'
10
10
  require 'net/yail/output_api'
11
11
 
12
+ # This tells us our version info.
13
+ require 'net/yail/yail-version'
14
+
12
15
  # Finally, a real class to include!
13
- require 'net/yail/message_parser.rb'
16
+ require 'net/yail/event'
14
17
 
15
18
  # If a thread crashes, I want the app to die. My threads are persistent, not
16
19
  # temporary.
@@ -40,18 +43,19 @@ module Net
40
43
  #
41
44
  # Current list of incoming events and the parameters sent to the handler:
42
45
  # * :incoming_any(raw) - "global" handler that catches all events and may
43
- # modify their data as necessary before the real handler is hit. This
46
+ # modify their data as necessary before the real handler is hit. This should
44
47
  # only be used in cases where it's necessary to grab a lot of events that
45
48
  # also need to be processed elsewhere, such as doing input filtering for
46
49
  # all events that have a "text" element. Note that returning true from
47
50
  # this handler will still break the entire chain of event handling.
48
51
  # * :incoming_msg(fullactor, actor, target, text) - Normal message from actor to target
49
52
  # * :incoming_act(fullactor, actor, target, text) - CTCP "action" (emote) from actor to target
50
- # * :incoming_invite(fullactor, actor, target, text) - INVITE to target channel from actor
53
+ # * :incoming_invite(fullactor, actor, target) - INVITE to target channel from actor
51
54
  # * :incoming_ctcp(fullactor, actor, target, text) - CTCP other than "action" from actor to target
52
55
  # * :incoming_ctcpreply(fullactor, actor, target, text) - CTCP NOTICE from actor to target
53
56
  # * :incoming_notice(fullactor, actor, target, text) - other NOTICE from actor to target
54
57
  # * :incoming_mode(fullactor, actor, target, modes, objects) - actor sets modes on objects in target channel
58
+ # * :incoming_topic_change(fullactor, actor, channel, text) - actor sets channel topic to given text string
55
59
  # * :incoming_join(fullactor, actor, target) - actor joins target channel
56
60
  # * :incoming_part(fullactor, actor, target, text) - actor leaves target with message in text
57
61
  # * :incoming_kick(fullactor, actor, target, object, text) - actor kicked object from target with reason 'text'
@@ -230,8 +234,6 @@ class YAIL
230
234
  include Net::IRCEvents::Defaults
231
235
  include Net::IRCOutputAPI
232
236
 
233
- VERSION = '1.3.5'
234
-
235
237
  attr_reader(
236
238
  :me, # Nickname on the IRC server
237
239
  :registered, # If true, we've been welcomed
@@ -322,6 +324,14 @@ class YAIL
322
324
  # Don't listen if socket is dead
323
325
  return if @dead_socket
324
326
 
327
+ # Exit a bit more gracefully than just crashing out - allow any :outgoing_quit filters to run,
328
+ # and even give the server a second to clean up before we fry the connection
329
+ #
330
+ # TODO: perhaps this should be in a callback so user can override TERM/INT handling
331
+ quithandler = lambda { quit('Terminated by user'); sleep 1; stop_listening; exit }
332
+ trap("INT", quithandler)
333
+ trap("TERM", quithandler)
334
+
325
335
  # Build forced / magic logic - welcome setting @me, ping response, etc.
326
336
  # Since we do these here, nobody can skip them and they're always first.
327
337
  setup_magic_handlers
@@ -336,17 +346,36 @@ class YAIL
336
346
  handle(:outgoing_begin_connection, @username, @address, @realname)
337
347
  end
338
348
 
349
+ # This starts the connection, threading, etc. as start_listening, but *forces* the user into
350
+ # and endless loop. Great for a simplistic bot, but probably not universally desired.
351
+ def start_listening!
352
+ start_listening
353
+ while !@dead_socket
354
+ # This is more for CPU savings than actually needing a delay - CPU spikes if we never sleep
355
+ sleep 0.05
356
+ end
357
+ end
358
+
339
359
  # Kills and clears all threads. See note above about my lack of knowledge
340
360
  # regarding threads. Please help me if you know how to make this system
341
361
  # better. DEAR LORD HELP ME IF YOU CAN!
342
362
  def stop_listening
343
- # Kill all threads if they're really threads
344
- [@ioloop_thread, @input_processor, @privmsg_processor].each {|thread| thread.terminate if Thread === thread}
363
+ return unless Thread === @ioloop_thread
345
364
 
346
- # Just for safety, set everything to nil
347
- @ioloop_thread = nil
348
- @input_processor = nil
349
- @privmsg_processor = nil
365
+ # Do thread-ending in a new thread or else we're liable to kill the
366
+ # thread that's called this method
367
+ Thread.new do
368
+ # Kill all threads if they're really threads
369
+ [@ioloop_thread, @input_processor, @privmsg_processor].each {|thread| thread.terminate if Thread === thread}
370
+
371
+ @socket.close
372
+ @socket = nil
373
+ @dead_socket = true
374
+
375
+ @ioloop_thread = nil
376
+ @input_processor = nil
377
+ @privmsg_processor = nil
378
+ end
350
379
  end
351
380
 
352
381
  private
@@ -382,7 +411,7 @@ class YAIL
382
411
  # This should be called from a thread only! Does nothing but listens
383
412
  # forever for incoming data, and calling handlers due to this listening
384
413
  def io_loop
385
- while true
414
+ loop do
386
415
  # if no data is coming in, don't block the socket!
387
416
  read_incoming_data if Kernel.select([@socket], nil, nil, 0)
388
417
 
@@ -397,7 +426,7 @@ class YAIL
397
426
  # whenever the @input_buffer var has any.
398
427
  def process_input_loop
399
428
  lines = nil
400
- while true
429
+ loop do
401
430
  # Only synchronize long enough to copy and clear the input buffer.
402
431
  @input_buffer_mutex.synchronize do
403
432
  lines = @input_buffer.dup
@@ -456,7 +485,7 @@ class YAIL
456
485
  # Our final thread loop - grabs the first privmsg for each target and
457
486
  # sends it on its way.
458
487
  def process_privmsg_loop
459
- while true
488
+ loop do
460
489
  check_privmsg_output if @next_message_time <= Time.now && !@privmsg_buffer.empty?
461
490
 
462
491
  sleep 0.05
@@ -474,63 +503,69 @@ class YAIL
474
503
  end
475
504
  end
476
505
 
477
- # Use the exciting new parser
478
- msg = Net::YAIL::MessageParser.new(line)
506
+ # Use the exciting new-new parser
507
+ event = Net::YAIL::IncomingEvent.parse(line)
508
+
509
+ # Partial conversion to using events - we still have a horrible case statement, but
510
+ # we're at least using the event object. Slightly less hacky than before.
479
511
 
480
- # In the legacy system, there are never situations where we want the params
481
- # separated into array elements. So we convert them here, since so many
482
- # handlers require a param or two followed by "everything else".
483
- params = msg.params.dup
484
- param1 = params.shift
485
- other1 = params.join(' ') || ''
486
- param2 = params.shift
487
- other2 = params.join(' ') || ''
512
+ # Except for this - we still have to handle numerics the crappy way until we build the proper
513
+ # dispatching of events
514
+ event = event.parent if event.parent && :incoming_numeric == event.parent.type
488
515
 
489
- case msg.command
516
+ case event.type
490
517
  # Ping is important to handle quickly, so it comes first.
491
- when 'PING'
492
- handle(:incoming_ping, msg.params.first)
493
- when /^\d{3}$/
494
- handle_numeric(msg.command.to_i, msg.prefix, msg.nick, param1, other1)
495
- when 'INVITE'
496
- handle(:incoming_invite, msg.prefix, msg.nick, msg.params.last)
497
-
498
- # This can encompass three possible messages, so further refining happens here - the last param
499
- # is always the message itself, so we look for patterns there.
500
- when 'PRIVMSG'
501
- case msg.params.last
502
- when /^\001ACTION (.+?)\001$/
503
- handle(:incoming_act, msg.prefix, msg.nick, msg.params.first, $1)
504
- when /^\001(.+?)\001$/
505
- handle(:incoming_ctcp, msg.prefix, msg.nick, msg.params.first, $1)
506
- else
507
- handle(:incoming_msg, msg.prefix, msg.nick, param1, other1)
508
- end
518
+ when :incoming_ping
519
+ handle(event.type, event.text)
509
520
 
510
- # This can encompass two possible messages, again based on final param
511
- when 'NOTICE'
512
- case msg.params.last
513
- when /^\001(.+?)\001$/
514
- handle(:incoming_ctcpreply, msg.prefix, msg.nick, msg.params.first, $1)
515
- else
516
- handle(:incoming_notice, msg.prefix, msg.nick, param1, other1)
517
- end
521
+ when :incoming_numeric
522
+ # Lovely - I passed in a "nick" - which, according to spec, is NEVER part of a numeric reply
523
+ handle_numeric(event.numeric, event.servername, nil, event.target, event.text)
524
+
525
+ when :incoming_invite
526
+ handle(event.type, event.fullname, event.nick, event.channel)
527
+
528
+ # Fortunately, the legacy handler for all five "message" types is the same!
529
+ when :incoming_msg, :incoming_ctcp, :incoming_act, :incoming_notice, :incoming_ctcpreply
530
+ # Legacy handling requires merger of target and channel....
531
+ target = event.target if event.pm?
532
+ target = event.channel if !target
533
+
534
+ # Notices come from server sometimes, so... another merger for legacy fun!
535
+ nick = event.server? ? '' : event.nick
536
+ handle(event.type, event.from, nick, target, event.text)
537
+
538
+ # This is a bit painful for right now - just use some hacks to make it work semi-nicely,
539
+ # but let's not put hacks into the core Event object. Modes need reworking soon anyway.
540
+ #
541
+ # NOTE: text is currently the mode settings ('+b', for instance) - very bad. TODO: FIX FIX FIX!
542
+ when :incoming_mode
543
+ # Modes can come from the server, so legacy system again regularly sent nil data....
544
+ nick = event.server? ? '' : event.nick
545
+ handle(event.type, event.from, nick, event.channel, event.text, event.targets.join(' '))
546
+
547
+ when :incoming_topic_change
548
+ handle(event.type, event.fullname, event.nick, event.channel, event.text)
518
549
 
519
- when 'MODE'
520
- handle(:incoming_mode, msg.prefix, msg.nick, param1, param2, other2)
521
- when 'JOIN'
522
- handle(:incoming_join, msg.prefix, msg.nick, param1)
523
- when 'PART'
524
- handle(:incoming_part, msg.prefix, msg.nick, param1, other1)
525
- when 'KICK'
526
- handle(:incoming_kick, msg.prefix, msg.nick, param1, param2, other2)
527
- when 'QUIT'
528
- handle(:incoming_quit, msg.prefix, msg.nick, param1)
529
- when 'NICK'
530
- handle(:incoming_nick, msg.prefix, msg.nick, param1)
550
+ when :incoming_join
551
+ handle(event.type, event.fullname, event.nick, event.channel)
552
+
553
+ when :incoming_part
554
+ handle(event.type, event.fullname, event.nick, event.channel, event.text)
555
+
556
+ when :incoming_kick
557
+ handle(event.type, event.fullname, event.nick, event.channel, event.target, event.text)
558
+
559
+ when :incoming_quit
560
+ handle(event.type, event.fullname, event.nick, event.text)
561
+
562
+ when :incoming_nick
563
+ handle(event.type, event.fullname, event.nick, event.text)
531
564
 
532
565
  # Unknown line!
533
566
  else
567
+ # This should really never happen, so reporting is forced here for now
568
+ report('Unknown line: %s!' % line.inspect)
534
569
  handle(:incoming_miscellany, line)
535
570
  end
536
571
  end
@@ -542,9 +577,14 @@ class YAIL
542
577
  public
543
578
  # Event handler hook. Kinda hacky. Calls your event(s) before the default
544
579
  # event. Default stuff will happen if your handler doesn't return true.
545
- def prepend_handler(event, *procs)
580
+ def prepend_handler(event, *procs, &block)
546
581
  raise "Cannot change handlers while threads are listening!" if @ioloop_thread
547
582
 
583
+ # Allow blocks as well as procs
584
+ if block_given?
585
+ procs.push(block)
586
+ end
587
+
548
588
  # See if this is a word for a numeric - only applies to incoming events
549
589
  if (event.to_s =~ /^incoming_(.*)$/)
550
590
  number = @event_number_lookup[$1].to_i
@@ -114,6 +114,9 @@ module Defaults
114
114
  # user or try again in 20 minutes or something. Note that we only fail
115
115
  # when the adapter hasn't gotten logged in yet - an attempt at changing
116
116
  # nick after registration (welcome message) just generates a report.
117
+ #
118
+ # TODO: This should really not even be here. Client should have full control over whether or not
119
+ # they want this. Base IRC bot class should have this, but not the core YAIL lib.
117
120
  def _nicknameinuse(text, args)
118
121
  text =~ /^(\S+)/
119
122
  report "Nickname #{$1} is already in use."
@@ -153,6 +156,8 @@ module Defaults
153
156
  end
154
157
 
155
158
  # Names line
159
+ #
160
+ # TODO: Either store this data silently or ditch this code - this verbosity doesn't belong in a core lib
156
161
  def _namreply(text, args)
157
162
  text =~ /^(@|\*|=) (\S+) :?(.+)$/
158
163
  channeltype = {'@' => 'Secret', '*' => 'Private', '=' => 'Normal'}[$1]
@@ -0,0 +1,285 @@
1
+ require 'net/yail/message_parser.rb'
2
+
3
+ module Net
4
+ class YAIL
5
+
6
+ # Base event class for stuff shared by any type of event. Note that :type and :handled
7
+ # apply to *all* events, so they're explicitly defined here.
8
+ class BaseEvent
9
+ attr_reader :type, :handled
10
+
11
+ # Creates an event object and sets up some sane defaults for common elements
12
+ def initialize()
13
+ @handled = false
14
+ @type = nil
15
+ end
16
+
17
+ # Sets this event as having been handled (i.e., no further handling should occur)
18
+ def handle; @handled = true; end
19
+
20
+ # Cheesy shortcut to @handled in "boolean" form
21
+ def handled?; return @handled; end
22
+ end
23
+
24
+ # The outgoing event class - outgoing events haven't got much in
25
+ # common, so this class is primarily to facilitate the new system.
26
+ class OutgoingEvent < BaseEvent
27
+ # Outgoing events in our system are always :outgoing_xxx
28
+ def type
29
+ return :"outgoing_#{@type.to_s}"
30
+ end
31
+ end
32
+
33
+ # This is the incoming event class. For all situations where the server
34
+ # sent us some kind of event, this class handles all the data.
35
+ #
36
+ # All events will have a :raw attribute that stores the exact text sent from
37
+ # the IRC server. Other possible pieces of data are as follows:
38
+ # * fullname: Rarely needed, full text of origin of an action
39
+ # * nick: Nickname of originator of an event
40
+ # * channel: Where applicable, the name of the channel in which the event
41
+ # happened.
42
+ # * text: Actual message/emote/notice/etc
43
+ # * target: User targeted for various commands - PRIVMSG/NOTICE recipient, KICK victim, etc
44
+ # * pm?: Set to true if the event is a "private" event (not sent to the
45
+ # channel). Useful primarily for message types of events (PRIVMSG).
46
+ #
47
+ # To more easily call the right user event, we store each event type and its "parent" where it
48
+ # makes sense. This ensures that a user currently handling :incoming_ctcp won't be totally
49
+ # screwed when we add in :incoming_userinfo and such. Top-level handlers aren't in here, which
50
+ # is vital to avoid trying to hack around numerics (not to mention a bunch of fairly useless
51
+ # data)
52
+ #
53
+ # Look at the source for specifics of which IRC events set up what data. Or try to parse the
54
+ # lovely RFCs....
55
+ #
56
+ # For convenience, the event stores its MessageParser object so users can access raw data as
57
+ # necessary (for numeric messages, this is often useful)
58
+ class IncomingEvent < BaseEvent
59
+ attr_reader :raw, :msg
60
+ private_class_method :new
61
+
62
+ # Sets up data and accessors
63
+ def initialize(data = {})
64
+ super()
65
+
66
+ # Don't modify incoming element!
67
+ @data = data.dup
68
+ @raw = @data.delete(:raw)
69
+ @msg = @data.delete(:msg)
70
+ @type = @data.delete(:type)
71
+
72
+ # Give useful accessors in a hacky but fun way! I can't decide if I prefer the pain of
73
+ # using method_missing or the pain of not knowing how to do this without a string eval....
74
+ for key in @data.keys
75
+ key = key.to_s
76
+ self.instance_eval("def #{key}; return @data[:#{key}]; end")
77
+ end
78
+ end
79
+
80
+ # Helps us debug
81
+ def to_s
82
+ return super().gsub('IncomingEvent', "IncomingEvent[#{@type.to_s}]")
83
+ end
84
+
85
+ # Incoming events in our system are always :incoming_xxx
86
+ def type; return :"incoming_#{@type.to_s}"; end
87
+
88
+ # Effectively our event "factory" - uses Net::YAIL::MessageParser and returns an event
89
+ # object - usually just one, but TODO: some lines actually contain multiple messages. When
90
+ # EventManager or similar is implemented, we'll just register events and this will be a non-issue
91
+ def self.parse(line)
92
+ # Parse with MessageParser to get raw IRC info
93
+ raw = line.dup
94
+ msg = Net::YAIL::MessageParser.new(line)
95
+ data = { :raw => raw, :msg => msg, :parent => nil }
96
+
97
+ # Not all messages from the server identify themselves as such, so we just assume it's from
98
+ # the server unless we explicitly see a nick
99
+ data[:server?] = true
100
+
101
+ # Sane defaults for most messages
102
+ if msg.servername
103
+ data[:from] = data[:servername] = msg.servername
104
+ elsif msg.prefix && msg.nick
105
+ data[:from] = data[:fullname] = msg.prefix
106
+ data[:nick] = msg.nick
107
+ data[:server?] = false
108
+ end
109
+
110
+ case msg.command
111
+ when 'ERROR'
112
+ data[:type] = :error
113
+ data[:text] = msg.params.last
114
+ event = new(data)
115
+
116
+ when 'PING'
117
+ data[:type] = :ping
118
+ data[:text] = msg.params.last
119
+ event = new(data)
120
+
121
+ when 'TOPIC'
122
+ data[:type] = :topic_change
123
+ data[:channel] = msg.params.first
124
+ data[:text] = msg.params.last
125
+ event = new(data)
126
+
127
+ when /^\d{3}$/
128
+ # Get base event for the "numeric" type - so many of these exist, and so few are likely
129
+ # to be handled directly. Sadly, some hackery has to happen here to make "text" backward-
130
+ # compatible since old YAIL auto-joined all parameters into one string.
131
+ data[:type] = :numeric
132
+ params = msg.params.dup
133
+ data[:target] = params.shift
134
+ data[:parameters] = params
135
+ data[:text] = params.join(' ')
136
+ data[:numeric] = msg.command.to_i
137
+ event = new(data)
138
+
139
+ # Create child event for the specific numeric
140
+ data[:type] = msg.command.to_sym
141
+ data[:parent] = event
142
+ event = new(data)
143
+
144
+ when 'INVITE'
145
+ data[:type] = :invite
146
+ data[:channel] = msg.params.last
147
+
148
+ # This should always be us, but still worth capturing just in case
149
+ data[:target] = msg.params.first
150
+ event = new(data)
151
+
152
+ # This can encompass three possible messages, so further refining happens here - the last param
153
+ # is always the message itself, so we look for patterns there.
154
+ when 'PRIVMSG'
155
+ event = privmsg_events(msg, data)
156
+
157
+ # This can encompass two possible messages, again based on final param
158
+ when 'NOTICE'
159
+ event = notice_events(msg, data)
160
+
161
+ when 'MODE'
162
+ event = mode_events(msg, data)
163
+
164
+ when 'JOIN'
165
+ data[:type] = :join
166
+ data[:channel] = msg.params.last
167
+ event = new(data)
168
+
169
+ when 'PART'
170
+ data[:type] = :part
171
+ data[:channel] = msg.params.first
172
+ data[:text] = msg.params.last
173
+ event = new(data)
174
+
175
+ when 'KICK'
176
+ data[:type] = :kick
177
+ data[:channel] = msg.params[0]
178
+ data[:target] = msg.params[1]
179
+ data[:text] = msg.params[2]
180
+ event = new(data)
181
+
182
+ when 'QUIT'
183
+ data[:type] = :quit
184
+ data[:text] = msg.params.first
185
+ event = new(data)
186
+
187
+ when 'NICK'
188
+ data[:type] = :nick
189
+ data[:text] = msg.params.first
190
+ event = new(data)
191
+
192
+ # Unknown line! If this library is complete, we should *never* see this situation occur,
193
+ # so it'll be up to the caller to decide what to do.
194
+ else
195
+ data[:type] = :unknown
196
+ event = new(data)
197
+ end
198
+
199
+ return event
200
+ end
201
+
202
+ protected
203
+
204
+ # Parses a MODE to its events - basic, backward-compatible :mode event for now, but
205
+ # TODO: eventually get set up for multiple atomic mode messages (need event manager first)
206
+ def self.mode_events(msg, data)
207
+ data[:type] = :mode
208
+ data[:channel] = msg.params.shift
209
+ data[:text] = msg.params.shift
210
+ data[:targets] = msg.params
211
+ event = new(data)
212
+ end
213
+
214
+ # Parses basic data for the "message" constructs: PRIVMSG and NOTICE
215
+ def self.parse_message_data(msg, data)
216
+ # Defaults so all messages have a fairly standard interface
217
+ data[:pm?] = false
218
+ data[:target] = nil
219
+ data[:channel] = msg.params.first
220
+
221
+ # If this isn't a channel message, set up PM data - keep channel, just set it to nil so the
222
+ # API is consistent
223
+ unless msg.params.first =~ /^[!&#+]/
224
+ data[:channel] = nil
225
+ data[:pm?] = true
226
+ data[:target] = msg.params.first
227
+ end
228
+ end
229
+
230
+ # Parses a PRIVMSG to its events - CTCP stuff needs parents, ACT stuff needs two-parent
231
+ # hierarchy
232
+ def self.privmsg_events(msg, data)
233
+ # Parse common elements
234
+ parse_message_data(msg, data)
235
+
236
+ # Get base event
237
+ data[:type] = :msg
238
+ data[:text] = msg.params.last
239
+ event = new(data)
240
+
241
+ # Is this CTCP?
242
+ if event.text =~ /^\001(.+?)\001$/
243
+ data[:type] = :ctcp
244
+ data[:text] = $1
245
+ data[:parent] = event
246
+
247
+ event = new(data)
248
+ end
249
+
250
+ # CTCP action?
251
+ if :ctcp == data[:type] && event.text =~ /^ACTION (.+)$/
252
+ data[:type] = :act
253
+ data[:text] = $1
254
+ data[:parent] = event
255
+
256
+ event = new(data)
257
+ end
258
+
259
+ return event
260
+ end
261
+
262
+ # Parses a NOTICE to its events - CTCP replies come through here
263
+ def self.notice_events(msg, data)
264
+ # Parse common elements
265
+ parse_message_data(msg, data)
266
+
267
+ # Get base event
268
+ data[:type] = :notice
269
+ data[:text] = msg.params.last
270
+ event = new(data)
271
+
272
+ if event.text =~ /^\001(.+?)\001$/
273
+ data[:type] = :ctcp_reply
274
+ data[:text] = $1
275
+ data[:parent] = event
276
+
277
+ event = new(data)
278
+ end
279
+
280
+ return event
281
+ end
282
+
283
+ end
284
+ end
285
+ end
@@ -42,7 +42,7 @@ module Magic
42
42
  def magic_nick(fullactor, actor, nickname)
43
43
  # Reset name if it's me
44
44
  if actor.downcase == @me.downcase
45
- @me = nickname
45
+ @me = nickname.dup
46
46
  end
47
47
 
48
48
  # Allow user-defined events (and/or reporting)
@@ -1,3 +1,6 @@
1
+ # Net::YAIL's solution to the amazing lack of *useful* IRC message parsers. So far as I know,
2
+ # this will parse any message coming from an RFC-compliant IRC server.
3
+
1
4
  module Net
2
5
  class YAIL
3
6
 
@@ -57,7 +60,8 @@ class MessageParser
57
60
  # separate the trailing arg as it can contain nearly any character. And
58
61
  # finally, we split the "middle" args on space.
59
62
  arglist = matches[6].sub(/^ +/, '')
60
- (middle_args, trailing_arg) = arglist.split(/ *:/, 2)
63
+ arglist.sub!(/^:/, ' :')
64
+ (middle_args, trailing_arg) = arglist.split(/ +:/, 2)
61
65
  @params.push(middle_args.split(/ +/), trailing_arg)
62
66
  @params.compact!
63
67
  @params.flatten!
@@ -32,7 +32,7 @@ module IRCOutputAPI
32
32
  # speaking, that is.
33
33
  def raw(line, report = true)
34
34
  @socket.puts line
35
- report "bot: '#{line}'" if report
35
+ report "bot: #{line.inspect}" if report
36
36
  end
37
37
 
38
38
  # Calls :outgoing_privmsg handler, then sends a message (text) out to the
@@ -0,0 +1,5 @@
1
+ module Net
2
+ class YAIL
3
+ VERSION = '1.4.0'
4
+ end
5
+ end
data/tests/net_yail.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib")
2
2
  require 'net/yail'
3
3
  require 'net/yail/message_parser'
4
+ require 'net/yail/event'
4
5
  puts "VERSION: #{Net::YAIL::VERSION}"
data/tests/tc_event.rb ADDED
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/net_yail'
3
+ require 'test/unit'
4
+
5
+ # Stolen from tc_message_parser - same tests, different object
6
+ class MessageParserEventTest < Test::Unit::TestCase
7
+ # Simplest test case, I think
8
+ def test_ping
9
+ event = Net::YAIL::IncomingEvent.parse("PING :nerdbucket.com")
10
+ assert_equal 'nerdbucket.com', event.text
11
+ assert_equal :incoming_ping, event.type
12
+ assert !event.respond_to?(:servername)
13
+ assert !event.respond_to?(:nick)
14
+ assert !event.respond_to?(:channel)
15
+ assert !event.respond_to?(:fullname)
16
+ assert event.server?
17
+ end
18
+
19
+ def test_topic
20
+ event = Net::YAIL::IncomingEvent.parse(":Dude!dude@nerdbucket.com TOPIC #nerdtalk :31 August 2010 \357\277\275 Foo.")
21
+ assert_equal :incoming_topic_change, event.type
22
+ assert_equal 'Dude', event.nick
23
+ assert_equal "31 August 2010 \357\277\275 Foo.", event.text
24
+ assert_equal '#nerdtalk', event.channel
25
+ assert_equal 'Dude!dude@nerdbucket.com', event.fullname
26
+ end
27
+
28
+ # Parsing of PRIVMSG messages
29
+ def test_messages
30
+ # Basic test of privmsg-type command
31
+ event = Net::YAIL::IncomingEvent.parse(':Nerdmaster!jeremy@nerdbucket.com PRIVMSG Nerdminion :Do my bidding!!')
32
+ assert_nil event.parent
33
+ assert_equal 'Nerdmaster', event.nick
34
+ assert_equal 'jeremy', event.msg.user
35
+ assert_equal 'nerdbucket.com', event.msg.host
36
+ assert_equal 'Nerdmaster!jeremy@nerdbucket.com', event.fullname
37
+ assert_equal 'Nerdmaster!jeremy@nerdbucket.com', event.from
38
+ assert !event.server?
39
+ assert_equal 'PRIVMSG', event.msg.command
40
+ assert_equal :incoming_msg, event.type
41
+ assert_equal 'Nerdminion', event.target
42
+ assert_equal true, event.pm?
43
+ assert_equal 'Do my bidding!!', event.text
44
+
45
+ # CTCP to user
46
+ event = Net::YAIL::IncomingEvent.parse(":Nerdmaster!jeremy@nerdbucket.com PRIVMSG Nerdminion :\001FOO is to bar as BAZ is to...?\001")
47
+ assert_equal 'Nerdmaster', event.nick
48
+ assert_equal :incoming_ctcp, event.type
49
+ assert_nil event.channel
50
+ assert_equal 'Nerdminion', event.target
51
+ assert_equal true, event.pm?
52
+ assert_equal 'FOO is to bar as BAZ is to...?', event.text
53
+ assert_equal :incoming_msg, event.parent.type
54
+ assert_equal "\001FOO is to bar as BAZ is to...?\001", event.parent.text
55
+ assert_nil event.parent.parent
56
+
57
+ # Action to channel
58
+ event = Net::YAIL::IncomingEvent.parse(":Nerdmaster!jeremy@nerdbucket.com PRIVMSG #bottest :\001ACTION gives Towelie a joint\001")
59
+ assert_equal 'Nerdmaster', event.nick
60
+ assert_equal :incoming_act, event.type
61
+ assert_equal '#bottest', event.channel
62
+ assert_equal false, event.pm?
63
+ assert_equal 'gives Towelie a joint', event.text
64
+ assert_equal :incoming_ctcp, event.parent.type
65
+ assert_equal "ACTION gives Towelie a joint", event.parent.text
66
+ assert_equal :incoming_msg, event.parent.parent.type
67
+ assert_equal "\001ACTION gives Towelie a joint\001", event.parent.parent.text
68
+ assert_nil event.parent.parent.parent
69
+
70
+ # PM to channel with less common prefix
71
+ event = Net::YAIL::IncomingEvent.parse(":Nerdmaster!jeremy@nerdbucket.com PRIVMSG !bottest :foo")
72
+ assert_equal :incoming_msg, event.type
73
+ assert_equal false, event.pm?
74
+ assert_equal '!bottest', event.channel
75
+ end
76
+
77
+ # Quick test of a numeric message I've ACTUALLY SEEN!!
78
+ def test_numeric
79
+ event = Net::YAIL::IncomingEvent.parse(':nerdbucket.com 266 Nerdmaster :Current global users: 22 Max: 33')
80
+ assert_equal 'Current global users: 22 Max: 33', event.text
81
+ assert_equal :incoming_266, event.type
82
+ assert_equal 'nerdbucket.com', event.servername
83
+ assert_equal 'nerdbucket.com', event.from
84
+ assert_equal 'Nerdmaster', event.target
85
+
86
+ assert !event.respond_to?(:nick)
87
+ assert !event.respond_to?(:channel)
88
+ assert !event.respond_to?(:fullname)
89
+ assert event.server?
90
+
91
+ assert_equal :incoming_numeric, event.parent.type
92
+ assert_equal 266, event.parent.numeric
93
+
94
+ # Numeric with multiple args
95
+ event = Net::YAIL::IncomingEvent.parse(':someserver.co.uk.fn.bb 366 Towelie #bottest :End of /NAMES list.')
96
+ assert_equal :incoming_366, event.type
97
+ assert_equal '#bottest End of /NAMES list.', event.text
98
+ assert_equal ['#bottest', 'End of /NAMES list.'], event.parameters
99
+
100
+ # First param in the message params list should still be nick
101
+ assert_equal 'Towelie', event.msg.params.first
102
+ end
103
+
104
+ # Test an invite
105
+ def test_invite
106
+ event = Net::YAIL::IncomingEvent.parse(':Nerdmaster!jeremy@nerdbucket.com INVITE Nerdminion :#nerd-talk')
107
+ assert_equal '#nerd-talk', event.channel
108
+ assert_equal 'Nerdmaster', event.nick
109
+ assert_equal 'Nerdminion', event.target
110
+ end
111
+
112
+ # Test a user joining message
113
+ def test_join
114
+ event = Net::YAIL::IncomingEvent.parse(':Nerdminion!minion@nerdbucket.com JOIN :#nerd-talk')
115
+ assert_equal '#nerd-talk', event.channel
116
+ assert_equal 'Nerdminion', event.nick
117
+ assert_equal :incoming_join, event.type
118
+ end
119
+
120
+ def test_part
121
+ event = Net::YAIL::IncomingEvent.parse(':Nerdminion!minion@nerdbucket.com PART #nerd-talk :No, YOU GO TO HELL')
122
+ assert_equal '#nerd-talk', event.channel
123
+ assert_equal 'Nerdminion', event.nick
124
+ assert_equal 'No, YOU GO TO HELL', event.text
125
+ assert_equal :incoming_part, event.type
126
+ end
127
+
128
+ def test_kick
129
+ event = Net::YAIL::IncomingEvent.parse(%q|:Nerdmaster!jeremy@nerdbucket.com KICK #nerd-talk Nerdminion :You can't quit! You're FIRED!|)
130
+ assert_equal '#nerd-talk', event.channel
131
+ assert_equal 'Nerdminion', event.target
132
+ assert_equal 'Nerdmaster', event.nick
133
+ assert_equal :incoming_kick, event.type
134
+ assert_equal %q|You can't quit! You're FIRED!|, event.text
135
+ end
136
+
137
+ def test_quit
138
+ event = Net::YAIL::IncomingEvent.parse(':TheTowel!ce611d7b0@nerdbucket.com QUIT :Bye bye')
139
+ assert_equal 'TheTowel', event.nick
140
+ assert_equal :incoming_quit, event.type
141
+ assert_equal 'Bye bye', event.text
142
+ end
143
+
144
+ def test_nick
145
+ # Nick change when nick is "unusual" - this also tests the bug with a single parameter being
146
+ # treated incorrectly
147
+ event = Net::YAIL::IncomingEvent.parse(':[|\|1]!~nerdmaste@nerd.nerdbucket.com NICK :Deadnerd')
148
+ assert_equal '[|\|1]', event.nick
149
+ assert_equal :incoming_nick, event.type
150
+ assert_equal 'Deadnerd', event.text
151
+ end
152
+
153
+ # Test some notice stuff
154
+ def test_notice_and_ctcp_reply
155
+ event = Net::YAIL::IncomingEvent.parse(":nerdbucket.com NOTICE Nerdminion :You suck. A lot.")
156
+ assert_equal 'nerdbucket.com', event.servername
157
+ assert_equal 'nerdbucket.com', event.from
158
+ assert event.server?
159
+ assert_equal :incoming_notice, event.type
160
+ assert_equal 'Nerdminion', event.target
161
+ assert !event.respond_to?(:nick)
162
+ assert !event.respond_to?(:fullname)
163
+ assert_equal 'You suck. A lot.', event.text
164
+
165
+ # This CTCP message...
166
+ # ":Nerdmaster!jeremy@nerdbucket.com PRIVMSG Nerdminion \001USERINFO\001"
167
+ # ...might yield this response:
168
+ event = Net::YAIL::IncomingEvent.parse(":Nerdminion!minion@nerdbucket.com NOTICE Nerdmaster :\001USERINFO :Minion of the nerd\001")
169
+ assert !event.respond_to?(:servername)
170
+ assert_equal :incoming_ctcp_reply, event.type
171
+ assert_equal 'Nerdmaster', event.target
172
+ assert_equal 'Nerdminion', event.nick
173
+ assert_equal 'Nerdminion!minion@nerdbucket.com', event.fullname
174
+ assert_equal 'Nerdminion!minion@nerdbucket.com', event.from
175
+ assert_equal 'USERINFO :Minion of the nerd', event.text
176
+
177
+ # Channel-wide notice
178
+ event = Net::YAIL::IncomingEvent.parse(":Nerdmaster!jeremy@nerdbucket.com NOTICE #channel-ten-news :Tonight's late-breaking story...")
179
+ assert !event.respond_to?(:servername)
180
+ assert_equal :incoming_notice, event.type
181
+ assert_equal 'Nerdmaster', event.nick
182
+ assert_equal '#channel-ten-news', event.channel
183
+ assert_nil event.target
184
+ assert_equal 'Nerdmaster!jeremy@nerdbucket.com', event.fullname
185
+ assert_equal %q|Tonight's late-breaking story...|, event.text
186
+ end
187
+
188
+ def test_modes
189
+ event = Net::YAIL::IncomingEvent.parse(":Nerdmaster!jeremy@nerdbucket.com MODE #bots +ob Towelie Doogles!*@*")
190
+ assert !event.respond_to?(:servername)
191
+ assert_equal 'Nerdmaster', event.nick
192
+ assert_equal :incoming_mode, event.type
193
+ assert_equal '#bots', event.channel
194
+ assert_equal ['Towelie', 'Doogles!*@*'], event.targets
195
+ assert_equal '+ob', event.text
196
+
197
+ # Newly-created channels do this
198
+ event = Net::YAIL::IncomingEvent.parse(':nerdbucket.com MODE #bots +nt')
199
+ assert event.server?
200
+ assert_equal 'nerdbucket.com', event.servername
201
+
202
+ # TODO: Parse modes better! This case will be interesting, as the "i" is channel-specific. Useful
203
+ # parsing would give us something like {'#bots' => '-i', 'Doogles!*@*' => '-b', 'Towelie' => '-v', 'Nerdmaster' => '-v'}
204
+ event = Net::YAIL::IncomingEvent.parse(":Nerdmaster!jeremy@nerdbucket.com MODE #bots -bivv Doogles!*@* Towelie Nerdmaster")
205
+ assert_equal 'Nerdmaster', event.nick
206
+ assert_equal :incoming_mode, event.type
207
+ assert_equal '#bots', event.channel
208
+ assert_equal ['Doogles!*@*', 'Towelie', 'Nerdmaster'], event.targets
209
+ assert_equal '-bivv', event.text
210
+
211
+ event = Net::YAIL::IncomingEvent.parse(":Nerdmaster!jeremy@nerdbucket.com MODE #bots +m")
212
+ assert_equal 'Nerdmaster', event.nick
213
+ assert_equal :incoming_mode, event.type
214
+ assert_equal '#bots', event.channel
215
+ assert_equal [], event.targets
216
+ assert_equal '+m', event.text
217
+
218
+ # TODO: This is even worse than above - this is a pretty specific message (setting channel key
219
+ # to 'foo'), but has to be parsed in a pretty absurd way to get that info.
220
+ event = Net::YAIL::IncomingEvent.parse(":Nerdmaster!jeremy@nerdbucket.com MODE #bots +k foo")
221
+ assert_equal 'Nerdmaster', event.nick
222
+ assert_equal :incoming_mode, event.type
223
+ assert_equal '#bots', event.channel
224
+ assert_equal ['foo'], event.targets
225
+ assert_equal '+k', event.text
226
+ end
227
+ end
@@ -2,7 +2,7 @@
2
2
  require File.dirname(__FILE__) + '/net_yail'
3
3
  require 'test/unit'
4
4
 
5
- class MessageParserText < Test::Unit::TestCase
5
+ class MessageParserTest < Test::Unit::TestCase
6
6
  # Very simple parsing of easy strings
7
7
  def test_parse_basic
8
8
  # Basic test of privmsg-type command
@@ -26,6 +26,15 @@ class MessageParserText < Test::Unit::TestCase
26
26
  assert_equal 'arg2', msg.params[1]
27
27
  assert_equal 'final :trailing :arg, --fd9823', msg.params[2]
28
28
 
29
+ # Server command of some type - no actual final arg
30
+ msg = Net::YAIL::MessageParser.new(':nerdbucket.com SERVERCOMMAND arg1:finaltrailingarg')
31
+ assert_equal 'nerdbucket.com', msg.servername
32
+ assert_nil msg.user
33
+ assert_nil msg.nick
34
+ assert_nil msg.host
35
+ assert_equal 'nerdbucket.com', msg.prefix
36
+ assert_equal 'arg1:finaltrailingarg', msg.params[0]
37
+
29
38
  # WTF? Well, IRC spec says it's valid
30
39
  msg = Net::YAIL::MessageParser.new('MAGICFUNKYFRESHCMD arg1 arg2')
31
40
  assert_nil msg.servername
@@ -34,11 +43,11 @@ class MessageParserText < Test::Unit::TestCase
34
43
  assert_equal 'arg2', msg.params[1]
35
44
 
36
45
  # Action
37
- msg = Net::YAIL::MessageParser.new(':Nerdmaster!jeremy@nerdbucket.com PRIVMSG #bottest :\001ACTION gives Towelie a joint\001')
46
+ msg = Net::YAIL::MessageParser.new(":Nerdmaster!jeremy@nerdbucket.com PRIVMSG #bottest :\001ACTION gives Towelie a joint\001")
38
47
  assert_equal 'Nerdmaster', msg.nick
39
48
  assert_equal 'PRIVMSG', msg.command
40
49
  assert_equal '#bottest', msg.params.first
41
- assert_equal '\001ACTION gives Towelie a joint\001', msg.params.last
50
+ assert_equal "\001ACTION gives Towelie a joint\001", msg.params.last
42
51
 
43
52
  # Bot sets mode
44
53
  msg = Net::YAIL::MessageParser.new(':Towelie!~x2e521146@towelie.foo.bar MODE Towelie :+i')
@@ -67,5 +76,13 @@ class MessageParserText < Test::Unit::TestCase
67
76
  assert_equal '[|\|1]!~nerdmaste@nerd.nerdbucket.com', msg.prefix
68
77
  assert_equal 'Deadnerd', msg.params.shift
69
78
  assert_equal 0, msg.params.length
79
+
80
+ # Annoying topic change
81
+ msg = Net::YAIL::MessageParser.new(":Dude!dude@nerdbucket.com TOPIC #nerdtalk :31 August 2010 \357\277\275 Foo.")
82
+ assert_equal 'TOPIC', msg.command
83
+ assert_equal 'Dude', msg.nick
84
+ assert_equal "31 August 2010 \357\277\275 Foo.", msg.params.last
85
+ assert_equal '#nerdtalk', msg.params.first
86
+ assert_equal 'Dude!dude@nerdbucket.com', msg.prefix
70
87
  end
71
88
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 1
7
- - 3
8
- - 5
9
- version: 1.3.5
7
+ - 4
8
+ - 0
9
+ version: 1.4.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jeremy Echols
@@ -14,7 +14,7 @@ autorequire: net/yail
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-08-09 00:00:00 -04:00
17
+ date: 2010-09-02 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -25,8 +25,9 @@ executables: []
25
25
  extensions: []
26
26
 
27
27
  extra_rdoc_files:
28
- - README
28
+ - YAIL-RDOC
29
29
  files:
30
+ - examples/simple/dumbbot.rb
30
31
  - examples/logger/logger_bot.rb
31
32
  - examples/logger/default.yml
32
33
  - examples/logger/run.rb
@@ -37,10 +38,12 @@ files:
37
38
  - lib/net/yail/message_parser.rb
38
39
  - lib/net/yail/default_events.rb
39
40
  - lib/net/yail/output_api.rb
40
- - lib/net/yail/event_manager.rb
41
+ - lib/net/yail/event.rb
42
+ - lib/net/yail/yail-version.rb
41
43
  - tests/net_yail.rb
42
44
  - tests/tc_message_parser.rb
43
- - README
45
+ - tests/tc_event.rb
46
+ - YAIL-RDOC
44
47
  has_rdoc: true
45
48
  homepage: http://ruby-irc-yail.nerdbucket.com/
46
49
  licenses: []
@@ -48,7 +51,7 @@ licenses: []
48
51
  post_install_message:
49
52
  rdoc_options:
50
53
  - --main
51
- - README
54
+ - YAIL-RDOC
52
55
  require_paths:
53
56
  - lib
54
57
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -77,3 +80,4 @@ summary: "Yet Another IRC Library: wrapper for IRC communications in Ruby."
77
80
  test_files:
78
81
  - tests/net_yail.rb
79
82
  - tests/tc_message_parser.rb
83
+ - tests/tc_event.rb
data/README DELETED
@@ -1,67 +0,0 @@
1
- Rubyforge page: http://rubyforge.org/projects/ruby-irc-yail
2
-
3
- *Want to jump straight to the examples instead of reading this lame "about the project" doc? Just click on Net::YAIL !*
4
-
5
- Net::YAIL is a library built for dealing with IRC communications in Ruby.
6
- This is a project I've been building for about three years, based
7
- originally on the very messy initial release of IRCSocket (back when I first
8
- started, that was the only halfway-decent IRC lib I found). I've put a lot
9
- of time and effort into cleaning it up to make it better for my own uses,
10
- and now it's almost entirely my code.
11
-
12
- Some credit should also be given to Ruby-IRC, as I stole its eventmap.yml
13
- file with very minor modifications.
14
-
15
- This library may not be useful to everybody (or anybody other than myself,
16
- for that matter), and Ruby-IRC or another lib may work for your situation
17
- far better than this thing will, but the general design I built here has
18
- just felt more natural to me than the other libraries I've looked at since
19
- I started my project.
20
-
21
- Features of YAIL:
22
-
23
- * Allows event handlers to be specified very easily for all known IRC events,
24
- and except in a few rare cases one can choose to override the default
25
- handling mechanisms.
26
- * Allows handling outgoing messages, such as when privmsg is called. The API
27
- won't allow you to stop the outgoing message (though I may offer this if
28
- people want it), but you can filter data before it's sent out. This is one
29
- thing I didn't see anywhere else.
30
- * Threads for input and output are persistent. This is a feature, not a bug.
31
- Some may hate this approach, but I'm a total n00b to threads, and it seemed
32
- like the way to go, having thread loops responsible for their own piece of
33
- the library. I'd *love* input here if anybody can tell me why this is a bad
34
- idea....
35
- * "Stacked" event handling is possible if you want to provide a very modular
36
- framework of your own. When you prepend a handler, its return determines if
37
- the next handler will get called. This isn't useful for a simple bot most
38
- likely, but can have some utility in bigger projects where a single event
39
- may need to be dispatched to several handlers.
40
- * Easy to build a simple bot without subclassing anything. One gripe I had
41
- with IRCSocket was that it was painful to do anything without subclassing
42
- and overriding methods. No need here.
43
- * Lots of built-in reporting. You may hate this part, but for a bot, it's
44
- really handy to have most incoming data reported on some level. I may make
45
- this optional at some point, but only if people complain, since I haven't
46
- yet seen a need to do so....
47
- * Built-in PRIVMSG buffering! You can of course choose to not buffer, but by
48
- default you cannot send more than one message to a given target (user or
49
- channel) more than once per second. Additionally, this buffering method is
50
- ideal for a bot that's trying to be chatty on two channels at once, because
51
- buffering is per-target, so queing up 20 lines on <tt>##foo</tt> doesn't mean waiting
52
- 20 seconds to spit data out to <tt>##bar</tt>. The one caveat here is that if your
53
- app is trying to talk to too many targets at once, the buffering still won't
54
- save you from a flood-related server kick. If this is a problem for others,
55
- I'll look into building an even more awesome buffering system.
56
- * The included IRCBot is a great starting point for building your own bot,
57
- but if you want something even simpler, just look at Net::YAIL's documentation
58
- for the most basic working examples.
59
-
60
- I still have a lot to do, though. The output API is definitely not fully
61
- fleshed out. I believe that the library is also missing a lot for people
62
- who just have a different approach than me, since this was purely designed for
63
- my own benefit, and then released almost exclusively to piss off the people
64
- whose work I stole to get where I'm at today. (Just kiddin', Pope)
65
-
66
- This code is released under the MIT license. I hear it's all the rage with
67
- the kids these days.
File without changes