PageRenderTime 84ms CodeModel.GetById 56ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/example/specbot.lisp

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