net-yail 1.3.5 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/YAIL-RDOC +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
|