Ticket #13: tracking.patch

File tracking.patch, 9.5 KB (added by Erik Huelsmann, 18 years ago)

First iteration to make message tracking work in cl-irc

  • TODO

    ? dist.sh
    ? msg
    ? tracking.patch
    ? working-track.lisp
    ? doc/tracking-considerations
    RCS file: /project/cl-irc/cvsroot/cl-irc/TODO,v
    retrieving revision 1.2
    diff -u -r1.2 TODO
     
    22  - Add DCC
    33
    44    - I would really like usocket first
     5    - In the process add configuration options for automatically accepting
     6      DCC connections (both :send and :chat types)
    57
    68  - If a message (as in PRIVMSG) is longer than 512 characters
    79  (including carriage return and linefeed) we should probably split
  • cl-irc.asd

    RCS file: /project/cl-irc/cvsroot/cl-irc/cl-irc.asd,v
    retrieving revision 1.2
    diff -u -r1.2 cl-irc.asd
     
    3939                 (:file "command"
    4040                        :depends-on ("protocol"))
    4141                 (:file "event"
     42                        :depends-on ("command"))
     43                 (:file "track"
    4244                        :depends-on ("command"))))
  • package.lisp

    RCS file: /project/cl-irc/cvsroot/cl-irc/package.lisp,v
    retrieving revision 1.8
    diff -u -r1.8 package.lisp
     
    129129             :users-
    130130             :wallops
    131131             :userhost
    132              :ison)))
     132             :ison
     133             ;;; user activity tracking
     134             :track-seen
     135             :track-spoke
     136             :seen
     137             :spoke
     138             )))
    133139
  • protocol.lisp

    RCS file: /project/cl-irc/cvsroot/cl-irc/protocol.lisp,v
    retrieving revision 1.25
    diff -u -r1.25 protocol.lisp
     
    159159   (users
    160160    :initarg :users
    161161    :accessor users
    162     :initform (make-hash-table :test #'equal))))
     162    :initform (make-hash-table :test #'equal))
     163   (seen-db)
     164   (spoke-db)))
    163165
    164166(defmethod print-object ((object connection) stream)
    165167  "Print the object for the Lisp reader."
  • new file track.lisp

    RCS file: track.lisp
    diff -N track.lisp
    - +  
     1;;;
     2;;; seen.lisp
     3;;;
     4;;; Created: 2004-01-08 by Zach Beane <xach@gwi.net>
     5;;;
     6;;; Adapted to be part of cl-irc by Erik Huelsmann <e.huelsmann@gmx.net>
     7;;;
     8;;; Track the last time a nick was seen doing certain IRC
     9;;; stuff. Can be publicly queried with:
     10;;;
     11;;;   botname, seen johndoe?
     12;;;
     13;;; or
     14;;;
     15;;;   /msg botname seen johndoe?
     16;;;
     17;;;
     18;;; To add it to your bot, do something like:
     19;;;
     20;;;   (irc-seen:initialize connection "seen-db.sexp")
     21;;;
     22
     23(in-package :irc)
     24
     25
     26(defclass msg-db ()
     27  ((table :reader msg-db-table
     28          :initform (make-hash-table :test 'equalp)
     29          :documentation "A hash table mapping IRC nicknames to
     30          their seen data.")
     31   (file :reader msg-db-file
     32         :initarg :file
     33         :documentation "The file to which seen data will be
     34         saved and updated.")
     35   (compact-threshold :accessor msg-db-compact-threshold
     36                      :initarg :compact-threshold
     37                      :initform 10000
     38                      :documentation "How many updates to write
     39                      to the data file before compacting it.")
     40   (update-count :accessor msg-db-update-count
     41                 :initform 0)))
     42
     43(defun %file-update (stream key &rest args)
     44  (let ((*print-pretty* nil)
     45        (*print-readably* t))
     46    (print (cons key args) stream)))
     47
     48(defun %table-update (table key &rest args)
     49  (setf (gethash key table) args))
     50
     51(defmethod update-db ((db msg-db)
     52                      nick time message &optional channel)
     53  (with-open-file (out (msg-db-file db)
     54                       :direction :output
     55                       :if-exists :append
     56                       :if-does-not-exist :create)
     57    (%file-update out nick time message channel)
     58  (incf (msg-db-update-count db))
     59  (maybe-compact-db db)
     60  (%table-update (msg-db-table db)
     61                 nick time message channel)))
     62
     63(defmethod save-db ((db msg-db))
     64  (with-open-file (out (msg-db-file db)
     65                       :direction :output
     66                       :if-exists :supersede
     67                       :if-does-not-exist :create)
     68    (maphash #'(lambda (key val)
     69                 (apply #'%file-update out key val))
     70             (msg-db-table db))))
     71
     72(defmethod compact-db ((db msg-db))
     73  (save-db db)
     74  (setf (msg-db-update-count db) 0))
     75
     76(defmethod maybe-compact-db ((db msg-db))
     77  (when (> (msg-db-update-count db)
     78           (msg-db-compact-threshold db))
     79    (compact-db db)))
     80
     81(defmethod load-db ((db msg-db))
     82  (with-open-file (in (msg-db-file db)
     83                      :direction :input
     84                      :if-does-not-exist :create)
     85    (loop for item = (read in nil nil)
     86          while item
     87          do
     88          (destructuring-bind
     89              (nick &rest rest)
     90              item
     91            (apply #'%table-update (append (list (msg-db-table db) nick)
     92                                           rest))))))
     93
     94
     95(defun make-msg-db (&key file (compact-threshold 10000))
     96  (let ((db (make-instance 'msg-db
     97                           :file file
     98                           :compact-threshold compact-threshold)))
     99    (load-db db)
     100    (compact-db db)
     101    db))
     102
     103
    0104
     105;;; Producing pretty messages for various events
     106
     107(defmacro with-slots-and-args (slots args message &body body)
     108  "Evaluate BODY with the slots of the MESSAGE bound to the
     109  symbols of SLOTS, and the arguments of the message
     110  destructuring-bind'ed to ARGS."
     111  (let ((irc-message (gensym)))
     112    `(let ((,irc-message ,message))
     113       (with-slots ,slots ,irc-message
     114         (destructuring-bind ,args (arguments ,irc-message)
     115           ,@body)))))
     116
     117
     118
     119(defmethod seen-message ((message irc-message))
     120  (format nil "something to do with ~A" (command message)))
     121
     122(defmethod seen-message ((message irc-kick-message))
     123  (with-slots-and-args (source trailing-argument) (channel target)
     124      message
     125    (declare (ignore target))
     126    (format nil "being kicked out of ~A by ~A ~@[(~A)~]"
     127            channel source trailing-argument)))
     128
     129(defmethod seen-message ((message irc-join-message))
     130  (format nil "joining ~A" (trailing-argument message)))
     131
     132(defmethod seen-message ((message irc-quit-message))
     133  (format nil "quitting irc~@[ saying ~A~]" (trailing-argument message)))
     134
     135(defmethod seen-message ((message irc-part-message))
     136  (with-slots-and-args (trailing-argument) (channel)
     137      message
     138    (format nil "leaving ~A~@[ saying ~A~]" channel trailing-argument)))
     139
     140(defmethod seen-message ((message irc-nick-message))
     141  (with-slots-and-args (source) (new-nick)
     142      message
     143    (format nil "changing his nick from ~A to ~A" source new-nick)))
     144
     145;; Most messages have the seen-related nick as the source, but
     146;; there are exceptions.
     147
     148(defmethod track-nick ((message irc-message))
     149  (source message))
     150
     151(defmethod track-nick ((message irc-kick-message))
     152  (destructuring-bind (channel target)
     153      (arguments message)
     154    target))
     155
     156
     157
    1158
     159;; Hook setup
     160
     161(defun make-seen-nick-hook (db)
     162  ;; Triggers two updates
     163  (lambda (message)
     164    (let ((time (received-time message))
     165          (seen-message (seen-message message)))
     166      (destructuring-bind (new-nick)
     167          (arguments message)
     168        (update-db db (track-nick message) time seen-message)
     169        (update-db db new-nick time seen-message)))))
     170
     171(defun make-spoke-hook (db)
     172  (lambda (message)
     173    (update-db db
     174               (track-nick message)
     175               (received-time message)
     176               (trailing-argument message)
     177               (first (arguments message)))))
     178
     179(defun track-seen (connection file)
     180  "Add hooks to CONNECTION necessary to track `seen' information
     181and respond to user queries. Uses FILE as the `seen' database.
     182
     183Returns the database-object used to track seen messages."
     184  (let* ((db (make-msg-db :file file))
     185         (fun (lambda (irc-message)
     186                (update-db db
     187                           (track-nick irc-message)
     188                           (received-time irc-message)
     189                           (seen-message irc-message)))))
     190    (setf (slot-value connection 'seen-db) db)
     191    (dolist (class '(irc-kick-message
     192                     irc-quit-message
     193                     irc-part-message
     194                     irc-join-message))
     195      (add-hook connection class fun))
     196    (add-hook connection 'irc-nick-message
     197                  (make-seen-nick-hook db))))
     198
     199(defun track-spoke (connection file)
     200  (let* ((db (make-msg-db :file file))
     201         (fun (make-spoke-hook db)))
     202    (setf (slot-value connection 'spoke-db) db)
     203    (dolist (class '(irc-privmsg-message
     204                     ctcp-action-message))
     205      (add-hook connection class fun))))
     206
     207(defmethod seen ((connection connection) nick)
     208  "Returns (values time msg)"
     209  (values-list (gethash nick (msg-db-table (slot-value connection 'seen-db)))))
     210
     211(defmethod spoke ((connection connection) nick)
     212  "Returns (values time msg channel)"