feedbot-utils.lisp
 1 ;;;; feedbot-utils.lisp
2
3 (in-package :feedbot)
4
5 (defmacro with-gensyms (syms &body body)
6 `(let ,(loop for s in syms collect `(,s (gensym)))
7 ,@body))
8
9 (defun sanitize-string (str)
10 "Remove non-printable characters from string, including vertical
11 whitespace and non-ASCII strings."
12 (let ((asciied-str (remove-if #'(lambda (c)
13 (or (< (char-code c) #x20)
14 (> (char-code c) #x7f)))
15 str)))
16 (string-trim '(#\Space #\Tab #\Newline) asciied-str)))
17
18 (defun try-parse-feed (url)
19 "Call `feedparse:parse-feed' and, if it returns without an error,
20 return the feed item. Otherwise, return NIL and its exception object as
21 multiple values."
22 (handler-case
23 (parse-feed url)
24 (t (c)
25 (format *error-output*
26 "Error fetching/parsing feed ~a: ~s.~%"
27 url (class-name (class-of c)))
28 (return-from try-parse-feed (values nil c)))))
29
30 (defun send-ison (bot nicks)
31 "Send ISON message from `bot' to set of `nicks'."
32 (labels ((add-nick (params nick)
33 (format nil "~a ~a" params nick))
34 (make-ison-params (nicklist)
35 (let ((ison-params (list ""))
36 (nicks (copy-list nicklist)))
37 ;; If we don't have any nicks to check, then return.
38 (when (null nicks)
39 (return-from make-ison-params))
40 ;; Make ISON parameters and push them in ison-params
41 (loop until (null nicks) do
42 (let* ((nick (pop nicks))
43 (newcar-ison-params (add-nick (car ison-params) nick)))
44 ;; When we either have nothing in ison-params or the
45 ;; list is too long, push a fresh list of
46 ;; parametes. 512 is a magic number given in RFC
47 ;; 1459.
48 (if (> (length newcar-ison-params) 512)
49 (push nick ison-params)
50 (setf (car ison-params) newcar-ison-params))))
51 ison-params)))
52 (loop for ison-param in (make-ison-params nicks) do
53 (ison (ircbot-connection bot) ison-param))))
54
55 (defun split-string-by (sep str)
56 "Split string `str' by character `sep', returning the result in a
57 list."
58 ;; Special case, empty string.
59 (when (string= "" str)
60 (return-from split-string-by))
61 ;; Otherwise, we "take" from `str' until we come to string without
62 ;; spaces.
63 (loop with source = str
64 with sink = nil
65 for pos = (position sep source)
66 until (null pos) do
67 (push (subseq source 0 pos) sink)
68 (setq source (subseq source (1+ pos)))
69 finally (return (nreverse (push source sink)))))
70
71 (defun parse-ison (ison-param)
72 "Parse ISON parameter, returning a list of nicks."
73 (remove "" (split-string-by #\Space ison-param)
74 :test #'string=))
75
76 (defun parse-arguments (arguments)
77 "Feedbot-specific code to parse Trilemabot `arguments'.
78
79 This returns the first argument passed to the bot and ignores
80 everything else."
81 (assert (stringp arguments))
82 (let* ((sanitized-args (string-trim '(#\Space #\Newline #\Tab) arguments))
83 (pos (position #\Space sanitized-args)))
84 (if (null pos)
85 sanitized-args
86 (subseq arguments 0 pos))))
87
88 (defun response-rcpt (bot src-message channel)
89 "Get the response-message recipient of `src-message'.
90
91 If the message was sent in a channel, then the recipient is that
92 channel. Otherwise, the sender's nickname is returned."
93 (let ((src (source src-message)))
94 (if (string= channel (ircbot-nick bot))
95 src
96 channel)))