123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- (defpackage :irc-translator
- (:use :cl :hurd-common :mach
- :hurd :hurd-translator
- :hurd-tree-translator
- :flexi-streams))
- (in-package :irc-translator)
- (defvar *nickname* (first ext:*args*))
- (defvar *server* (second ext:*args*))
- (defconstant +max-file-size+ 5000)
- (defun create-adjustable-array (&key (size 0) (contents nil))
- (make-array size
- :adjustable t
- :fill-pointer t
- :element-type '(unsigned-byte 8)
- :initial-contents contents))
- (defun has-data-p (connection)
- (let ((result (socket:socket-status (irc:network-stream connection))))
- (or (eq :input result)
- (eq :io result))))
- (defclass irc-translator (tree-translator)
- ((file-stat :initarg :file-stat
- :initform nil
- :accessor file-stat
- :documentation "Stat information for regular files.")
- (dir-stat :initarg :dir-stat
- :initform nil
- :accessor dir-stat
- :documentation "Stat information for directories.")
- (connection :initarg :connection
- :initform nil
- :accessor connection
- :documentation "Irc connection object.")
- (notice-node :initform nil
- :accessor notice-node
- :documentation "Node with notices from the server.")))
- (defclass data-entry ()
- ((contents :initform nil
- :initarg :data
- :accessor data)))
- (defclass channel-obj-entry ()
- ((channel :initarg :channel
- :accessor channel)))
- (defclass channel-entry (dir-entry channel-obj-entry) ())
- (defclass topic-entry (data-entry entry channel-obj-entry) ())
- (defclass users-entry (entry channel-obj-entry) ())
- (defclass log-entry (entry channel-obj-entry data-entry) ())
- (defclass notice-entry (log-entry) ())
- (defclass kick-entry (channel-obj-entry entry) ())
- (defclass pvt-entry (log-entry)
- ((user :initarg :user
- :accessor user)))
- (defun update-topic-data (node)
- (setf (data node)
- (string-to-octets (concatenate-string
- (irc:topic (channel node))
- (list #\Newline)))))
- (defun read-from-data-entry (node start amount stream)
- (let* ((size (stat-get (stat node) 'st-size))
- (size-res (- size start)))
- (unless (plusp size-res)
- (return-from read-from-data-entry t))
- (let* ((total (min size-res amount))
- (end (+ start total)))
- (write-sequence (subseq (data node) start end)
- stream)
- t)))
- (define-callback read-file irc-translator
- ((node topic-entry) user start amount stream)
- (when (has-access-p node user :read)
- (when (null (data node))
- (update-topic-data node))
- (read-from-data-entry node start amount stream)))
- (define-callback read-file irc-translator
- ((node kick-entry) user start amount stream)
- (declare (ignore start amount stream))
- (has-access-p node user :read)) ;; Nothing to read at all.
- (defun get-key-list (hashtable)
- (sort (loop for key being the hash-keys of hashtable
- collect key)
- #'string<))
- (define-callback read-file irc-translator
- ((node users-entry) user start amount stream)
- (when (has-access-p node user :read)
- (let* ((users (irc:users (channel node)))
- (size (calculate-users-size users))
- (size-res (- size start)))
- (unless (plusp size-res)
- (return-from read-file t))
- (let* ((total (min size-res amount))
- (end (+ start total))
- (pos 0))
- (loop for nick in (get-key-list users)
- do (cond
- ((>= pos end)
- (return-from read-file t))
- (t
- (loop for a across (concatenate-string nick
- (list #\Newline))
- do (progn
- (cond
- ((>= pos end)
- (return-from read-file t))
- ((>= pos start)
- (write-byte (char-code a) stream)))
- (incf pos))))))
- t))))
- (define-callback read-file irc-translator
- ((node log-entry) user start amount stream)
- (when (has-access-p node user :read)
- (read-from-data-entry node start amount stream)))
- (define-callback write-file irc-translator
- ((node notice-entry) user offset stream amount)
- (declare (ignore user offset stream amount))
- nil)
- (defun get-message-stream (stream amount)
- (let ((array (make-array amount :element-type '(unsigned-byte 8))))
- (read-sequence array stream)
- (string-trim (list #\Newline)
- (octets-to-string array))))
- (define-callback write-file irc-translator
- ((node pvt-entry) user offset stream amount)
- (declare (ignore offset))
- (when (has-access-p node user :write)
- (let ((msg (get-message-stream stream amount)))
- (irc:privmsg (connection translator)
- (user node)
- msg)
- (add-new-info node
- (make-privmsg-string *nickname* msg))
- t)))
- (define-callback write-file irc-translator
- ((node log-entry) user offset stream amount)
- (declare (ignore offset))
- (when (has-access-p node user :write)
- (let ((msg (get-message-stream stream amount)))
- (irc:privmsg (connection *translator*)
- (channel node)
- msg)
- (add-new-info node
- (make-privmsg-string *nickname* msg))
- t)))
- (define-callback write-file irc-translator
- ((node kick-entry) user offset stream amount)
- (declare (ignore offset))
- (when (has-access-p node user :write)
- (let* ((kick-str (string-trim " " (get-message-stream stream amount)))
- (space-pos (position #\Space kick-str))
- (reason-p (not (null space-pos))))
- (let ((nick (if reason-p (subseq kick-str 0 space-pos) kick-str))
- (reason (if reason-p (string-trim " " (subseq kick-str (1+ space-pos)))
- "no reason")))
- (irc:kick (connection *translator*)
- (channel node)
- (irc:find-user (connection *translator*)
- nick)
- reason)
- t))))
- (define-callback write-file irc-translator
- ((node topic-entry) user offset stream amount)
- (declare (ignore offset))
- (when (has-access-p node user :write)
- (let ((new-topic (get-message-stream stream amount)))
- (irc:topic- (connection *translator*)
- (channel node)
- new-topic)
- t)))
- (define-callback report-no-users irc-translator
- ((node topic-entry))
- (setf (data node) nil))
- (defun calculate-users-size (table)
- (loop for key being the hash-keys of table
- sum (1+ (length key))))
- (define-callback refresh-node irc-translator
- ((node topic-entry) user)
- (declare (ignore user))
- (setf (stat-get (stat node) 'st-size)
- (1+ (length (irc:topic (channel node))))))
- (define-callback refresh-node irc-translator
- ((node users-entry) user)
- (declare (ignore user))
- (let* ((users (irc:users (channel node)))
- (size (calculate-users-size users)))
- (setf (stat-get (stat node) 'st-size) size)))
- (defmethod do-remove-directory-entry ((found node) node name)
- (remove-dir-entry node name))
- (defmethod do-remove-directory-entry ((found channel-entry) node name)
- (declare (ignore node))
- (irc:part (connection *translator*)
- (irc:normalized-name (channel found))
- (format nil "rm ~a" name))
- t)
- (define-callback remove-directory-entry irc-translator
- (node user name)
- (let ((found (get-entry node name)))
- (when (and found
- (is-owner-p found user))
- (do-remove-directory-entry found node name))))
- (define-callback create-directory irc-translator
- (node user name mode)
- (declare (ignore mode))
- (unless (eq node (root *translator*))
- (return-from create-directory nil))
- (unless (is-owner-p node user)
- (return-from create-directory nil))
- (let ((old (get-entry node name)))
- (cond
- (old nil)
- (t
- (irc:join (connection *translator*)
- (concatenate-string "#"
- name))
- t))))
- (define-callback shutdown irc-translator
- ()
- (irc:quit (connection *translator*) "settrans -g")
- (sleep 0.5))
- (defun make-pvt-file (root user)
- (let ((new-entry (make-instance 'pvt-entry
- :parent root
- :stat (make-stat (file-stat *translator*))
- :data (create-adjustable-array)
- :user user)))
- (add-entry root new-entry user)))
- (define-callback create-file irc-translator
- (node user filename mode)
- (declare (ignore user mode))
- (when (eq node (root translator))
- (make-pvt-file (root translator)
- filename)))
- (define-callback fill-root-node irc-translator
- ((node dir-entry))
- (setf (file-stat translator)
- (make-stat (stat node)
- :mode (make-mode :perms '((:owner :read :write)
- (:group :read)))
- :type :reg
- :size 0))
- (setf (dir-stat translator)
- (make-stat (stat node)
- :nlink 0
- :mode (make-mode :perms '((:owner :read :exec)
- (:group :read :exec)))
- :type :dir))
- (setf (connection translator)
- (irc:connect :nickname *nickname* :server *server*))
- (let ((notice-entry (make-instance 'notice-entry
- :parent node
- :stat (make-stat (file-stat translator))
- :data (create-adjustable-array))))
- (setf (notice-node translator) notice-entry)
- (add-entry node notice-entry "notice")))
- (defmethod add-new-info ((node log-entry) str)
- (let* ((current-size (stat-get (stat node) 'st-size))
- (final-str (concatenate-string str (list #\Newline)))
- (this-size (length final-str))
- (new-size (+ current-size this-size)))
- (adjust-array (data node)
- new-size
- :fill-pointer t)
- (replace (data node) (string-to-octets final-str)
- :start1 current-size)
- (when (> new-size +max-file-size+)
- (let ((extra (- new-size +max-file-size+)))
- (setf (data node)
- (create-adjustable-array :size +max-file-size+
- :contents (subseq (data node) extra)))
- (decf new-size extra)))
- (setf (stat-get (stat node) 'st-size) new-size)))
- (defmethod add-new-info ((channel-name string) str)
- (let ((found (get-entry (root *translator*) channel-name)))
- (when (and found
- (typep found 'channel-entry))
- (let ((found2 (get-entry found "conversation")))
- (when (and found2
- (typep found2 'log-entry))
- (add-new-info found2 str))))))
- (defun create-new-channel (orig-channel channel)
- (let* ((channel-obj (irc:find-channel (connection *translator*)
- orig-channel))
- (channel-dir (make-instance 'channel-entry
- :parent (root *translator*)
- :stat (make-stat (dir-stat *translator*))
- :channel channel-obj)))
- (assert (not (null channel-obj)))
- (add-entry (root *translator*) channel-dir channel)
- (let ((topic-entry (make-instance 'topic-entry
- :parent channel-dir
- :stat (make-stat (file-stat *translator*))
- :channel channel-obj))
- (users-entry (make-instance 'users-entry
- :parent channel-dir
- :stat (make-stat (file-stat *translator*))
- :channel channel-obj))
- (conversation-entry (make-instance 'log-entry
- :parent channel-dir
- :stat (make-stat (file-stat *translator*))
- :data (create-adjustable-array)
- :channel channel-obj))
- (kick-entry (make-instance 'kick-entry
- :parent channel-dir
- :stat (make-stat (file-stat *translator*))
- :channel channel-obj)))
- (add-entry channel-dir kick-entry "kick")
- (add-entry channel-dir conversation-entry "conversation")
- (add-entry channel-dir users-entry "users")
- (add-entry channel-dir topic-entry "topic"))))
- (defun get-channel-name (str)
- (string-left-trim "#"
- (string-downcase str)))
- (defun handle-join (msg)
- (let* ((orig-channel (first (irc:arguments msg)))
- (channel (get-channel-name orig-channel))
- (who (irc:source msg)))
- (when (string= who *nickname*)
- (create-new-channel orig-channel channel))
- (add-new-info channel
- (format nil "~s enters the room" who))))
- (defun remove-channel (name)
- (remove-dir-entry (root *translator*) name))
- (defun handle-part (msg)
- (let* ((args (irc:arguments msg))
- (orig-channel (first (irc:arguments msg)))
- (channel (get-channel-name orig-channel))
- (who (irc:source msg)))
- (when (string= who *nickname*)
- (remove-channel channel))
- (add-new-info channel
- (format nil "~s exits the room (~s)" who
- (if (null (rest args))
- "no reason"
- (second args))))))
- (defun make-privmsg-string (who msg)
- (format nil "~s: ~a" who msg))
- (defun handle-privmsg-pvt (source msg)
- (add-new-info (if (has-entry-p (root *translator*) source)
- (get-entry (root *translator*) source)
- (make-pvt-file (root *translator*) source))
- (make-privmsg-string source msg)))
- (defun handle-privmsg (msg)
- (let ((dest (first (irc:arguments msg)))
- (src (irc:source msg))
- (str (second (irc:arguments msg))))
- (cond
- ((string= dest *nickname*)
- (handle-privmsg-pvt src str))
- (t
- (add-new-info (get-channel-name dest)
- (make-privmsg-string src str))))))
- (defun join-string-list (string-list)
- "Concatenates a list of strings and puts spaces between the elements."
- (format nil "~{~A~^ ~}" string-list))
- (defun handle-notice (msg)
- (add-new-info (notice-node *translator*)
- (join-string-list (irc:arguments msg))))
- (defun handle-quit (msg)
- (add-new-info (notice-node *translator*)
- (format nil "QUIT: ~a ~a"
- (irc:source msg)
- (join-string-list (irc:arguments msg)))))
- (defun handle-kick (msg)
- (let* ((args (irc:arguments msg))
- (orig-channel (first (irc:arguments msg)))
- (channel (get-channel-name orig-channel))
- (kicker (irc:source msg))
- (kicked (second args))
- (reason (if (= (length args) 3) (third args) "no reason")))
- (add-new-info channel
- (format nil "~s kicks ~s from the room (~s)"
- kicker
- kicked
- reason))
- (when (string= kicked *nickname*)
- (remove-channel channel)
- (add-new-info (notice-node *translator*)
- (format nil "KICK: You got kicked from ~s by ~s (~s)"
- orig-channel
- kicker
- reason)))))
- (defun handle-irc-message (msg)
- (let ((cmd (irc:command msg)))
- (cond
- ((string= "JOIN" cmd)
- (handle-join msg))
- ((string= "PART" cmd)
- (handle-part msg))
- ((string= "PRIVMSG" cmd)
- (handle-privmsg msg))
- ((or (string= "NOTICE" cmd)
- (and (>= (length cmd) 3)
- (or (string= "ERR" (subseq cmd 0 3))
- (string= "RPL" (subseq cmd 0 3)))))
- (handle-notice msg))
- ((string= "QUIT" cmd)
- (handle-quit msg))
- ((string= "KICK" cmd)
- (handle-kick msg))
- ((or (string= "PING" cmd)
- (string= "UNKNOWN-REPLY" cmd))))))
- (defun main ()
- (let ((translator
- (make-instance 'irc-translator
- :name "irc-translator")))
- (setup-translator translator)
- (let ((*translator* translator))
- (loop do (progn
- (wait :miliseconds 100)
- (loop while (has-data-p (connection *translator*))
- do (handler-bind
- ((irc:no-such-reply
- #'(lambda (c)
- (declare (ignore c))
- (continue))))
- (handle-irc-message (irc:read-message
- (connection *translator*))))))))))
- (main)
|