(in-package #:ircbot) (defvar *max-lag* 60) (defvar *ping-freq* 30) (defclass ircbot () ((connection :accessor ircbot-connection :initform nil) (channels :reader ircbot-channels :initarg :channels) (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)) (map nil (lambda (c) (join (ircbot-connection bot) c)) (ircbot-channels 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))) (map nil (lambda (c) (join conn c)) (ircbot-channels 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))