ircbot.lisp
  1 (in-package #:ircbot)
2
3 (defvar *max-lag* 60)
4 (defvar *ping-freq* 30)
5
6
7 (defclass ircbot ()
8 ((connection :accessor ircbot-connection :initform nil)
9 (channels :reader ircbot-channels :initarg :channels)
10 (server :reader ircbot-server :initarg :server)
11 (port :reader ircbot-port :initarg :port)
12 (nick :reader ircbot-nick :initarg :nick)
13 (password :reader ircbot-password :initarg :password)
14 (connection-security :reader ircbot-connection-security
15 :initarg :connection-security
16 :initform :none)
17 (run-thread :accessor ircbot-run-thread :initform nil)
18 (ping-thread :accessor ircbot-ping-thread :initform nil)
19 (lag :accessor ircbot-lag :initform nil)
20 (lag-track :accessor ircbot-lag-track :initform nil)))
21
22 (defmethod ircbot-check-nick ((bot ircbot) message)
23 (destructuring-bind (target msgtext) (arguments message)
24 (declare (ignore msgtext))
25 (if (string= target (ircbot-nick bot))
26 (ircbot-nickserv-auth bot)
27 (ircbot-nickserv-ghost bot))))
28
29 (defmethod ircbot-connect :around ((bot ircbot))
30 (let ((conn (connect :nickname (ircbot-nick bot)
31 :server (ircbot-server bot)
32 :port (ircbot-port bot)
33 :connection-security (ircbot-connection-security bot))))
34 (setf (ircbot-connection bot) conn)
35 (call-next-method)
36 (read-message-loop conn)))
37
38 (defmethod ircbot-connect ((bot ircbot))
39 (let ((conn (ircbot-connection bot)))
40 (add-hook conn 'irc-err_nicknameinuse-message (lambda (message)
41 (declare (ignore message))
42 (ircbot-randomize-nick bot)))
43 (add-hook conn 'irc-kick-message (lambda (message)
44 (declare (ignore message))
45 (map nil
46 (lambda (c) (join (ircbot-connection bot) c))
47 (ircbot-channels bot))))
48 (add-hook conn 'irc-notice-message (lambda (message)
49 (ircbot-handle-nickserv bot message)))
50 (add-hook conn 'irc-pong-message (lambda (message)
51 (ircbot-handle-pong bot message)))
52 (add-hook conn 'irc-rpl_welcome-message (lambda (message)
53 (ircbot-start-ping-thread bot)
54 (ircbot-check-nick bot message)))))
55
56 (defmethod ircbot-connect-thread ((bot ircbot))
57 (setf (ircbot-run-thread bot)
58 (sb-thread:make-thread (lambda () (ircbot-connect bot))
59 :name "ircbot-run")))
60
61 (defmethod ircbot-disconnect ((bot ircbot) &optional (quit-msg "..."))
62 (sb-sys:without-interrupts
63 (quit (ircbot-connection bot) quit-msg)
64 (setf (ircbot-lag-track bot) nil)
65 (setf (ircbot-connection bot) nil)
66 (if (not (null (ircbot-run-thread bot)))
67 (sb-thread:terminate-thread (ircbot-run-thread bot)))
68 (sb-thread:terminate-thread (ircbot-ping-thread bot))))
69
70 (defmethod ircbot-reconnect ((bot ircbot) &optional (quit-msg "..."))
71 (let ((threaded-p (not (null (ircbot-run-thread bot)))))
72 (ircbot-disconnect bot quit-msg)
73 (if threaded-p
74 (ircbot-connect-thread bot)
75 (ircbot-connect bot))))
76
77 (defmethod ircbot-handle-nickserv ((bot ircbot) message)
78 (let ((conn (ircbot-connection bot)))
79 (if (string= (host message) "services.")
80 (destructuring-bind (target msgtext) (arguments message)
81 (declare (ignore target))
82 (cond ((string= msgtext "This nickname is registered. Please choose a different nickname, or identify via /msg NickServ identify <password>.")
83 (ircbot-nickserv-auth bot))
84 ((string= msgtext (format nil "~A has been ghosted." (ircbot-nick bot)))
85 (nick conn (ircbot-nick bot)))
86 ((string= msgtext (format nil "~A is not online." (ircbot-nick bot)))
87 (ircbot-nickserv-auth bot))
88 ((string= msgtext (format nil "You are now identified for ~A." (ircbot-nick bot)))
89 (map nil (lambda (c) (join conn c)) (ircbot-channels bot))))))))
90
91 (defmethod ircbot-handle-pong ((bot ircbot) message)
92 (destructuring-bind (server ping) (arguments message)
93 (declare (ignore server))
94 (let ((response (ignore-errors (parse-integer ping))))
95 (when response
96 (setf (ircbot-lag-track bot) (delete response (ircbot-lag-track bot) :test #'=))
97 (setf (ircbot-lag bot) (- (received-time message) response))))))
98
99 (defmethod ircbot-nickserv-auth ((bot ircbot))
100 (privmsg (ircbot-connection bot) "NickServ"
101 (format nil "identify ~A" (ircbot-password bot))))
102
103 (defmethod ircbot-nickserv-ghost ((bot ircbot))
104 (privmsg (ircbot-connection bot) "NickServ"
105 (format nil "ghost ~A ~A" (ircbot-nick bot) (ircbot-password bot))))
106
107 (defmethod ircbot-randomize-nick ((bot ircbot))
108 (nick (ircbot-connection bot)
109 (format nil "~A-~A" (ircbot-nick bot) (+ (random 90000) 10000))))
110
111 (defmethod ircbot-send-message ((bot ircbot) target message-text)
112 (privmsg (ircbot-connection bot) target message-text))
113
114 (defmethod ircbot-start-ping-thread ((bot ircbot))
115 (let ((conn (ircbot-connection bot)))
116 (setf (ircbot-ping-thread bot)
117 (sb-thread:make-thread
118 (lambda ()
119 (loop
120 do (progn (sleep *ping-freq*)
121 (let ((ct (get-universal-time)))
122 (push ct (ircbot-lag-track bot))
123 (ping conn (princ-to-string ct))))
124 until (ircbot-timed-out-p bot))
125 (ircbot-reconnect bot))
126 :name "ircbot-ping"))))
127
128 (defmethod ircbot-timed-out-p ((bot ircbot))
129 (loop
130 with ct = (get-universal-time)
131 for v in (ircbot-lag-track bot)
132 when (> (- ct v) *max-lag*)
133 do (return t)))
134
135
136 (defun make-ircbot (server port nick password channel)
137 (make-instance 'ircbot
138 :server server
139 :port port
140 :nick nick
141 :password password
142 :channel channel))