1 | ;;;; $Id: parse-message.lisp 160 2006-05-22 20:21:38Z ehuelsmann $ |
---|
2 | ;;;; $URL: tags/0.8.1/parse-message.lisp $ |
---|
3 | |
---|
4 | ;;;; See the LICENSE file for licensing information. |
---|
5 | |
---|
6 | (in-package :irc) |
---|
7 | |
---|
8 | (defun find-reply-name (reply-number &key (reply-names *reply-names*)) |
---|
9 | "Numeric replies in the IRC RFCs have more meaningful names. Given |
---|
10 | a numeric reply (`reply-number') this function will either return the |
---|
11 | symbol representing the reply or raise a continuable error |
---|
12 | (`no-such-reply') which gives you the opportunity to ignore the |
---|
13 | situation." |
---|
14 | (let ((name (assoc reply-number reply-names))) |
---|
15 | (if name |
---|
16 | (cadr name) |
---|
17 | (progn |
---|
18 | (cerror "Ignore unknown reply." |
---|
19 | 'no-such-reply :reply-number reply-number) |
---|
20 | :unknown-reply)))) |
---|
21 | |
---|
22 | (defun return-source (string &key (start 0)) |
---|
23 | "Assuming `string' is a valid IRC message this function returns the |
---|
24 | source part of the message. Returns nil if the source part is not |
---|
25 | present." |
---|
26 | (cut-between string #\: '(#\! #\Space) :start start)) |
---|
27 | |
---|
28 | (defun return-user (string &key (start 0)) |
---|
29 | "Assuming `string' is a valid IRC message this function returns the |
---|
30 | user part of the message. Returns nil if the user part is not |
---|
31 | present." |
---|
32 | (cut-between string #\! '(#\@ #\Space) :start start)) |
---|
33 | |
---|
34 | (defun return-host (string &key (start 0)) |
---|
35 | "Assuming `string' is a valid IRC message this function returns the |
---|
36 | host part of the message. Returns nil if the host part is not |
---|
37 | present." |
---|
38 | (cut-between string #\@ '(#\Space) :start start)) |
---|
39 | |
---|
40 | (defun return-command (string &key (start 0)) |
---|
41 | "Assuming `string' is a valid IRC message this function returns the |
---|
42 | command part of the message. Returns nil if the command part is not |
---|
43 | present." |
---|
44 | (if (eql (char string start) #\Space) |
---|
45 | (cut-between string #\Space '(#\Space) :start start) |
---|
46 | (cut-between string nil '(#\Space) :start start :cut-extra nil))) |
---|
47 | |
---|
48 | (defun return-arguments (string &key (start 0)) |
---|
49 | "Assuming `string' is a valid IRC message this function returns the |
---|
50 | arguments part of the message as a list. Returns nil if the arguments |
---|
51 | part is not present." |
---|
52 | (multiple-value-bind (end-position return-argument) |
---|
53 | (cut-before string " :" '(#\Return) :start start :cut-to-end t) |
---|
54 | (values end-position (tokenize-string return-argument |
---|
55 | :delimiters '(#\Space))))) |
---|
56 | |
---|
57 | (defun return-trailing-argument (string &key (start 0)) |
---|
58 | "Assuming `string' is a valid IRC message this function returns the |
---|
59 | trailing-argument part of the message. Returns nil if the |
---|
60 | trailing-argument part is not present." |
---|
61 | (when (< start (length string)) |
---|
62 | (cut-between string #\: '(#\Return) :start start :cut-to-end t))) |
---|
63 | |
---|
64 | (defun combine-arguments-and-trailing (string &key (start 0)) |
---|
65 | (multiple-value-bind |
---|
66 | (start return-string) |
---|
67 | (return-arguments string :start start) |
---|
68 | (multiple-value-bind |
---|
69 | (return-index trailing) |
---|
70 | (return-trailing-argument string :start start) |
---|
71 | (values return-index |
---|
72 | (append return-string (when (and trailing (string/= "" trailing)) |
---|
73 | (list trailing))))))) |
---|
74 | |
---|
75 | (defun parse-raw-message (string &key (start 0)) |
---|
76 | "Assuming `string' is a valid IRC message, parse the message and |
---|
77 | return the values in the following order: |
---|
78 | |
---|
79 | - source |
---|
80 | - user |
---|
81 | - host |
---|
82 | - command |
---|
83 | - arguments |
---|
84 | - trailing-argument |
---|
85 | |
---|
86 | Any values not present will be represented as nil." |
---|
87 | (let ((index start) |
---|
88 | (returns nil)) |
---|
89 | (dolist (function '(return-source |
---|
90 | return-user |
---|
91 | return-host |
---|
92 | return-command |
---|
93 | combine-arguments-and-trailing)) |
---|
94 | (multiple-value-bind (return-index return-string) |
---|
95 | (funcall function string :start index) |
---|
96 | (setf index return-index) |
---|
97 | (push return-string returns))) |
---|
98 | (apply #'values (reverse returns)))) |
---|
99 | |
---|
100 | (defun irc-error-reply-p (string) |
---|
101 | "Returns t if `string' is a string-representation of an IRC error |
---|
102 | reply message, nil otherwise." |
---|
103 | (unless (zerop (length string)) |
---|
104 | (if (and (every #'digit-char-p string) |
---|
105 | (member (char string 0) '(#\4 #\5))) |
---|
106 | t |
---|
107 | nil))) |
---|
108 | |
---|
109 | (defun numeric-reply-p (string) |
---|
110 | "Returns t if `string' is a string-representation of an IRC number |
---|
111 | reply, nil otherwise." |
---|
112 | (every #'digit-char-p string)) |
---|
113 | |
---|
114 | (defun ctcp-type-p (string type) |
---|
115 | "Is the `string' actually a representation of the CTCP `type'?" |
---|
116 | (if (string-equal (substring string 1 (min (length string) |
---|
117 | (1+ (length (symbol-name type))))) |
---|
118 | type) |
---|
119 | type |
---|
120 | nil)) |
---|
121 | |
---|
122 | (defun dcc-type-p (string type) |
---|
123 | "Is the `string' actually a representation of the DCC `type'?" |
---|
124 | (case type |
---|
125 | (:dcc-chat-request |
---|
126 | (when (string-equal (char string 5) #\C) |
---|
127 | :dcc-chat-request)) |
---|
128 | (:dcc-send-request |
---|
129 | (when (string-equal (char string 5) #\S) |
---|
130 | :dcc-send-request)) |
---|
131 | (otherwise nil))) |
---|
132 | |
---|
133 | (defun ctcp-message-type (string) |
---|
134 | "If `string' is a CTCP message, return the type of the message or |
---|
135 | nil if this is a) not a CTCP message or b) a CTCP message we don't |
---|
136 | know about." |
---|
137 | (if (or (not (stringp string)) |
---|
138 | (zerop (length string)) |
---|
139 | (not (eql (char string 0) +soh+))) |
---|
140 | nil |
---|
141 | (case (char string 1) |
---|
142 | (#\A (ctcp-type-p string :action)) |
---|
143 | (#\C (ctcp-type-p string :clientinfo)) |
---|
144 | (#\D |
---|
145 | (or (dcc-type-p string :dcc-chat-request) |
---|
146 | (dcc-type-p string :dcc-send-request))) |
---|
147 | (#\F (ctcp-type-p string :finger)) |
---|
148 | (#\P (ctcp-type-p string :ping)) |
---|
149 | (#\S (ctcp-type-p string :source)) |
---|
150 | (#\T (ctcp-type-p string :time)) |
---|
151 | (#\U (ctcp-type-p string :userinfo)) |
---|
152 | (#\V (ctcp-type-p string :version)) |
---|
153 | (otherwise nil)))) |
---|
154 | |
---|
155 | (defun create-irc-message (string) |
---|
156 | "If `string' is a valid IRC message parse it and return an object of |
---|
157 | the correct type with its slots prefilled according to the information |
---|
158 | in the message." |
---|
159 | (multiple-value-bind (source user host command arguments) |
---|
160 | (parse-raw-message string) |
---|
161 | (let* ((class 'irc-message) |
---|
162 | (trailing-argument (car (last arguments))) |
---|
163 | (ctcp (ctcp-message-type trailing-argument))) |
---|
164 | (when command |
---|
165 | (cond |
---|
166 | ;;((irc-error-reply-p command) |
---|
167 | ;; Disable for now, as it prevents adding hooks for some useful |
---|
168 | ;; error types |
---|
169 | ;;(progn |
---|
170 | ;; (setf command (find-reply-name (parse-integer command))) |
---|
171 | ;; (setf class 'irc-error-reply))) |
---|
172 | ((numeric-reply-p command) |
---|
173 | (progn |
---|
174 | (setf command (find-reply-name (parse-integer command))) |
---|
175 | (setf class (find-irc-message-class command)))) |
---|
176 | (t |
---|
177 | (progn |
---|
178 | (setf command (intern (string-upcase command) |
---|
179 | (find-package :keyword))) |
---|
180 | (setf class (find-irc-message-class command)))))) |
---|
181 | (when ctcp |
---|
182 | (setf class (find-ctcp-message-class ctcp))) |
---|
183 | (let ((instance (make-instance class |
---|
184 | :source (or source "") |
---|
185 | :user (or user "") |
---|
186 | :host (or host "") |
---|
187 | :command (if command |
---|
188 | (string command) |
---|
189 | "") |
---|
190 | :arguments arguments |
---|
191 | :connection nil |
---|
192 | :received-time (get-universal-time) |
---|
193 | :raw-message-string (or string "")))) |
---|
194 | (when ctcp |
---|
195 | (setf (ctcp-command instance) ctcp)) |
---|
196 | instance)))) |
---|