source: trunk/example/specbot.lisp

Last change on this file was 211, checked in by Erik Huelsmann, 16 years ago

Update specbot.lisp to conform to deprecation of trailing-arguments.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 7.5 KB
Line 
1;;;; $Id: specbot.lisp 211 2008-06-30 21:29:32Z ehuelsmann $
2;;;; $Source$
3
4;;;; specbot.lisp - an example IRC bot for cl-irc
5
6;;; specbot is an example IRC bot for cl-irc. It runs on
7;;; irc.freenode.net in the channels #lisp, #scheme and #clhs
8;;; (preferred for testing). It responds to queries of its various
9;;; databases, which right now include "clhs" and "r5rs".
10
11;;; You will need to load and populate the tables for both the clhs
12;;; and r5rs lookup packages; currently these are available in
13;;; lisppaste CVS.
14
15;;; To use it, load the cl-irc system, load specbot.lisp, and
16;;; invoke (specbot:start-specbot "desirednickname" "desiredserver"
17;;; "#channel1" "#channel2" "#channel3" ...)
18
19(defpackage :specbot (:use :common-lisp :irc) (:export :start-specbot
20                                                       :shut-up
21                                                       :un-shut-up))
22(in-package :specbot)
23
24(defvar *connection*)
25(defvar *nickname* "")
26
27(defun shut-up ()
28  (setf (irc:client-stream *connection*) (make-broadcast-stream)))
29
30(defun un-shut-up ()
31  (setf (irc:client-stream *connection*) *trace-output*))
32
33(defmacro aif (test conseq &optional (else nil))
34  `(let ((it ,test))
35     (if it ,conseq
36       (symbol-macrolet ((it ,test))
37         ,else))))
38
39(defun clhs-lookup (str)
40  (and (find-package :clhs-lookup)
41       (funcall (intern "SPEC-LOOKUP" :clhs-lookup)
42                str)))
43
44(defun r5rs-lookup (str)
45  (and (find-package :r5rs-lookup)
46       (funcall (intern "SYMBOL-LOOKUP" :r5rs-lookup)
47                str)))
48
49(defun cocoa-lookup (str)
50  (and (find-package :cocoa-lookup)
51       (funcall (intern "SYMBOL-LOOKUP" :cocoa-lookup)
52                str)))
53
54(defun elisp-lookup (str)
55  (and (find-package :elisp-lookup)
56       (funcall (intern "SYMBOL-LOOKUP" :elisp-lookup)
57                str)))
58
59(defun clim-lookup (str)
60  (and (find-package :clim-lookup)
61       (funcall (intern "TERM-LOOKUP" :clim-lookup)
62                str)))
63
64(defvar *spec-providers*
65  '((clhs-lookup "clhs" "The Common Lisp HyperSpec")
66    (r5rs-lookup "r5rs" "The Revised 5th Ed. Report on the Algorithmic Language Scheme")
67    (cocoa-lookup "cocoa" "Classes in the Cocoa Foundation and Application kits")
68    (elisp-lookup "elisp" "GNU Emacs Lisp Reference Manual")
69    (clim-lookup "clim" "Common Lisp Interface Manager II Specification")))
70
71(defvar *spaces-allowed*
72  '(clim-lookup))
73
74(defvar *alists* nil)
75
76(defun add-simple-alist-lookup (file designator prefix description)
77  (unless (assoc designator *alists*)
78    (let ((alist (with-open-file (s file :direction :input) (read s))))
79      (push (cons designator alist) *alists*)
80      (setf *spec-providers*
81            (nconc *spec-providers*
82                   (list `((simple-alist-lookup ,designator) ,prefix ,description)))))))
83
84(defun simple-alist-lookup (designator string)
85  (let ((alist (cdr (assoc designator *alists*))))
86    (cdr (assoc string alist :test #'equalp))))
87
88(defun valid-message (string prefix &key space-allowed)
89  (if (eql (search prefix string :test #'char-equal) 0)
90      (and (or space-allowed
91               (not (find #\space string :start (length prefix))))
92           (length prefix))
93      nil))
94
95(defun strip-address (string &key (address *nickname*) (final nil))
96  (loop for i in (list (format nil "~A " address)
97                       (format nil "~A: " address)
98                       (format nil "~A:" address)
99                       (format nil "~A, " address))
100        do (aif (valid-message string i :space-allowed t)
101                (return-from strip-address (subseq string it))))
102  (and (not final) string))
103
104(defun msg-hook (message)
105  (let ((destination (if (string-equal (first (arguments message)) *nickname*)
106                         (source message)
107                         (first (arguments message))))
108        (to-lookup (strip-address (car (last (arguments message))))))
109    (if (and (or
110              (string-equal (first (arguments message)) *nickname*)
111              (not (string= to-lookup (car (last (arguments message))))))
112             (member to-lookup '("help" "help?") :test #'string-equal))
113        (progn
114          (privmsg *connection* destination
115                   (format nil "To use the ~A bot, say something like \"database term\", where database is one of (~{~S~^, ~}) and term is the desired lookup. The available databases are:"
116                           *nickname*
117                           (mapcar #'second *spec-providers*)))
118          (loop for i from 1 for j in *spec-providers*
119                with elts = nil
120                do (push j elts)
121                if (zerop (mod i 4))
122                do (progn
123                     (privmsg *connection* destination
124                              (format nil "~{~{~*~S, ~A~}~^; ~}"
125                                      (nreverse elts)))
126                     (setf elts nil)))
127          )
128        (loop for type in *spec-providers*
129              for actual-fun = (if (typep (first type) 'symbol)
130                                   (first type)
131                                   (lambda (lookup) (destructuring-bind (fun first-arg) (first type)
132                                                      (funcall fun first-arg lookup))))
133              do
134              (aif (strip-address to-lookup :address (second type) :final t)
135                   (let ((looked-up (funcall actual-fun it)))
136                     (if (and (<= 0 (count #\space it)
137                                  (if (member actual-fun *spaces-allowed*) 1 0)1)
138                              (not looked-up))
139                         (setf looked-up (format nil "Sorry, I couldn't find anything for ~A."  it)))
140                     (and looked-up
141                          (privmsg *connection* destination looked-up))))))))
142
143(defparameter *754-file*
144  (merge-pathnames "754.lisp-expr"
145                   (make-pathname
146                    :directory
147                    (pathname-directory
148                     (or *load-truename*
149                         *default-pathname-defaults*)))))
150
151(defparameter *ppc-file*
152  (merge-pathnames "ppc-assem.lisp-expr"
153                   (make-pathname
154                    :directory
155                    (pathname-directory
156                     (or *load-truename*
157                         *default-pathname-defaults*)))))
158
159(defparameter *sus-file*
160  (merge-pathnames "sus.lisp-expr"
161                   (make-pathname
162                    :directory
163                    (pathname-directory
164                     (or *load-truename*
165                         *default-pathname-defaults*)))))
166
167(defparameter *man-file*
168  (merge-pathnames "man.lisp-expr"
169                   (make-pathname
170                    :directory
171                    (pathname-directory
172                     (or *load-truename*
173                         *default-pathname-defaults*)))))
174
175(defun start-specbot (nick server &rest channels)
176  (add-simple-alist-lookup *754-file* 'ieee754 "ieee754" "Section numbers of IEEE 754")
177  (add-simple-alist-lookup *ppc-file* 'ppc "ppc" "PowerPC assembly mnemonics")
178  (add-simple-alist-lookup *sus-file* 'sus "posix" "Single UNIX Specification")
179  (add-simple-alist-lookup *man-file* 'man "man" "Mac OS X Man Pages")
180  (setf *nickname* nick)
181  (setf *connection* (connect :nickname *nickname* :server server))
182  (mapcar #'(lambda (channel) (join *connection* channel)) channels)
183  (add-hook *connection* 'irc::irc-privmsg-message 'msg-hook)
184  #+(or sbcl
185        openmcl)
186  (start-background-message-handler *connection*)
187  #-(or sbcl
188        openmcl)
189  (read-message-loop *connection*))
190
191(defun shuffle-hooks ()
192  (irc::remove-hooks *connection* 'irc::irc-privmsg-message)
193  (add-hook *connection* 'irc::irc-privmsg-message 'msg-hook))
Note: See TracBrowser for help on using the repository browser.