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 +51 -0
- data/examples/simple/dumbbot.rb +25 -0
- data/lib/net/yail.rb +104 -64
- data/lib/net/yail/default_events.rb +5 -0
- data/lib/net/yail/event.rb +285 -0
- data/lib/net/yail/magic_events.rb +1 -1
- data/lib/net/yail/message_parser.rb +5 -1
- data/lib/net/yail/output_api.rb +1 -1
- data/lib/net/yail/yail-version.rb +5 -0
- data/tests/net_yail.rb +1 -0
- data/tests/tc_event.rb +227 -0
- data/tests/tc_message_parser.rb +20 -3
- metadata +12 -8
- data/README +0 -67
- data/lib/net/yail/event_manager.rb +0 -0
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/
|
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
|
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
|
-
|
344
|
-
[@ioloop_thread, @input_processor, @privmsg_processor].each {|thread| thread.terminate if Thread === thread}
|
363
|
+
return unless Thread === @ioloop_thread
|
345
364
|
|
346
|
-
#
|
347
|
-
|
348
|
-
|
349
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
481
|
-
#
|
482
|
-
|
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
|
516
|
+
case event.type
|
490
517
|
# Ping is important to handle quickly, so it comes first.
|
491
|
-
when
|
492
|
-
handle(
|
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
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
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
|
520
|
-
handle(
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
when
|
526
|
-
handle(
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
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
|
@@ -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
|
-
|
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!
|
data/lib/net/yail/output_api.rb
CHANGED
@@ -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:
|
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
|
data/tests/net_yail.rb
CHANGED
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
|
data/tests/tc_message_parser.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
require File.dirname(__FILE__) + '/net_yail'
|
3
3
|
require 'test/unit'
|
4
4
|
|
5
|
-
class
|
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(
|
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
|
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
|
-
-
|
8
|
-
-
|
9
|
-
version: 1.
|
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-
|
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
|
-
-
|
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/
|
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
|
-
-
|
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
|
-
-
|
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
|