Class Net::YAIL
In: lib/net/yail/message_parser.rb
lib/net/yail.rb
Parent: Object

This library is based on the initial release of IRCSocket with a tiny bit of plagarism of Ruby-IRC.

My aim here is to build something that is still fairly simple to use, but powerful enough to build a decent IRC program.

This is far from complete, but it does successfully power a relatively complicated bot, so I believe it‘s solid and "good enough" for basic tasks.

Events

  • Register handlers by calling prepend_handler(symbol, method)
  • Events based on incoming data are represented by :incoming_*, while outgoing are :outgoing_*
  • I‘m still using the names from IRCSocket dev(s), so this means an incoming message would call the :incoming_msg handler, and a message being sent would call the :outgoing_msg handler.

Incoming Events

Current list of incoming events and the parameters sent to the handler:

  • :incoming_any(raw) - "global" handler that catches all events and may modify their data as necessary before the real handler is hit. This only be used in cases where it‘s necessary to grab a lot of events that also need to be processed elsewhere, such as doing input filtering for all events that have a "text" element. Note that returning true from this handler will still break the entire chain of event handling.
  • :incoming_msg(fullactor, actor, target, text) - Normal message from actor to target
  • :incoming_act(fullactor, actor, target, text) - CTCP "action" (emote) from actor to target
  • :incoming_invite(fullactor, actor, target, text) - INVITE to target channel from actor
  • :incoming_ctcp(fullactor, actor, target, text) - CTCP other than "action" from actor to target
  • :incoming_ctcpreply(fullactor, actor, target, text) - CTCP NOTICE from actor to target
  • :incoming_notice(fullactor, actor, target, text) - other NOTICE from actor to target
  • :incoming_mode(fullactor, actor, target, modes, objects) - actor sets modes on objects in target channel
  • :incoming_join(fullactor, actor, target) - actor joins target channel
  • :incoming_part(fullactor, actor, target, text) - actor leaves target with message in text
  • :incoming_kick(fullactor, actor, target, object, text) - actor kicked object from target with reason ‘text‘
  • :incoming_quit(fullactor, actor, text) - actor left server completely with reason ‘text‘
  • :incoming_nick(fullactor, actor, nickname) - actor changed to nickname
  • :incoming_ping(text) - ping from server with given text
  • :incoming_miscellany(line) - text from server didn‘t match anything known
  • :incoming_welcome(text, args) - raw 001 from server, means we successfully logged in
  • :incoming_bannedfromchan(text, args) - banned from channel
  • Anything else in the eventmap.yml file with params(text, args).

Common parameter elements:

  • fullactor: Rarely needed, full text of origin of an action
  • actor: Nickname of originator of an action
  • target: Nickname for private actions, channel name for public
  • text: Actual message/emote/notice/etc
  • args: For numeric handlers, this is a hash of :fullactor, :actor, and :target. Most numeric handlers I‘ve built don‘t need this, so I made it easier to just get what you specifically want.

Outgoing Events

Generally speaking, you won‘t need these very often, but they‘re here for the edge cases all the same. Note that the socket output cannot be skipped (see "Return value from events" below), so this is truly just to allow modifying things before they go out (filtering speech, converting or stripping markup, etc) or just general stats-type logic.

Note that in all cases below, the client is about to perform an action. Text can be filtered, things can be logged, but keep in mind that the action has not yet happened.

Events:

  • :outgoing_begin_connection(username, address, realname) - called when the start_listening method has set up all threading and such. Default behavior is to call user() and nick()
  • :outgoing_privmsg(target, text) - Any kind of PRIVMSG output is about to get sent out
  • :outgoing_msg(target, text) - Hit by a direct call to msg, which is normally used for "plain" messages, but a "clever" user could do their own CTCP messages here as well. Shoot them if they do.
  • :outgoing_ctcp(target, text) - All CTCP messages hit here eventually
  • :outgoing_act(target, text) - ACTION CTCP messages should go through this, not manually use ctcp.
  • :outgoing_notice(target, text) - All NOTICE messages hit here
  • :outgoing_ctcpreply(target, text) - CTCP NOTICE messages
  • :outgoing_mode(target, modes, objects) - Sets or queries mode. If modes is present, sends mode list to target. Objects would be users.
  • :outgoing_join(target, pass) - About to attempt to join target channel with given password (pass is ’’ if not specified in the join() command)
  • :outgoing_part(target, text) - The given target channel is about to be left, with optional text reason.
  • :outgoing_quit(text) - The client is about to quit, with optional text reason.
  • :outgoing_nick(new_nick) - The client is about to change nickname
  • :outgoing_user(username, myaddress, address, realname) - We‘re about to send a USER command.
  • :outgoing_pass(password) - The client is about to send a password to the server via PASS.
  • :outgoing_oper(user, password) - The client is about to request ops from the server via OPER.
  • :outgoing_topic(channel, new_topic) - If new_topic is blank (nil or ’’), the client is requesting the channel‘s topic, otherwise setting it.
  • :outgoing_names(channel) - Client is requesting a list of names in the given channel, or all channels and names if channel is blank.
  • :outgoing_list(channel, server) - Client is querying channel information. I honestly don‘t know what server is for from RFC, but asking for a specific channel gives just data on that channel.
  • :outgoing_invite(nick, channel) - Client is sending an INVITE message to nick for channel.
  • :outgoing_kick(nick, channel, comment) - Client is about to kick the user from the channel with an optional comment.

