net-yail 1.3.5 → 1.4.0

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