Update of /project/net-nittin-irc/cvsroot/net-nittin-irc/example In directory common-lisp.net:/tmp/cvs-serv32272
Modified Files: logger.lisp Removed Files: irclogs.css Log Message: add raw support, unichannel format support, remove html support, improve sexp format Date: Tue Dec 16 16:19:56 2003 Author: krosenberg
Index: net-nittin-irc/example/logger.lisp diff -u net-nittin-irc/example/logger.lisp:1.10 net-nittin-irc/example/logger.lisp:1.11 --- net-nittin-irc/example/logger.lisp:1.10 Mon Dec 15 13:16:40 2003 +++ net-nittin-irc/example/logger.lisp Tue Dec 16 16:19:56 2003 @@ -1,5 +1,5 @@ ;;;; -*- Mode: Lisp -*- -;;;; $Id: logger.lisp,v 1.10 2003/12/15 18:16:40 krosenberg Exp $ +;;;; $Id: logger.lisp,v 1.11 2003/12/16 21:19:56 krosenberg Exp $ ;;;; ;;;; Purpose: A logging bot ;;;; Author: Kevin Rosenberg @@ -12,7 +12,10 @@ (:use :common-lisp :irc :cl-ppcre) (:export #:add-logger #:quit-logger - #:log-file-path)) + #:log-file-path + #:add-hook-logger + #:remove-hook-logger + #:*loggers*)) (in-package irc-logger)
(defclass channel () @@ -36,25 +39,13 @@ (user-output :initarg :user-output :reader user-output :documentation "Output parameter from user, maybe stream or pathname.") + (unichannel :initarg :unichannel :reader unichannel :type boolean + :documentation "T if user-output is directory for individual channel output.") (formats :initarg :formats :reader formats :documentation "A list of output formats.")))
(defvar *loggers* nil "List of active loggers.") -(defvar *uri-scanner* - (create-scanner - '(:register - (:alternation - (:sequence :word-boundary "http://" - (:char-class (:range #\a #\z) (:range #\a #\z) (:range #\0 #\9)) - (:greedy-repetition 1 nil :non-whitespace-char-class)) - (:sequence :word-boundary "ftp://" - (:char-class (:range #\a #\z) (:range #\a #\z) (:range #\0 #\9)) - (:greedy-repetition 1 nil :non-whitespace-char-class)) - (:sequence :word-boundary "mailto:" - (:char-class (:range #\a #\z) (:range #\a #\z) (:range #\0 #\9)) - (:greedy-repetition 1 nil :non-whitespace-char-class)))) - :case-insensitive-mode t))
(defparameter *user-address-scanner* (create-scanner @@ -77,29 +68,19 @@ (declare (ignore second minute hour day-of-week daylight-p zone)) (make-output-name name year month day-of-month)))
-(defun html-title (channel-name) - (multiple-value-bind - (second minute hour day-of-month month year dow daylight-p zone) - (decode-universal-time (get-universal-time)) - (declare (ignore second minute hour dow daylight-p zone)) - (format nil "~A IRC Log ~4,'0D/~2,'0D/~2,'0D" - (string-left-trim '(##) channel-name) year month day-of-month))) - -(defun write-file-header (format channel-name stream) - (case format - (:html - (format stream - "<?xml version='1.0' ?>~%<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">~%") - (format stream - "<html xmlns='http://www.w3.org/1999/xhtml'>~%<head>~%<title>~A</title>~%<link rel='stylesheet' href='/irclogs.css' type='text/css' />~%</head>~%<body id='body'>~%<table><tbody>~%" - (html-title channel-name))))) - -(defun write-file-footer (format stream) - (case format - (:html - (format stream "</tbody></table></body></html>~%")))) +(defgeneric write-file-header (format channel-name stream))
-(defun log-file-path (output-root channel-name year month day format) +(defmethod write-file-header ((format t) channel-name stream) + (declare (ignore format channel-name stream)) + ) + +(defgeneric write-file-footer (format channel-name stream)) + +(defmethod write-file-footer ((format t) channel-name stream) + (declare (ignore format channel-name stream)) + ) + +(defun %log-file-path (output-root channel-name year month day type) (make-pathname :defaults output-root :directory (append (pathname-directory output-root) @@ -107,10 +88,18 @@ (string-left-trim '(##) channel-name) (format nil "~4,'0D-~2,'0D" year month))) :name (make-output-name channel-name year month day) - :type (case format - (:html "html") - (:sexp "sexp") - (t "txt")))) + :type type)) + +(defgeneric log-file-path (output-root channel-name year month day format)) + +(defmethod log-file-path (output-root channel-name year month day (format (eql :raw))) + (%log-file-path output-root channel-name year month day "raw")) + +(defmethod log-file-path (output-root channel-name year month day (format (eql :sexp))) + (%log-file-path output-root channel-name year month day "sexp")) + +(defmethod log-file-path (output-root channel-name year month day (format (eql :text))) + (%log-file-path output-root channel-name year month day "txt"))
(defun log-file-path-utime (output-root channel-name format utime) @@ -135,11 +124,12 @@ (defun (setf get-output-name) (value channel istream) (setf (elt (current-output-names channel) istream) value))
-(defun ensure-output-stream-for-directory-output (utime logger channel istream) +(defun ensure-output-stream-for-unichannel (utime logger channel istream) (let ((name (make-output-name-utime (name channel) utime))) (unless (string= name (get-output-name channel istream)) (when (get-stream channel istream) (write-file-footer (get-format logger istream) + (name channel) (get-stream channel istream)) (close (get-stream channel istream))) (setf (get-output-name channel istream) name) @@ -161,18 +151,16 @@ (defun ensure-output-stream (utime logger channel istream) "Ensures that *output-stream* is correct." (cond - ((streamp (user-output logger)) - (unless (get-stream channel istream) - (setf (get-stream channel istream) (user-output logger)))) - ((pathnamep (user-output logger)) - (cond - ;; user specified a named file for output - ((pathname-name (user-output logger)) - (setf (get-stream channel istream) - (open (user-output logger) :direction :output :if-exists :append))) - ;; user specified a directory for output files - (t - (ensure-output-stream-for-directory-output utime logger channel istream)))))) + ((streamp (user-output logger)) + (unless (get-stream channel istream) + (setf (get-stream channel istream) (user-output logger)))) + ((pathnamep (user-output logger)) + (cond + ((unichannel logger) + (ensure-output-stream-for-unichannel utime logger channel istream)) + (t + (setf (get-stream channel istream) + (open (user-output logger) :direction :output :if-exists :append)))))))
(defun format-utime (utime) (multiple-value-bind @@ -188,23 +176,6 @@ (declare (ignore second day-of-month month year day-of-week daylight-p zone)) (format nil "~2,'0D:~2,'0D" hour minute)))
-(defun activate-uris (str) - "Find any URI's in a string and make them HTML clickable." - (let ((split (split *uri-scanner* str :with-registers-p t))) - (if (= 1 (length split)) - str - (with-output-to-string (stream) - (dolist (item split) - (if (and (> (length item) 6) - (or - (string-equal "http://" (subseq item 0 7)) - (string-equal "ftp://" (subseq item 0 6)) - (string-equal "mailto:" (subseq item 0 7))) - ;; (ignore-errors (puri:parse-uri item)) - t) - (format stream "<a href='~A'>~A</a>" item item) - (write-string item stream))))))) - (defun user-address (msg) (let ((split (split *user-address-scanner* (raw-message-string msg) :with-registers-p t))) @@ -212,108 +183,112 @@ (second split) "")))
-(defun %output-event (stream format utime type source text object user-address) - (case format - (:html - (write-string "<tr><td class='time'>" stream) - (write-string (format-utime utime) stream) - (write-string "</td> " stream) - (case type - (:privmsg - (format stream "<td><span class='brack'><</span><span class='source'>~A</span><span class='brack'>></span></td><td class='msg'>~A</td>" - source (activate-uris text))) - (:action - (format stream - "<td><span class='action-brack'>*</span><span class='action-name'>~A</span><span class='action-brack'>*</span> <span class='action-msg'>~A</span></td>" - source (activate-uris text))) - (t - (format stream "<td><span class='info-source'>~A</span> <span class='info-brack'>[</span><span class='user-address'>~A</span><span class='info-brack'>]</span> <span class='info-msg'>~A</span>" - source user-address text) - (when object - (case type - (:quit - (format stream " <span class='info-brack'>[</span><span class='info-object'>~A</span><span class='info-brack'>]</span>" - object)) - (t - (format stream " <span class='info-object'>~A</span>" object)))) - (write-string "</td>" stream) - )) - (write-string "</tr>" stream) - (write-char #\Newline stream)) - (:sexp - (format stream "(~W ~W ~W ~W ~W ~W)~%" utime type source text object user-address)) - (t - (format stream "~A " (format-utime utime)) - (case type - (:privmsg - (format stream "<~A> ~A" source text)) - (:action - (format stream "*~A* ~A" source text)) - (t - (format stream "[info] ~A [~A] ~A" source user-address text) - (when object - (format stream (case type - (:quit " [~A]") - (t " ~A")) - object)))) - (write-char #\Newline stream)))) - -(defun is-info-type (type) +(defun need-user-address? (type) (not (or (eq :action type) (eq :privmsg type))))
-(defun output-event-for-a-stream (msg type text object logger channel istream) +(defgeneric %output-event (format stream utime type channel source text msg unichannel)) + +(defmethod %output-event ((format t) stream utime type channel source text + msg unichannel) + (%output-event :raw stream utime type channel source text msg unichannel)) + +(defmethod %output-event ((format (eql :raw)) stream utime type channel source text + msg unichannel) + (declare (ignore unichannel)) + (format stream "~S~%" (string-right-trim '(#\return) (raw-message-string msg)))) + +(defmethod %output-event ((format (eql :sexp)) stream utime type channel source text + msg unichannel) + (if unichannel + (format stream "(~S ~S ~S ~S ~S)~%" utime type source text + (when (need-user-address? type) (user-address msg))) + (format stream "(~S ~S ~S ~S ~S ~S)~%" utime type source channel + text (when (need-user-address? type) (user-address msg))))) + +(defmethod %output-event ((format (eql :text)) stream utime type channel source text + msg unichannel) + (format stream "~A " (format-utime utime)) + (when (and (null unichannel) channel) + (format stream "[~A] " channel)) + + (let ((user-address (when (need-user-address? type) (user-address msg)))) + (case type + (:privmsg + (format stream "<~A> ~A" source text)) + (:action + (format stream "*~A* ~A" source text)) + (:join + (format stream "~A [~A] has joined ~A" source user-address channel)) + (:part + (format stream "-!- ~A [~A] has left ~A" source user-address channel)) + (:nick + (format stream "-!- ~A is now known as ~A" source text)) + (:kick + (format stream "-!- ~A [~A] has been kicked from ~A" source user-address channel)) + (:quit + (format stream "-!- ~A [~A] has quit [~A]" source user-address text)) + (:mode + (format stream "-!- ~A has set mode ~A" source text)) + (:topic + (format stream "-!- ~A changed the topic of ~A to: ~A" source channel text)) + (:notice + (format stream "-~A:~A- ~A" source channel text)) + (t + (warn "Unhandled msg type ~A." type)))) + (write-char #\Newline stream)) + +(defun output-event-for-a-stream (msg type channel text logger istream) (ensure-output-stream (received-time msg) logger channel istream) - (%output-event (get-stream channel istream) (get-format logger istream) - (received-time msg) type (source msg) text object - (when (is-info-type type) (user-address msg))) + (%output-event (get-format logger istream) (get-stream channel istream) + (received-time msg) type (name channel) (source msg) text msg + (unichannel logger)) (force-output (get-stream channel istream)))
-(defun output-event (msg type text &optional object) +(defun output-event (msg type channel-name &optional text) (dolist (logger *loggers*) (case type - (:quit + ((:quit :nick) (dolist (channel (channels logger)) (dotimes (i (length (formats logger))) - (output-event-for-a-stream msg type text object logger channel i)))) + (output-event-for-a-stream msg type channel text logger i)))) (t - (let* ((channel-name (case type - (:join - (trailing-argument msg)) - (t - (car (last (arguments msg)))))) - (channel (find channel-name (the list (channels logger)) + (let* ((channel (find channel-name (the list (channels logger)) :test #'string-equal :key #'name))) (when channel (dotimes (i (length (formats logger))) - (output-event-for-a-stream msg type text object logger channel i)))))))) + (output-event-for-a-stream msg type channel text logger i))))))))
(defun privmsg-hook (msg) - (output-event msg :privmsg (trailing-argument msg))) + (output-event msg :privmsg (first (arguments msg)) (trailing-argument msg)))
(defun action-hook (msg) - (output-event msg :action - (subseq (trailing-argument msg) - 8 + (output-event msg :action (first (arguments msg)) + (subseq (trailing-argument msg) 8 (- (length (trailing-argument msg)) 1))))
(defun nick-hook (msg) - (output-event msg :nick "is now known as" - (trailing-argument msg))) + (output-event msg :nick nil (trailing-argument msg)))
(defun part-hook (msg) - (output-event msg :part "has left" - (first (arguments msg)))) + (output-event msg :part (first (arguments msg))))
(defun quit-hook (msg) - (output-event msg :quit "has quit" (trailing-argument msg))) + (output-event msg :quit (trailing-argument msg)))
(defun join-hook (msg) - (output-event msg :join "has joined" - (trailing-argument msg))) + (output-event msg :join (trailing-argument msg)))
(defun kick-hook (msg) - (output-event msg :kick "has been kicked from" - (first (arguments msg)))) + (output-event msg :kick (first (arguments msg)))) + +(defun notice-hook (msg) + (output-event msg :notice (first (arguments msg)) (trailing-argument msg))) + +(defun topic-hook (msg) + (output-event msg :topic (first (arguments msg)) (trailing-argument msg))) + +(defun mode-hook (msg) + (output-event msg :mode (first (arguments msg))))
(defun make-channels (names formats output) (loop for i from 0 to (1- (length names)) @@ -327,6 +302,10 @@ :current-output-names (make-array (length formats) :initial-element nil))))
+(defun is-unichannel-output (user-output) + "Returns T if output is setup for a single channel directory structure." + (and (pathnamep user-output) (null (pathname-name user-output)))) + (defun create-logger (nick server &key channels output (logging-stream t) (async t) @@ -350,7 +329,8 @@ :server server :channels (make-channels channels formats output) :user-output output - :formats formats))) + :formats formats + :unichannel (is-unichannel-output output))))
(mapc #'(lambda (channel) (join conn channel)) channels) (add-hook conn 'irc::irc-privmsg-message 'privmsg-hook) @@ -360,6 +340,9 @@ (add-hook conn 'irc::irc-quit-message 'quit-hook) (add-hook conn 'irc::irc-join-message 'join-hook) (add-hook conn 'irc::irc-kick-message 'kick-hook) + (add-hook conn 'irc::irc-mode-message 'mode-hook) + (add-hook conn 'irc::irc-topic-message 'topic-hook) + (add-hook conn 'irc::irc-notice-message 'notice-hook) (cond (async #+sbcl (add-asynchronous-message-handler conn) @@ -400,5 +383,9 @@ :async async :formats formats))) (push logger *loggers*) logger)) -
+(defun add-hook-logger (logger msg hook) + (add-hook (connection logger) msg hook)) + +(defun remove-hook-logger (logger msg) + (remove-hook (connection logger) msg))