Note that a single output call can hit multiple handlers, so you must plan carefully. A call to act() will hit the act handler, then ctcp (since act is a type of ctcp message), then privmsg.

Custom Events

Yes, you can register your own wacky event handlers if you like, and have your code call them. Just register a handler with some funky name of your own design (avoid the prefixes :incoming and :outgoing for obvious reasons), and so long as something calls that handler, your handler method will get its data.

This isn‘t likely useful for a simple program, but for a subclass or wrapper of the IRC class, having the ability to give its users new events without mucking up this class can be helpful. For instance, see IRCBot#irc_loop and the :irc_loop event. If one wants their bot to do something regularly, they just handle that event and get frequent calls.

Return value from events

The return can be critical - a true value tells the handlers to stop their chain (true = "yes, I handled this event, stay the frak away you other, lesser handlers!), so no other handlers will be called.

Note that critical handlers (incoming ping, welcome, and nick change) cannot be overwritten as they actually run before user-defined handlers, and output handlers are just for filtering and cannot stop the socket from sending its data. If you want to change that low-level stuff, you should subclass, modify the code directly, monkey-patch, or just write your own library.

When should you return false from an event? Generally any time you have a handler that really needs to report itself. Unless you have multiple layers of handlers for a given event, there‘s little reason to worry about breaking the chain of events. Since handlers are prepended to the list, anybody subclassing your code can override your events, not the other way around. The main use is if you have multiple handlers for a single complex event, where you want each handler to do its own set process and pass on the event if it isn‘t resposible for that particular situation. Allows complex interactions to be made a bit cleaner, theoretically.

Simple example

For a program to do anything useful, it must instantiate an object with useful data and register some handlers:

  require 'rubygems'
  require 'net/yail'

  irc = Net::YAIL.new(
    :address    => 'irc.someplace.co.uk',
    :username   => 'Frakking Bot',
    :realname   => 'John Botfrakker',
    :nicknames  => ['bot1', 'bot2', 'bot3']
  )

  irc.prepend_handler :incoming_welcome, proc {|text, args|
    irc.join('#foo')
    return false
  }

  irc.start_listening
  while irc.dead_socket == false
    # Avoid major CPU overuse by taking a very short nap
    sleep 0.05
  end

Now we‘ve built a simple IRC listener that will connect to a (probably invalid) network, identify itself, and sit around waiting for the welcome message. After this has occurred, we join a channel and return false.

One could also define a method instead of a proc:

  require 'rubygems'
  require 'net/yail'

  def welcome(text, args)
    @irc.join('#channel')
    return false
  end

  irc = Net::YAIL.new(
    :address    => 'irc.someplace.co.uk',
    :username   => 'Frakking Bot',
    :realname   => 'John Botfrakker',
    :nicknames  => ['bot1', 'bot2', 'bot3']
  )

  irc.prepend_handler :incoming_welcome, method(:welcome)
  irc.start_listening
  while irc.dead_socket == false
    # Avoid major CPU overuse by taking a very short nap
    sleep 0.05
  end

Better example

See the included logger bot (under the examples directory of this project) for use of the IRCBot base class. It‘s a fully working bot example with real-world use.

Methods

Included Modules

Net::IRCEvents::Magic Net::IRCEvents::Defaults Net::IRCOutputAPI

Classes and Modules

Class Net::YAIL::MessageParser

Constants

VERSION = '1.3.2'

Attributes

dead_socket  [R] 
loud  [RW] 
me  [R] 
nicknames  [R] 
registered  [R] 
silent  [RW] 
socket  [R] 
throttle_seconds  [RW] 

Public Class methods

Makes a new instance, obviously.

Note: I haven‘t done this everywhere, but for the constructor, I felt it needed to have hash-based args. It‘s just cleaner to me when you‘re taking this many args.

Options:

  • :address: Name/IP of the IRC server
  • :port: Port number, defaults to 6667
  • :username: Username reported to server
  • :realname: Real name reported to server
  • :nicknames: Array of nicknames to cycle through
  • :silent: Don‘t report output messages from this object, defaults to false
  • :loud: Report a whole lot of stuff that‘s normally silenced and is generally very annoying. Defaults to false, thankfully.
  • :throttle_seconds: Seconds between a cycle of privmsg sends. Defaults to 1. One "cycle" is defined as sending one line of output to all targets that have output buffered.
  • :server_password: Very optional. If set, this is the password sent out to the server before USER and NICK messages.

Public Instance methods

Handles the given event (if it‘s in the @handlers array) with the arguments specified.

The @handlers must be a hash where key = event to handle and value is a Proc object (via Class.method(:name) or just proc {…}). This should be fine if you‘re setting up handlers with the prepend_handler method, but if you get "clever," you‘re on your own.

Since numerics are so many and so varied, this method will auto-fallback to a simple report if no handler was defined.

Event handler hook. Kinda hacky. Calls your event(s) before the default event. Default stuff will happen if your handler doesn‘t return true.

Reports may not get printed in the proper order since I scrubbed the IRCSocket report capturing, but this is way more straightforward to me.

Starts listening for input and builds the perma-threads that check for input, output, and privmsg buffering.

Kills and clears all threads. See note above about my lack of knowledge regarding threads. Please help me if you know how to make this system better. DEAR LORD HELP ME IF YOU CAN!

[Validate]