source: tags/mode_tracking/utility.lisp

Last change on this file was 88, checked in by Erik Huelsmann, 20 years ago

Implement MODE tracking.

  • TODO: Remove MODE updating item.
  • doc/user-guide.txt: Add section about modes.
  • package.lisp: Export new symbols.
  • variable.lisp (*default-isupport-CHANMODES*, *default-isupport-PREFIX*): New. (*default-isupport-values*): Add modes described by the RFC and declare them the default (minimal set). (mode-description): New. Structure to describe characteristics of mode arguments sent by the server. (*default-char-to-channel-modes-map*,

*char-to-user-modes-map*): New. Assoc lists to map characters to names.

  • utility.lisp (parse-isupport-prefix-argument): New. Returns the prefix and mode argument parts of the PREFIX RPL_ISUPPORT parameter. (nick-prefixes-from-isupport): New. Returns a plist associating mode prefixes with mode character designations. (chanmode-descs-from-isupport): New. Returns a list of mode-description structures for use with auto-creation of mode objects. (do-property-list): New. Macro to walk a property list like dolist. (parse-mode-arguments): New. Parses mode arguments given a connection object and target and translates those into a list of mode change instructions.
  • protocol.lisp (irc-mode): New. Abstract super class. Derivatives used to store mode values. (set-mode-value, unset-mode-value, reset-mode-value, has-value-p): New. Methods for irc-mode and its derivatives. (single-value-mode, list-value-mode): New. Classes implementing two types of value-holding mode-storage. (connection:channel-mode-descriptions): New. Slot in which the channel mode descriptions for the connection get stored. (connection:nick-prefixes): New. Slot which stores a plist associating RPL_NAMREPLY prefixes with mode characters. (connection:user-mode-descriptions): New. Slot which stores user modes like channel-mode-descriptions does for channels. (add-default-hooks): Add hook for irc-mode-message. (channel:modes): Change initialization to signal the value held will be of LIST type. (mode-name-from-char): New. Translates a mode character into an internal 'name': symbol. (mode-description): New. Retrieves a mode-description record from the given connection of a given mode name. (get-mode, set-mode, unset-mode, remove-mode): New. These provide operations on both channel and user modes. (has-mode-p, has-mode-value-p): New. This must be obvious. (remove-users): Also remove references to all users from any properties which carry the :user value-type. (make-mode): New. Automatically create mode object for the given mode to be added to the given target (user/channel). (user:modes): New. Slot to hold modes just like there is one on the channel class. (remove-user): Same as remove-user, but for the given user only.
  • event.lisp (default-hook [irc-rpl_isupport-message]): Set new channel-mode-descriptions and nick-prefixes slots. (default-hook [irc-rpl_namreply-message]): Set mode fields based on prefixes passed in the reply. (default-hook [irc-mode-message]): Set or unset channel and user modes upon reception of server notification.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 11.8 KB
