birch

https://github.com/jorams/birch.git

git clone 'https://github.com/jorams/birch.git'

(ql:quickload :birch)
9

Birch

Build Status

Birch is a simple Common Lisp IRC client library. It makes use of CLOS for event handling.

Dependencies

Birch is built in Common Lisp on SBCL. It depends on:

The tests also use Prove (MIT).

Installation

Birch can be loaded with Quicklisp:

(ql:quickload :birch)

Usage

The first step is to create a subclass of CONNECTION:

(defclass my-connection (connection) ())

To connect to an IRC network you create an instance of your connection class. You then pass that instance to CONNECT to actually connect to the network.

(defvar *connection* (make-instance 'my-connection
                                    :server-host "irc.example.com"
                                    :nick "mybot"))

The only required initargs for the default connection class are :SERVER-HOST and :NICK. The others of interest are:

The accessors for all of those are the name of the slot (the initarg without the ':').

Event handling in Birch is done by defining methods on HANDLE-EVENT.

(defgeneric handle-event (connection event)
  (:documentation "Will be called after an IRC message has successfully been
                   parsed and turned into an event. Most IRC messages don't
                   result in events, should you want to handle them you can
                   define a method on HANDLE-MESSAGE instead."))

For example, to do something when a PRIVMSG is received, you define a method specializing the first argument on your connection class and the second on the PRIVMSG-EVENT class:

(defmethod handle-event ((connection my-connection) (event privmsg-event))
  (format t "Message received on ~A: ~A" (channel event) (message event)))

A list of all the events currently included in Birch is below.

Sending commands to a connection can be done in two ways. You can use the generic function /RAW, which is basically a glorified FORMAT, or you can use one of the built-in functions for sending often-used commands. They're all generic functions and can thus easily be extended.

RAW can, for example, be called like this:

(/raw connection "JOIN ~A" channel)

But you should probably just use the function /JOIN in this case.

All currently implemented commands are listed below.

To start handling messages you should call PROCESS-MESSAGE-LOOP, which will block until the connection to the server is closed. Even then, if /QUIT wasn't called (so the ACTIVEP slot on the connection wasn't set to NIL) PROCESS-MESSAGE-LOOP will try to reconnect. You'll likely want to run this in a new thread.

Alternatively, you can call PROCESS-MESSAGE yourself.

We have to go deeper

If you want to handle a message from the server for which there is no event you can instead define a method on HANDLE-MESSAGE.

(defgeneric handle-message (connection prefix command params)
  (:documentation "Called when a raw message is returned.
                   CONNECTION is the connection object of the connection the
                   message was received on.
                   PREFIX is a list of (NICK USER HOST)
                   COMMAND is a keyword, such as :PRIVMSG or :RPL_WELCOME
                   PARAMS is a list of parameters"))

For example, to do something when RPL_WELCOME is received, you could define a method like so:

(defmethod handle-message ((connection my-connection)
                           prefix
                           (command (eql :RPL_WELCOME))
                           params)
  (format t "Received RPL_WELCOME, we can now do stuff"))

The COMMAND argument will either be the keyword-ized name of the command as defined in RFC2812, like :PRIVMSG, or, in the case of numeric replies, the name as found here.

If you want to handle something as an event you can define a method on HANDLE-MESSAGE that calls HANDLE-EVENT with a newly initialized EVENT object. The macro DEFINE-EVENT-DISPATCHER can be of great help with that.

(defmacro define-event-dispatcher (command class &optional positional-initargs)
  "Defines a method on HANDLE-MESSAGE to handle messages of which the command
   is COMMAND. This new method will call HANDLE-EVENT with a new instance of
   type CLASS.

   POSITIONAL-INITARGS should be a list of initargs to pass to MAKE-INSTANCE,
   where the position of the keyword determines the IRC command parameter that
   will be used as a value. A NIL will cause an IRC parameter to be ignored.

   For example, when POSITIONAL-INITARGS is (:CHANNEL), the first parameter of
   the IRC message will be passed as the initial value of :CHANNEL.
   If POSITIONAL-INITARGS is (:CHANNEL :TARGET), the first parameter will be
   passed as the initial value of :CHANNEL, and the second parameter will be
   passed as the initial value of :TARGET.

   Instead of a keyword, an element of POSITIONAL-INITARGS can also be a list of
   the form (:KEYWORD FUNCTION), which means the value passed as the initarg
   will be the result of calling FUNCTION with two arguments: the connection
   object and the IRC parameter.

   Any remaining arguments will be joined together (separated by spaces) and
   passed as the initial value of :MESSAGE."
  ...)

For example, kick events are implemented like this:

(defclass kick-event (channel-event)
  ((target :initarg :target
           :initform NIL
           :accessor target)))

(define-event-dispatcher :KICK 'kick-event ((:channel #'make-channel)
                                            (:target #'make-user)))

Events

Commands

Users and Channels

Birch keeps track of users and channels for you. The default events call MAKE-USER and MAKE-CHANNEL on the appropriate parameters, turning them into USER and CHANNEL objects with appropriate slots. To get a list of all users in a channel, call USERS on the channel object. Similarly, to get all channels a user is in (that we know of), call CHANNELS on a user object.

It is possible to change the class instantiated by MAKE-USER and MAKE-CHANNEL, by passing the desired class name to the :user-class or :channel-class initargs when creating the instance. This setting can also be changed after the fact with the accessors USER-CLASS and CHANNEL-CLASS.

It is worth noting that CONNECTION is itself a subclass of USER and will appear in channel user lists.

CTCP

Birch provides two utility functions for working with CTCP messages.

Notes

lisp (defmethod handle-event :around ((connection my-connection) (event event)) (handler-case (if (next-method-p) (call-next-method)) (serious-condition (condition) (format *trace-output* "~&Caught error: ~A~%" condition))))

HANDLE-MESSAGE and HANDLE-EVENT use a modified standard method combination to allow for :AROUND methods to exist when there are no primary methods without signalling an (apparently implementation-defined) error.

License

Copyright (c) 2015 Joram Schrijver

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.