diff -uNr a/ircbot/INSTALL b/ircbot/INSTALL --- a/ircbot/INSTALL false +++ b/ircbot/INSTALL b33e7eedd225c3d21173616cb50ed7e8073b1ed97306d0bad5f5518ad8907fe8397fa5639d8a01927b42afaf434f02809b7db53cfa00101aca550a69becd81fd @@ -0,0 +1,19 @@ +INSTALL + + * Install SBCL (with sb-thread) and Quicklisp. + + * From the SBCL REPL: + (ql:quickload :cl-irc) + + * Use V to press `ircbot` + +mkdir -p ~/src/ircbot +cd ~/src/ircbot + +mkdir .wot +cd .wot && wget http://trinque.org/trinque.asc && cd .. + +v.pl init http://trinque.org/src/ircbot +v.pl press ircbot-genesis ircbot-genesis.vpatch + +ln -s ~/src/ircbot/ircbot-genesis ~/quicklisp/local-projects/ircbot diff -uNr a/ircbot/README b/ircbot/README --- a/ircbot/README false +++ b/ircbot/README e3d9ae5ab03f51650c888823bb68ecf373d4056f481f6eabf9b10a60e65083dc9b5b2827850568ddec48890b8385f5cd16d647d2d034e1d5c1ff8293e7ea94ba @@ -0,0 +1,8 @@ +README + +`ircbot` provides a simple CLOS class, `ircbot`, which will maintain a +connection to a single IRC channel via `cl-irc`. The bot will handle +ping/pong and detect failed connections, and is capable of +authenticating with NickServ (using ghost when necessary to +reacquire nick). + diff -uNr a/ircbot/USAGE b/ircbot/USAGE --- a/ircbot/USAGE false +++ b/ircbot/USAGE e6091b4d7ad0f92eba4e8c506e2eea56b5468098fecc9e5f51aac69521e94e80d805873018492d62d3e5279e410bbd2c5cc498b36c0d9f880699bd914e883b2f @@ -0,0 +1,14 @@ +USAGE + +(asdf:load-system :ircbot) +(defvar *bot*) +(setf *bot* + (ircbot:make-ircbot + "chat.freenode.net" 6667 "nick" "password" "#channel")) + +; connect in separate thread, returning thread +(ircbot:ircbot-connect-thread *bot*) + +; or connect using the current thread +; (ircbot:ircbot-connect *bot*) + diff -uNr a/ircbot/ircbot.asd b/ircbot/ircbot.asd --- a/ircbot/ircbot.asd false +++ b/ircbot/ircbot.asd 34ae3443c5e24267b38f4a6396db58c00e159b9e35f70281fe420e0ae1d7f4791e290a8c67ec5cef84c97aeb36715769b2abbbf4488ee91643dd46f10137a2f0 @@ -0,0 +1,10 @@ +;;;; ircbot.asd + +(asdf:defsystem #:ircbot + :description "ircbot" + :author "Michael Trinque " + :license "http://trilema.com/2015/a-new-software-licensing-paradigm/" + :depends-on (#:cl-irc) + :components ((:file "package") + (:file "ircbot"))) + diff -uNr a/ircbot/ircbot.lisp b/ircbot/ircbot.lisp --- a/ircbot/ircbot.lisp false +++ b/ircbot/ircbot.lisp a591af341ff436f6a3391aa5163f6bc366358b719427154f88815d87213798c92eae4923e6be147b92c04562619564d7a64ad9ac51ca08e5165b0b4b3da9813a @@ -0,0 +1,141 @@ +(in-package #:ircbot) + +(defvar *max-lag* 60) +(defvar *ping-freq* 30) + + +(defclass ircbot () + ((connection :accessor ircbot-connection :initform nil) + (channel :reader ircbot-channel :initarg :channel) + (server :reader ircbot-server :initarg :server) + (port :reader ircbot-port :initarg :port) + (nick :reader ircbot-nick :initarg :nick) + (password :reader ircbot-password :initarg :password) + (connection-security :reader ircbot-connection-security + :initarg :connection-security + :initform :none) + (run-thread :accessor ircbot-run-thread :initform nil) + (ping-thread :accessor ircbot-ping-thread :initform nil) + (lag :accessor ircbot-lag :initform nil) + (lag-track :accessor ircbot-lag-track :initform nil))) + +(defmethod ircbot-check-nick ((bot ircbot) message) + (destructuring-bind (target msgtext) (arguments message) + (declare (ignore msgtext)) + (if (string= target (ircbot-nick bot)) + (ircbot-nickserv-auth bot) + (ircbot-nickserv-ghost bot)))) + +(defmethod ircbot-connect :around ((bot ircbot)) + (let ((conn (connect :nickname (ircbot-nick bot) + :server (ircbot-server bot) + :port (ircbot-port bot) + :connection-security (ircbot-connection-security bot)))) + (setf (ircbot-connection bot) conn) + (call-next-method) + (read-message-loop conn))) + +(defmethod ircbot-connect ((bot ircbot)) + (let ((conn (ircbot-connection bot))) + (add-hook conn 'irc-err_nicknameinuse-message (lambda (message) + (declare (ignore message)) + (ircbot-randomize-nick bot))) + (add-hook conn 'irc-kick-message (lambda (message) + (declare (ignore message)) + (join (ircbot-connection bot) + (ircbot-channel bot)))) + (add-hook conn 'irc-notice-message (lambda (message) + (ircbot-handle-nickserv bot message))) + (add-hook conn 'irc-pong-message (lambda (message) + (ircbot-handle-pong bot message))) + (add-hook conn 'irc-rpl_welcome-message (lambda (message) + (ircbot-start-ping-thread bot) + (ircbot-check-nick bot message))))) + +(defmethod ircbot-connect-thread ((bot ircbot)) + (setf (ircbot-run-thread bot) + (sb-thread:make-thread (lambda () (ircbot-connect bot)) + :name "ircbot-run"))) + +(defmethod ircbot-disconnect ((bot ircbot) &optional (quit-msg "...")) + (sb-sys:without-interrupts + (quit (ircbot-connection bot) quit-msg) + (setf (ircbot-lag-track bot) nil) + (setf (ircbot-connection bot) nil) + (if (not (null (ircbot-run-thread bot))) + (sb-thread:terminate-thread (ircbot-run-thread bot))) + (sb-thread:terminate-thread (ircbot-ping-thread bot)))) + +(defmethod ircbot-reconnect ((bot ircbot) &optional (quit-msg "...")) + (let ((threaded-p (not (null (ircbot-run-thread bot))))) + (ircbot-disconnect bot quit-msg) + (if threaded-p + (ircbot-connect-thread bot) + (ircbot-connect bot)))) + +(defmethod ircbot-handle-nickserv ((bot ircbot) message) + (let ((conn (ircbot-connection bot))) + (if (string= (host message) "services.") + (destructuring-bind (target msgtext) (arguments message) + (declare (ignore target)) + (cond ((string= msgtext "This nickname is registered. Please choose a different nickname, or identify via /msg NickServ identify .") + (ircbot-nickserv-auth bot)) + ((string= msgtext (format nil "~A has been ghosted." (ircbot-nick bot))) + (nick conn (ircbot-nick bot))) + ((string= msgtext (format nil "~A is not online." (ircbot-nick bot))) + (ircbot-nickserv-auth bot)) + ((string= msgtext (format nil "You are now identified for ~A." (ircbot-nick bot))) + (join conn (ircbot-channel bot)))))))) + +(defmethod ircbot-handle-pong ((bot ircbot) message) + (destructuring-bind (server ping) (arguments message) + (declare (ignore server)) + (let ((response (ignore-errors (parse-integer ping)))) + (when response + (setf (ircbot-lag-track bot) (delete response (ircbot-lag-track bot) :test #'=)) + (setf (ircbot-lag bot) (- (received-time message) response)))))) + +(defmethod ircbot-nickserv-auth ((bot ircbot)) + (privmsg (ircbot-connection bot) "NickServ" + (format nil "identify ~A" (ircbot-password bot)))) + +(defmethod ircbot-nickserv-ghost ((bot ircbot)) + (privmsg (ircbot-connection bot) "NickServ" + (format nil "ghost ~A ~A" (ircbot-nick bot) (ircbot-password bot)))) + +(defmethod ircbot-randomize-nick ((bot ircbot)) + (nick (ircbot-connection bot) + (format nil "~A-~A" (ircbot-nick bot) (+ (random 90000) 10000)))) + +(defmethod ircbot-send-message ((bot ircbot) target message-text) + (privmsg (ircbot-connection bot) target message-text)) + +(defmethod ircbot-start-ping-thread ((bot ircbot)) + (let ((conn (ircbot-connection bot))) + (setf (ircbot-ping-thread bot) + (sb-thread:make-thread + (lambda () + (loop + do (progn (sleep *ping-freq*) + (let ((ct (get-universal-time))) + (push ct (ircbot-lag-track bot)) + (ping conn (princ-to-string ct)))) + until (ircbot-timed-out-p bot)) + (ircbot-reconnect bot)) + :name "ircbot-ping")))) + +(defmethod ircbot-timed-out-p ((bot ircbot)) + (loop + with ct = (get-universal-time) + for v in (ircbot-lag-track bot) + when (> (- ct v) *max-lag*) + do (return t))) + + +(defun make-ircbot (server port nick password channel) + (make-instance 'ircbot + :server server + :port port + :nick nick + :password password + :channel channel)) diff -uNr a/ircbot/package.lisp b/ircbot/package.lisp --- a/ircbot/package.lisp false +++ b/ircbot/package.lisp 0399ee3f6cfff1dc2c8e3ca17095499543356e440307d88e5d555ec981eb6d95a3c0003543039d0602fdb17ff843d9b8a6b7637d5239f2f1da001a12f8efda8d @@ -0,0 +1,19 @@ +;;;; package.lisp + +(defpackage :ircbot + (:use :cl + :cl-irc) + (:export :make-ircbot + :ircbot + :ircbot-connect + :ircbot-connect-thread + :ircbot-disconnect + :ircbot-reconnect + :ircbot-connection + :ircbot-channel + :ircbot-send-message + :ircbot-server + :ircbot-port + :ircbot-nick + :ircbot-lag)) +