Line 
1;;;; $Id: utility.lisp 88 2005-03-20 16:55:43Z ehuelsmann $
2;;;; $Source$
3
4;;;; See the LICENSE file for licensing information.
5
6(in-package :irc)
7
8(defun get-day-name (day-number)
9  "Given a number, such as 1, return the appropriate day name,
10abbrevated, such as \"Tue\".  Index 0 is Monday."
11  (case day-number
12    (0 "Mon")
13    (1 "Tue")
14    (2 "Wed")
15    (3 "Thu")
16    (4 "Fri")
17    (5 "Sat")
18    (6 "Sun")
19    (otherwise
20     (error "Unknown day ~A." day-number))))
21
22(defun get-month-name (month-number)
23  "Index 1 is January."
24  (case month-number
25    (1 "Jan")
26    (2 "Feb")
27    (3 "Mar")
28    (4 "Apr")
29    (5 "May")
30    (6 "Jun")
31    (7 "Jul")
32    (8 "Aug")
33    (9 "Sep")
34    (10 "Oct")
35    (11 "Nov")
36    (12 "Dec")
37    (otherwise
38     (error "Unknown month ~A." month-number))))
39
40(defun make-time-message (second minute hour date month year day)
41  "Returns a string composed of the input parameters so that it
42represents a time message as by the IRC protocol."
43  (format nil "~A ~A ~2D ~2,'0D:~2,'0D:~2,'0D ~D"
44          (get-day-name day)
45          (get-month-name month)
46          date
47          hour
48          minute
49          second
50          year))
51
52(defun make-irc-message (command &key (arguments nil)
53                                 (trailing-argument nil))
54  "Return a valid IRC message, as a string, composed of the input
55parameters."
56  (let ((*print-circle* nil))
57    (format nil "~A~{ ~A~}~A~A~A~A" command arguments
58            (if trailing-argument
59                " :"
60              "")
61            (or trailing-argument "")
62            #\Return
63            #\Linefeed)))
64
65(defun make-ctcp-message (string)
66  "Return a valid IRC CTCP message, as a string, composed by
67`string'."
68  (format nil "~A~A~A" +soh+ string +soh+))
69
70(defun tokenize-string (string &key
71                               (delimiters '(#\Space #\Return #\Linefeed #\Newline)))
72  "Split string into a list, splitting on `delimiters' and removing any
73empty subsequences."
74  (split-sequence:split-sequence-if #'(lambda (character)
75                                        (member character delimiters))
76                                    string :remove-empty-subseqs t))
77
78(defun list-of-strings-to-integers (list)
79  "Take a list of strings and return a new list of integers (from
80parse-integer) on each of the string elements."
81  (let ((new-list nil))
82    (dolist (element (reverse list))
83      (push (parse-integer element) new-list))
84    new-list))
85
86(defun host-byte-order (string)
87  "Convert a string, such as 192.168.1.1, to host-byte-order, such as
883232235777."
89  (let ((list (list-of-strings-to-integers (split-sequence:split-sequence #\. string))))
90    (+ (* (first list) 256 256 256) (* (second list) 256 256)
91       (* (third list) 256) (fourth list))))
92
93(defun hbo-to-dotted-quad (integer)
94  "Host-byte-order integer to dotted-quad string conversion utility."
95  (let ((first (ldb (byte 8 24) integer))
96        (second (ldb (byte 8 16) integer))
97        (third (ldb (byte 8 8) integer))
98        (fourth (ldb (byte 8 0) integer)))
99    (format nil "~A.~A.~A.~A" first second third fourth)))
100
101(defun hbo-to-vector-quad (integer)
102  "Host-byte-order integer to dotted-quad string conversion utility."
103  (let ((first (ldb (byte 8 24) integer))
104        (second (ldb (byte 8 16) integer))
105        (third (ldb (byte 8 8) integer))
106        (fourth (ldb (byte 8 0) integer)))
107    (vector first second third fourth)))
108
109(defun cut-between (string start-char end-chars &key (start 0) (cut-extra t))
110  "If `start-char' is not nil, cut string between `start-char' and any
111of the `end-chars', from `start'.  If `start-char' is nil, cut from
112`start' until any of the `end-chars'.
113
114If `cut-extra' is t, we will cut from start + 1 instead of just
115`start'.
116
117When there is no string matching the input parameters `start' and nil
118will be returned, otherwise `end-position' and the string are
119returned."
120  (let ((end-position (position-if #'(lambda (char)
121                                       (member char end-chars))
122                                   string :start (1+ start)))
123        (cut-from (if cut-extra
124                      (1+ start)
125                      start)))
126    (if (and end-position start-char)
127        (if (eql (char string start) start-char)
128            (values end-position
129                    (subseq string cut-from end-position))
130            (values start nil))
131        (if end-position
132            (values end-position
133                    (subseq string cut-from end-position))
134            (values start nil)))))
135
136(defun cut-before (string substring end-chars &key (start 0) (cut-extra t))
137  "Cut `string' before `substring' or any of the `end-chars', from `start'.
138
139If `cut-extra' is t, we will cut from start + 1 instead of just
140`start'.
141
142When there is no string matching the input parameters `start' and nil
143will be returned, otherwise `end-position' and the string are
144returned."
145  (let ((end-position (search substring string :start2 start)))
146    (if end-position
147        (values (+ end-position (1- (length substring)))
148                (subseq string (if (and cut-extra
149                                        (< start end-position))
150                                   (1+ start) start) end-position))
151      (let ((end-position (position-if #'(lambda (x)
152                                           (member x end-chars))
153                                       string :start (1+ start)))
154            (cut-from (if cut-extra (1+ start) start)))
155        (if end-position
156            (values end-position
157                    (subseq string cut-from end-position))
158          (values start nil))))))
159
160(defun parse-isupport-prefix-argument (prefix)
161  (declare (type string prefix))
162  (let ((closing-paren-pos (position #\) prefix)))
163    (when (and (eq (elt prefix 0) #\( )
164               closing-paren-pos)
165      (let ((prefixes (subseq prefix (1+ closing-paren-pos)))
166            (modes (subseq prefix 1 closing-paren-pos)))
167        (when (= (length prefixes)
168                 (length modes))
169          (values prefixes modes))))))
170
171(defun nick-prefixes-from-isupport (isupport-arguments)
172  "Returns an assoc list associating prefix characters with mode characters."
173  (multiple-value-bind
174      (prefixes modes)
175      (parse-isupport-prefix-argument (second (assoc "PREFIX"
176                                                     isupport-arguments
177                                                     :test #'string=)))
178    (let ((rv))
179      (dotimes (i (length modes)
180                  rv)
181        (setf (getf rv (char prefixes i))
182              (char modes i))))))
183
184(defun chanmode-descs-from-isupport (isupport-arguments
185                                     &optional
186                                     (mode-symbols
187                                      *default-char-to-channel-modes-map*))
188  "Parses a string describing channel modes conforming to
189http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt
190paragraph 3.3.
191
192It returns a list of mode-description records."
193  (let* ((mode-desc-recs)
194         (pref (second (assoc "PREFIX" isupport-arguments :test #'string=)))
195         (chanmodes (second (assoc "CHANMODES" isupport-arguments
196                                   :test #'string=)))
197         (modes-list
198          (cons (second (multiple-value-list
199                         (parse-isupport-prefix-argument pref)))
200                (split-sequence:split-sequence #\, chanmodes)))
201         (mode-descs '(;; B type mode from PREFIX with nick argument
202                       (t t t list-value-mode)
203                       ;; A type mode
204                       (:optional-for-server
205                        :optional-for-server nil list-value-mode)
206                       ;; B type mode from CHANMODES
207                       (t   t   nil single-value-mode)
208                       ;; C type mode from CHANMODES
209                       (t   nil nil single-value-mode)
210                       ;; D type mode from CHANMODES
211                       (nil nil nil single-value-mode))))
212    (do ((mode (pop modes-list) (pop modes-list))
213         (mode-desc (pop mode-descs) (pop mode-descs)))
214        ((null mode-desc) mode-desc-recs)
215      (when (< 0 (length mode))
216        (let ((mode-struct
217               (make-mode-description :param-on-set-p (first mode-desc)
218                                      :param-on-unset-p (second mode-desc)
219                                      :nick-param-p (third mode-desc)
220                                      :class (fourth mode-desc))))
221          (dotimes (j (length mode))
222            (let ((mode-rec (copy-structure mode-struct))
223                  (mode-char (elt mode j)))
224              (setf (mode-desc-char mode-rec) mode-char
225                    (mode-desc-symbol mode-rec) (cdr (assoc mode-char
226                                                            mode-symbols)))
227              (push mode-rec mode-desc-recs))))))))
228
229(defmacro do-property-list ((prop val list) &body body)
230  (let ((lsym (gensym)))
231    `(let ((,lsym ,list))
232       (do* ((,prop (pop ,lsym) (pop ,lsym))
233             (,val (pop ,lsym) (pop ,lsym)))
234           ((and (null ,lsym)
235                 (null ,prop)
236                 (null ,val)))
237         ,@body))))
238
239(defgeneric irc-string-downcase (map-name string &key start end))
240
241(defmethod irc-string-downcase (map-name
242                                string &key (start 0) end)
243  (declare (ignore map-name))
244  (let* ((new-string (substitute #\[ #\{ string :start start :end end))
245         (new-string (substitute #\] #\} new-string :start start :end end))
246         (new-string (substitute #\\ #\| new-string :start start :end end))
247         (new-string (substitute #\~ #\^ new-string :start start :end end)))
248    (string-downcase new-string :start start :end end)))
249
250(defmethod irc-string-downcase ((map-name (eql :ascii))
251                                string &key (start 0) end)
252  (declare (ignore map-name))
253  (string-downcase string :start start :end end))
254
255(defun parse-isupport-multivalue-argument (argument)
256  (declare (type string argument))
257  (mapcar #'(lambda (x)
258              (split-sequence:split-sequence #\: x))
259          (split-sequence:split-sequence #\, argument)))
260
261(defun parse-mode-arguments (connection target arguments &key server-p)
262  "Create a list of mode changes with their arguments for `target'
263   from `mode-string' and `arguments'.
264
265   Throw nil to the UNKNOWN-MODE symbol if any of the mode chars are unknown."
266  (catch 'illegal-mode-spec
267    (if (and (= 1 (length arguments))
268             (null (position (char (first arguments) 0) "+-")))
269        ;; type 1 mode specification; only allowed on servers
270        (when server-p
271          (let ((ops)
272                (arg (car arguments)))
273            (dotimes (i (length arg) (reverse ops))
274              (push (char arg i) ops))))
275      ;; type 2 mode specification; clients and servers
276      (let ((ops))
277        (do ((changes (pop arguments) (pop arguments)))
278            ((null changes) (values ops nil))
279          (let* ((this-op (char changes 0))
280                 (modes (subseq changes 1))
281                 (param-req (if (char= this-op #\+)
282                                #'mode-desc-param-on-set-p
283                              #'mode-desc-param-on-unset-p)))
284            (unless (position this-op "+-")
285              (throw 'illegal-mode-spec nil))
286            (dotimes (i (length modes))
287              (let* ((mode-rec
288                      (mode-description connection target
289                                        (mode-name-from-char connection target
290                                                             (char modes i))))
291                     (param-p (funcall param-req mode-rec)))
292                (when (and param-p
293                           (= 0 (length arguments)))
294                  (throw 'illegal-mode-spec nil))
295                (push (list this-op
296                            (mode-desc-symbol mode-rec)
297                            (when param-p
298                              (if (mode-desc-nick-param-p mode-rec)
299                                  (find-user connection (pop arguments))
300                                (pop arguments)))) ops)))))))))
301
Note: See TracBrowser for help on using the repository browser.