anchor.diff:  ;;     (when (= 200 status)
   ;;       (decode-json-from-string value))))
   
  -
  -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  -
  -;; (setf *nickname* "botil-test" *channel* "#botil-test")
  +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  +
  +(defstruct worker
  +  input-queue
  +  send
  +  thread)
  +
  +(defun send (worker &rest message)
  +  (apply (worker-send worker) message))
  +
  +(defmacro make-worker-thread (name message-lambda-list
  +                              &body body)
  +  (check-type name symbol)
  +  (check-type message-lambda-list list)
  +  (let* ((vmessage     (gensym))
  +         (vinput-queue (gensym))
  +         (sname        (string name))
  +         (ll (parse-lambda-list message-lambda-list :destructuring))
  +         (pl (make-parameter-list ll)))
  +    `(let ((,vinput-queue (make-queue ,sname)))
  +       (make-worker
  +        :input-queue ,vinput-queue
  +        :send (lambda (&rest ,vmessage)
  +                (destructuring-bind ,message-lambda-list ,vmessage
  +                  (declare (ignorable ,@pl))
  +                  (enqueue ,vinput-queue ,vmessage)))
  +        :thread (make-thread
  +                 (lambda ()
  +                   (loop
  +                     :for ,vmessage := (dequeue ,vinput-queue)
  +                     :do (handler-case
  +                             (flet ((terminate-worker () (loop-finish)))
  +                               (block ,name
  +                                 (destructuring-bind ,message-lambda-list ,vmessage
  +                                   (declare (ignorable ,@pl))
  +                                   ,@body)))
  +                           (error (err)
  +                             (format *error-output* "~&~A: ~A~%"
  +                                     ',sname err)))))
  +                 :name ,sname)))))
  +
  +(defvar *botil*)
  +(defvar *dispatch*)
  +(defvar *command-processor*)
  +(defvar *query-processor*)
  +(defvar *sender*)
  +(defvar *logger*)
  +
  +(defun sender (recipient text)
  +  (privmsg *connection* recipient text))
  +
  +(defun say (&rest args)
  +  (format t "~&~{~A~^ ~}~%" args)
  +  (force-output))
  +
  +(defun answer (recipient format-control &rest format-arguments)
  +  (let ((text (apply (function format) nil format-control format-arguments)))
  +    (say text)
  +    (apply (function send) *sender* recipient format-control format-arguments)))
  +
  +(defun query-processor (sender query)
  +  (declare (ignore sender query))
  +  )
  +
  +
  +(defun joined-channels ()
  +  )
  +
  +(defun join-channel (channel)
  +  (join *connection* channel))
  +
  +(defun logged-channels () ;; TIME!
  +  )
  +
  +(defun start-logging-channel (channel)
  +  (unless (member channel (joined-channels) :test (function string=))
  +    (join-channel channel)))
  +
  +(defun stop-logging-channel (channel)
  +  (declare (ignore channel))
  +  )
  +
  +(defun logger (message)
  +  (declare (ignore message))
  +  )
  +
  +(defun command-processor (sender command)
  +  (let ((words (split-sequence #\space command :remove-empty-subseqs t)))
  +    (scase (first words)
  +           (("help")
  +            (answer sender "Available commands: help version uptime sources"))
  +           (("version")
  +            (answer sender "Version: ~A" *version*))
  +           (("uptime")
  +            (answer sender "~A" (substitute #\space #\newline
  +                                            (with-output-to-string (*standard-output*)
  +                                              (date) (uptime)))))
  +           (("reconnect")
  +            (if (string= (second words) *botpass*)
  +                (progn (answer sender "Reconnecting…")
  +                       (reconnect))
  +                (answer sender "I'm not in the mood.")))
  +           (otherwise                   ; ("sources")
  +            (answer sender "I'm an IRC bot forwarding HackerNews news; ~
  +                          under AGPL3 license, my sources are available at <~A>."
  +                    *sources-url*)))))
  +
  +(defun dispatch (message)
  +  (with-accessors ((source source)
  +                   (user user)
  +                   (host host)
  +                   (command command)
  +                   (arguments arguments)) message
  +    (format t "~&~S~%" (list :source source :user user :host host :command command :arguments arguments))
  +    (let ((recipient (first arguments)))
  +      (say arguments)
  +      (if (string= *nickname* recipient)
  +          (send *command-processor* user (second arguments))
  +          (send *logger* message)))))
  +
  +(defun botil (command)
  +  (ecase command
  +    ((reconnect)
  +     (dolist (channel (logged-channels))
  +       (start-logging-channel channel)))
  +    ((quit)
  +     (exit))))
  +
  +
  +(defun botil-initialize ()
  +  (setf *sender*            (make-worker-thread sender (recipient message &rest arguments)
  +                              (let ((message (format nil "~?" message arguments)))
  +                                
  +                                (format *trace-output* "~&~A <- ~A~%" recipient message)
  +                                (sender recipient message)))
  +        *query-processor*   (make-worker-thread query-processor (sender message)
  +                              (format *trace-output* "~&~A -> ~A~%" sender message)
  +                              (query-processor sender message))
  +        *logger*            (make-worker-thread logger (message)
  +                              (format *trace-output* "~&~A -> ~A~%" (user message) message)
  +                              (logger message))
  +        *command-processor* (make-worker-thread command-processor (sender message)
  +                              (format *trace-output* "~&~A -> ~A~%" sender message)
  +                              (command-processor sender message))
  +        *dispatch*          (make-worker-thread dispatch (message)
  +                              (format *trace-output* "~&~A -> ~A~%" (source message) message)
  +                              (dispatch message))
  +        *botil*             (make-worker-thread botil (command)
  +                              (format *trace-output* "~&BOTIL <- ~A~%" command)
  +                              (botil command))))
  +
  +
  +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  +
  +;; (setf *nickname* "botil-test"  "#botil-test")
   (defun configure ()
     (setf *server*   (or (uiop:getenv "BOTIL_SERVER")    *server*)
           *nickname* (or (uiop:getenv "BOTIL_NICKNAME")  *nickname*)
  
positive.code: (defun main ()
    "The main program of the botil IRC bot.
  We connect and reconnect to the *SERVER* under the *NICKNAME*,
  and join to the *CHANNEL* where HackerNews are published."
    (let ((*package* (load-time-value (find-package "COM.INFORMATIMAGO.SMALL-CL-PGMS.BOTIL"))))
      (configure)
      (with-simple-restart (quit "Quit")
        (catch :gazongues
          (with-retry (sleep (+ 10 (random 30)))
  
negative.code: ;;;;    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  ;;;;**************************************************************************
  (defpackage "COM.INFORMATIMAGO.SMALL-CL-PGMS.BOTIL"
    (:use "COMMON-LISP"
          "CL-IRC" "CL-JSON" "DRAKMA"  "SPLIT-SEQUENCE" "BORDEAUX-THREADS"
          "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.LIST"
          "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.UTILITY"
          "COM.INFORMATIMAGO.COMMON-LISP.LISP-SEXP.SOURCE-FORM"
          "COM.INFORMATIMAGO.CLEXT.QUEUE")
    (:import-from "COM.INFORMATIMAGO.COMMON-LISP.INTERACTIVE.INTERACTIVE"
                  "DATE" "UPTIME")
    (:export "MAIN")
  