--- /dev/null
+---
+postid: 000
+title: Feedbot [iii]: the IRC bot
+date: April 25, 2019
+author: Lucian Mogoșanu
+tags: tech, tmsr
+---
+
+All the basic building blocks pertaining to RSS functionality --
+i.e. the [checker][feedbot-i] and the [announcer][feedbot-ii] -- being
+in place, we conclude the [Feedbot][feedbot] series with:
+
+* The [V patch][feedbot-irc.vpatch]; and
+* my [seal][feedbot-irc.vpatch.spyked.sig].
+
+The patch in this episode introduces the following changes:
+
+* Some methods are removed, due to their retrospective uselessness;
+ some methods are added, due to them being found useful for both IRC
+ implementation and maintenance operations.
+* Auxiliary functions are provided in order to handle IRC operation
+ and Feedbot command argument parsing.
+* Most importantly, functionality is added to: manage the bot state;
+ glue to Ircbot and adapt the announcer to IRC; and implement
+ [Trilemabot][trilemabot] commands.
+
+The remainder of this post will detail the last bullet above. The
+reader is however encouraged to read the [V patch][feedbot-irc.vpatch]
+in its entirety, to get a broader image of the changes.
+
+**VII. State management**
+
+Feedbot state is a list of the form:
+
+~~~~ {.commonlisp}
+(feed-db msg-queue)
+~~~~
+
+where `feed-db` is a feed db and `msg-queue` is a message queue.
+
+Feedbot state is kept persistent using principally the feedbot
+`save-state` and `load-state` methods:
+
+~~~~ {.commonlisp}
+(defmethod feedbot-save-state ((bot feedbot) &optional (path "state.sexp"))
+ "Save bot state to disk location given by `path'."
+ (let ((feed-db (with-feed-db (feed-db) bot feed-db))
+ (msg-queue (with-msg-queue (msg-queue) bot msg-queue)))
+ (with-open-file (out path
+ :direction :output
+ :if-does-not-exist :create
+ :if-exists :supersede)
+ (write (list feed-db msg-queue) :stream out)
+ nil)))
+
+(defmethod feedbot-load-state ((bot feedbot) &optional (path "state.sexp"))
+ "Load bot state from disk location given by `path'.
+
+This method throws an error condition if the bot contains pre-existing
+state, i.e. `feed-db' and `msg-queue' are non-NIL."
+ (with-slots (feed-db msg-queue) bot
+ (assert (not feed-db))
+ (assert (not msg-queue)))
+ (feedbot-reload-state bot path))
+~~~~
+
+**VIII. Ircbot glue**
+
+IRC glue:
+
+**a\.** implements [entry announcer][announcer] functionality:
+
+~~~~ {.commonlisp}
+(defun announce-irc! (bot msg)
+ (announce-stdout! msg)
+ (let ((rcpt (get-msg-to! msg))
+ (entry (get-msg-entry! msg)))
+ (ircbot-send-message bot rcpt
+ (format nil "~a << ~a -- ~a~%"
+ (get-entry-link entry)
+ (get-msg-feed-title! msg)
+ (get-entry-title entry)))))
+~~~~
+
+**b\.** starts the checker and announcer threads on rpl_welcome:
+
+~~~~ {.commonlisp}
+(defun feedbot-rpl_welcome (bot message)
+ (declare (ignore message))
+ (feedbot-start-checker-thread bot)
+ (feedbot-start-announcer-thread bot))
+~~~~
+
+**c\.** sends messages to online nicks on rpl_ison
+
+~~~~ {.commonlisp}
+(defun feedbot-rpl_ison (bot message)
+ ;; Only when our reply contains some nicks...
+ (when (cdr (arguments message))
+ (let ((nicks (parse-ison (cadr (arguments message)))))
+ ;; Process messages...
+ (feedbot-process-msg-queue
+ bot #'(lambda (msg)
+ ;; Only when msg not send and :to is online...
+ (when (and (not (get-msg-sent! msg))
+ (member (get-msg-to! msg) nicks
+ :test #'string=))
+ ;; Wait a bit
+ (sleep *announce-delay*)
+ ;; Announce and mark as sent.
+ (announce-irc! bot msg)
+ (set-msg-sent! msg)))))))
+~~~~
+
+**d\.** implements ircbot-{connect,disconnect} routines
+
+~~~~ {.commonlisp}
+(defmethod ircbot-connect :after ((bot feedbot))
+ (feedbot-load-state bot)
+ (let ((conn (ircbot-connection bot)))
+ (add-hook conn 'irc-rpl_welcome-message
+ #'(lambda (message)
+ (feedbot-rpl_welcome bot message)))
+ (add-hook conn 'irc-rpl_ison-message
+ #'(lambda (message)
+ (feedbot-rpl_ison bot message)))))
+
+(defmethod ircbot-disconnect :after ((bot feedbot)
+ &optional (quit-msg "feedbot out"))
+ (declare (ignore quit-msg))
+ (with-slots (db-mutex queue-mutex checker-thread announcer-thread) bot
+ (ignore-errors
+ (release-mutex db-mutex :if-not-owner :force)
+ (release-mutex queue-mutex :if-not-owner :force)
+ (terminate-thread checker-thread)
+ (terminate-thread announcer-thread))
+ (setf checker-thread nil
+ announcer-thread nil)
+ (feedbot-flush-state bot)))
+~~~~
+
+**IX. Trilemabot commands**
+
+Consult [the feedbot manual][feedbot-manual] for more details.
+
+~~~~ {.commonlisp}
+(trilemabot-define-cmd (:help bot message target arguments)
+ (declare (ignore arguments))
+ (ircbot-send-message bot (response-rcpt bot message target)
+ "http://thetarpit.org/posts/y05/081-feedbot-manual.html"))
+~~~~
+
+**Post codex:** In total, Feedbot weighs circa nine hundred lines of
+code, of which almost a hundred comprise auxiliary functionality
+(parsing, sanitization and low-level IRC code), the rest being mostly
+Feedbot mechanism. Of these, about 23% are comments and another 7% are
+inline function documentation strings.
+
+The mechanism is, as far as I'm concerned, done. There are some
+operator-side bits missing (e.g. bulk adding/removal of feeds), but
+then again, some of them can be easily scripted, while others are more
+indicative of brain-fixing rather than any imagined code-fixing.
+
+Bug fixes and other comments are, as usual, more than welcome.
+
+[feedbot-i]: /posts/y05/08a-feedbot-i.html
+[feedbot-ii]: /posts/y05/08b-feedbot-ii.html
+[feedbot]: http://btcbase.org/log-search?q=feedbot
+[feedbot-irc.vpatch]: TODO
+[feedbot-irc.vpatch.spyked.sig]
+[trilemabot]: /posts/y05/078-trilemabot-ii.html
+[announcer]: /posts/y05/08b-feedbot-ii.html#selection-249.0-252.0
+[feedbot-manual]: /posts/y05/081-feedbot-manual.html