]> crepu.dev Git - config.git/blob - djavu-asus/elpa/jabber-20230715.456/jabber-muc.el
Actualizado el Readme
[config.git] / djavu-asus / elpa / jabber-20230715.456 / jabber-muc.el
1 ;; jabber-muc.el - advanced MUC functions -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2010 - Kirill A. Korinskiy - catap@catap.ru
4 ;; Copyright (C) 2003, 2004, 2007, 2008, 2009, 2010 - Magnus Henoch - mange@freemail.hu
5 ;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
6
7 ;; This file is a part of jabber.el.
8
9 ;; This program is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2 of the License, or
12 ;; (at your option) any later version.
13
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program; if not, write to the Free Software
21 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23 (require 'jabber-chat)
24 (require 'jabber-widget)
25 (require 'jabber-disco)
26 (require 'jabber-muc-nick-coloring)
27
28 ;; we need jabber-bookmarks for jabber-muc-autojoin (via
29 ;; jabber-get-bookmarks and jabber-parse-conference-bookmark):
30 (require 'jabber-bookmarks)
31
32 (require 'cl-lib)
33
34 ;;;###autoload
35 (defvar *jabber-active-groupchats* nil
36 "Alist of groupchats and nicknames.
37 Keys are strings, the bare JID of the room.
38 Values are strings.")
39
40 (defvar jabber-pending-groupchats (make-hash-table)
41 "Hash table of groupchats and nicknames.
42 Keys are JID symbols; values are strings.
43 This table records the last nickname used to join the particular
44 chat room. Items are thus never removed.")
45
46 (defvar jabber-muc-participants nil
47 "Alist of groupchats and participants.
48 Keys are strings, the bare JID of the room.
49 Values are lists of nickname strings.")
50
51 (defvar jabber-group nil
52 "The groupchat you are participating in.")
53
54 (defvar jabber-muc-topic ""
55 "The topic of the current MUC room.")
56
57 (defvar jabber-role-history ()
58 "Keeps track of previously used roles.")
59
60 (defvar jabber-affiliation-history ()
61 "Keeps track of previously used affiliations.")
62
63 (defvar jabber-muc-nickname-history ()
64 "Keeps track of previously referred-to nicknames.")
65
66 (defcustom jabber-muc-default-nicknames nil
67 "Default nickname for specific MUC rooms."
68 :group 'jabber-chat
69 :type '(repeat
70 (cons :format "%v"
71 (string :tag "JID of room")
72 (string :tag "Nickname"))))
73
74 (defcustom jabber-muc-autojoin nil
75 "List of MUC rooms to automatically join on connection.
76 This list is saved in your Emacs customizations. You can also store
77 such a list on the Jabber server, where it is available to every
78 client; see `jabber-edit-bookmarks'."
79 :group 'jabber-chat
80 :type '(repeat (string :tag "JID of room")))
81
82 (defcustom jabber-muc-disable-disco-check nil
83 "If non-nil, disable checking disco#info of rooms before joining them.
84 Disco information can tell whether the room exists and whether it is
85 password protected, but some servers do not support it. If you want
86 to join chat rooms on such servers, set this variable to t."
87 :group 'jabber-chat
88 :type 'boolean)
89
90 (defcustom jabber-groupchat-buffer-format "*-jabber-groupchat-%n-*"
91 "The format specification for the name of groupchat buffers.
92
93 These fields are available (all are about the group you are chatting
94 in):
95
96 %n Roster name of group, or JID if no nickname set
97 %b Name of group from bookmarks or roster name or JID if none set
98 %j Bare JID (without resource)"
99 :type 'string
100 :group 'jabber-chat)
101
102 (defcustom jabber-groupchat-prompt-format "[%t] %n> "
103 "The format specification for lines in groupchat.
104
105 These fields are available:
106
107 %t Time, formatted according to `jabber-chat-time-format'
108 %n, %u, %r
109 Nickname in groupchat
110 %j Full JID (room@server/nick)"
111 :type 'string
112 :group 'jabber-chat)
113
114 (defcustom jabber-muc-header-line-format
115 '(" " (:eval (jabber-jid-displayname jabber-group))
116 "\t" jabber-muc-topic)
117 "The specification for the header line of MUC buffers.
118
119 The format is that of `mode-line-format' and `header-line-format'."
120 :type 'sexp
121 :group 'jabber-chat)
122
123 (defcustom jabber-muc-private-buffer-format "*-jabber-muc-priv-%g-%n-*"
124 "The format specification for the buffer name for private MUC messages.
125
126 These fields are available:
127
128 %g Roster name of group, or JID if no nickname set
129 %n Nickname of the group member you're chatting with"
130 :type 'string
131 :group 'jabber-chat)
132
133 (defcustom jabber-muc-private-foreign-prompt-format "[%t] %g/%n> "
134 "The format specification for lines others type in a private MUC buffer.
135
136 These fields are available:
137
138 %t Time, formatted according to `jabber-chat-time-format'
139 %n Nickname in room
140 %g Short room name (either roster name or username part of JID)"
141 :type 'string
142 :group 'jabber-chat)
143
144 (defcustom jabber-muc-print-names-format " %n %a %j\n"
145 "The format specification for MUC list lines.
146
147 Fields available:
148
149 %n Nickname in room
150 %a Affiliation status
151 %j Full JID (room@server/nick)"
152 :type 'string
153 :group 'jabber-chat)
154
155 (defcustom jabber-muc-private-header-line-format
156 '(" " (:eval (jabber-jid-resource jabber-chatting-with))
157 " in " (:eval (jabber-jid-displayname (jabber-jid-user jabber-chatting-with)))
158 "\t" jabber-events-message
159 "\t" jabber-chatstates-message)
160 "The specification for the header line of private MUC chat buffers.
161
162 The format is that of `mode-line-format' and `header-line-format'."
163 :type 'sexp
164 :group 'jabber-chat)
165
166 ;;;###autoload
167 (defvar jabber-muc-printers '()
168 "List of functions that may be able to print part of a MUC message.
169 This gets prepended to `jabber-chat-printers', which see.")
170
171 ;;;###autoload
172 (defun jabber-muc-get-buffer (group)
173 "Return the chat buffer for chatroom GROUP.
174 Either a string or a buffer is returned, so use `get-buffer' or
175 `get-buffer-create'."
176 (format-spec jabber-groupchat-buffer-format
177 (list
178 (cons ?n (jabber-jid-displayname group))
179 (cons ?b (jabber-jid-bookmarkname group))
180 (cons ?j (jabber-jid-user group)))))
181
182 (defun jabber-muc-create-buffer (jc group)
183 "Prepare a buffer for chatroom GROUP.
184 This function is idempotent.
185
186 JC is the Jabber connection."
187 (with-current-buffer (get-buffer-create (jabber-muc-get-buffer group))
188 (unless (eq major-mode 'jabber-chat-mode)
189 (jabber-chat-mode jc #'jabber-chat-pp))
190 ;; Make sure the connection variable is up to date.
191 (setq jabber-buffer-connection jc)
192
193 (set (make-local-variable 'jabber-group) group)
194 (make-local-variable 'jabber-muc-topic)
195 (setq jabber-send-function 'jabber-muc-send)
196 (setq header-line-format jabber-muc-header-line-format)
197 (current-buffer)))
198
199 ;;;###autoload
200 (defun jabber-muc-private-get-buffer (group nickname)
201 "Return the chat buffer for private chat with NICKNAME in GROUP.
202 Either a string or a buffer is returned, so use `get-buffer' or
203 `get-buffer-create'."
204 (format-spec jabber-muc-private-buffer-format
205 (list
206 (cons ?g (jabber-jid-displayname group))
207 (cons ?n nickname))))
208
209 (defun jabber-muc-private-create-buffer (jc group nickname)
210 "Prepare a buffer for chatting with NICKNAME in GROUP.
211 This function is idempotent.
212
213 JC is the Jabber connection."
214 (with-current-buffer (get-buffer-create (jabber-muc-private-get-buffer group nickname))
215 (unless (eq major-mode 'jabber-chat-mode)
216 (jabber-chat-mode jc #'jabber-chat-pp))
217
218 (set (make-local-variable 'jabber-chatting-with) (concat group "/" nickname))
219 (setq jabber-send-function 'jabber-chat-send)
220 (setq header-line-format jabber-muc-private-header-line-format)
221
222 (current-buffer)))
223
224 (defun jabber-muc-send (jc body)
225 "Send BODY to MUC room in current buffer.
226
227 JC is the Jabber connection."
228 ;; There is no need to display the sent message in the buffer, as
229 ;; we will get it back from the MUC server.
230 (jabber-send-sexp jc
231 `(message
232 ((to . ,jabber-group)
233 (type . "groupchat"))
234 (body () ,body))))
235
236 (defun jabber-muc-add-groupchat (group nickname)
237 "Remember participating in GROUP under NICKNAME."
238 (let ((whichgroup (assoc group *jabber-active-groupchats*)))
239 (if whichgroup
240 (setcdr whichgroup nickname)
241 (add-to-list '*jabber-active-groupchats* (cons group nickname)))))
242
243 (defun jabber-muc-remove-groupchat (group)
244 "Remove GROUP from internal bookkeeping."
245 (let ((whichgroup (assoc group *jabber-active-groupchats*))
246 (whichparticipants (assoc group jabber-muc-participants)))
247 (setq *jabber-active-groupchats*
248 (delq whichgroup *jabber-active-groupchats*))
249 (setq jabber-muc-participants
250 (delq whichparticipants jabber-muc-participants))))
251
252 (defun jabber-muc-connection-closed (bare-jid)
253 "Remove MUC data for BARE-JID.
254 Forget all information about rooms that had been entered with
255 this JID. Suitable to call when the connection is closed."
256 (dolist (room-entry jabber-muc-participants)
257 (let* ((room (car room-entry))
258 (buffer (get-buffer (jabber-muc-get-buffer room))))
259 (when (bufferp buffer)
260 (with-current-buffer buffer
261 (when (string= bare-jid
262 (jabber-connection-bare-jid jabber-buffer-connection))
263 (setq *jabber-active-groupchats*
264 (cl-delete room *jabber-active-groupchats*
265 :key #'car :test #'string=))
266 (setq jabber-muc-participants
267 (delq room-entry jabber-muc-participants))))))))
268
269 (defun jabber-muc-participant-plist (group nickname)
270 "Return plist associated with NICKNAME in GROUP.
271 Return nil if nothing known about that combination."
272 (let ((whichparticipants (assoc group jabber-muc-participants)))
273 (when whichparticipants
274 (cdr (assoc nickname whichparticipants)))))
275
276 (defun jabber-muc-modify-participant (group nickname new-plist)
277 "Assign properties in NEW-PLIST to NICKNAME in GROUP."
278 (let ((participants (assoc group jabber-muc-participants)))
279 ;; either we have a list of participants already...
280 (if participants
281 (let ((participant (assoc nickname participants)))
282 ;; and maybe this participant is already in the list
283 (if participant
284 ;; if so, just update role, affiliation, etc.
285 (setf (cdr participant) new-plist)
286 (push (cons nickname new-plist) (cdr participants))))
287 ;; or we don't
288 (push (cons group (list (cons nickname new-plist))) jabber-muc-participants))))
289
290 (defun jabber-muc-report-delta (nickname old-plist new-plist reason actor)
291 "Compare OLD-PLIST and NEW-PLIST, and return a string explaining the change.
292 Return nil if nothing noteworthy has happened.
293 NICKNAME is the user experiencing the change. REASON and ACTOR, if non-nil,
294 are the corresponding presence fields.
295
296 This function is only concerned with presence stanzas resulting
297 in the user entering/staying in the room."
298 ;; The keys in the plist are affiliation, role and jid.
299 (when (plist-get new-plist 'jid)
300 ;; nickname is only used for displaying, so we can modify it if we
301 ;; want to.
302 (setq nickname (concat nickname " <"
303 (jabber-jid-user (plist-get new-plist 'jid))
304 ">")))
305 (cond
306 ((null old-plist)
307 ;; User enters the room
308 (concat nickname " enters the room ("
309 (plist-get new-plist 'role)
310 (unless (string= (plist-get new-plist 'affiliation) "none")
311 (concat ", " (plist-get new-plist 'affiliation)))
312 ")"))
313
314 ;; If affiliation changes, the role change is usually the logical
315 ;; one, so don't report it separately.
316 ((not (string= (plist-get old-plist 'affiliation)
317 (plist-get new-plist 'affiliation)))
318 (let ((actor-reason (concat (when actor
319 (concat " by " actor))
320 (when reason
321 (concat ": " reason))))
322 (from (plist-get old-plist 'affiliation))
323 (to (plist-get new-plist 'affiliation)))
324 ;; There are many ways to express these transitions in English.
325 ;; This one favors eloquence over regularity and consistency.
326 (cond
327 ;; Higher affiliation
328 ((or (and (member from '("outcast" "none" "member"))
329 (member to '("admin" "owner")))
330 (and (string= from "admin") (string= to "owner")))
331 (concat nickname " has been promoted to " to actor-reason))
332 ;; Lower affiliation
333 ((or (and (member from '("owner" "admin"))
334 (string= to "member"))
335 (and (string= from "owner") (string= to "admin")))
336 (concat nickname " has been demoted to " to actor-reason))
337 ;; Become member
338 ((string= to "member")
339 (concat nickname " has been granted membership" actor-reason))
340 ;; Lose membership
341 ((string= to "none")
342 (concat nickname " has been deprived of membership" actor-reason)))))
343
344 ;; Role changes
345 ((not (string= (plist-get old-plist 'role)
346 (plist-get new-plist 'role)))
347 (let ((actor-reason (concat (when actor
348 (concat " by " actor))
349 (when reason
350 (concat ": " reason))))
351 (from (plist-get old-plist 'role))
352 (to (plist-get new-plist 'role)))
353 ;; Possible roles are "none" (not in room, hence not of interest
354 ;; in this function), "visitor" (no voice), "participant" (has
355 ;; voice), and "moderator".
356 (cond
357 ((string= to "moderator")
358 (concat nickname " has been granted moderator privileges" actor-reason))
359 ((and (string= from "moderator")
360 (string= to "participant"))
361 (concat nickname " had moderator privileges revoked" actor-reason))
362 ((string= to "participant")
363 (concat nickname " has been granted voice" actor-reason))
364 ((string= to "visitor")
365 (concat nickname " has been denied voice" actor-reason)))))))
366
367 (defun jabber-muc-remove-participant (group nickname)
368 "Forget everything about NICKNAME in GROUP."
369 (let ((participants (assoc group jabber-muc-participants)))
370 (when participants
371 (let ((participant (assoc nickname (cdr participants))))
372 (setf (cdr participants) (delq participant (cdr participants)))))))
373
374 (defmacro jabber-muc-argument-list (&optional args)
375 "Prepend connection and group name to ARGS.
376 If the current buffer is not an MUC buffer, signal an error.
377 This macro is meant for use as an argument to `interactive'."
378 `(if (null jabber-group)
379 (error "Not in MUC buffer")
380 (nconc (list jabber-buffer-connection jabber-group) ,args)))
381
382 (defun jabber-muc-read-completing (prompt &optional allow-not-joined)
383 "Read the name of a joined chatroom, or use chatroom of current buffer if any.
384 If ALLOW-NOT-JOINED is provided and non-nil, permit choosing any
385 JID; only provide completion as a guide."
386 (or jabber-group
387 (jabber-read-jid-completing prompt
388 (if (null *jabber-active-groupchats*)
389 (error "You haven't joined any group")
390 (mapcar (lambda (x) (jabber-jid-symbol (car x)))
391 *jabber-active-groupchats*))
392 (not allow-not-joined)
393 jabber-group)))
394
395 (defun jabber-muc-read-nickname (group prompt)
396 "Read the nickname of a participant in GROUP."
397 (let ((nicknames (cdr (assoc group jabber-muc-participants))))
398 (unless nicknames
399 (error "Unknown group: %s" group))
400 (completing-read prompt nicknames nil t nil 'jabber-muc-nickname-history)))
401
402 (add-to-list 'jabber-jid-muc-menu
403 (cons "Request vcard" 'jabber-muc-vcard-get))
404
405 ;;;###autoload
406 (defun jabber-muc-vcard-get (jc group nickname)
407 "Request vcard from chat with NICKNAME in GROUP.
408
409 JC is the Jabber connection."
410 (interactive
411 (jabber-muc-argument-list
412 (list (jabber-muc-read-nickname jabber-group "Nickname: "))))
413 (let ((muc-name (format "%s/%s" group nickname)))
414 (jabber-vcard-get jc muc-name)))
415
416 (defun jabber-muc-instant-config (jc group)
417 "Accept default configuration for GROUP.
418 This can be used for a newly created room, as an alternative to
419 filling out the configuration form with `jabber-muc-get-config'.
420 Both of these methods unlock the room, so that other users can
421 enter it.
422
423 JC is the Jabber connection."
424 (interactive (jabber-muc-argument-list))
425 (jabber-send-iq jc group
426 "set"
427 '(query ((xmlns . "http://jabber.org/protocol/muc#owner"))
428 (x ((xmlns . "jabber:x:data") (type . "submit"))))
429 #'jabber-report-success "MUC instant configuration"
430 #'jabber-report-success "MUC instant configuration"))
431
432 (add-to-list 'jabber-jid-muc-menu
433 (cons "Configure groupchat" 'jabber-muc-get-config))
434
435 (defun jabber-muc-get-config (jc group)
436 "Ask for MUC configuration form.
437
438 JC is the Jabber connection."
439 (interactive (jabber-muc-argument-list))
440 (jabber-send-iq jc group
441 "get"
442 '(query ((xmlns . "http://jabber.org/protocol/muc#owner")))
443 #'jabber-process-data #'jabber-muc-render-config
444 #'jabber-process-data "MUC configuration request failed"))
445
446 (defalias 'jabber-groupchat-get-config 'jabber-muc-get-config
447 "Deprecated. See `jabber-muc-get-config' instead.")
448
449 (defun jabber-muc-render-config (jc xml-data)
450 "Render MUC configuration form.
451
452 JC is the Jabber connection.
453 XML-DATA is the parsed tree data from the stream (stanzas)
454 obtained from `xml-parse-region'."
455
456 (let ((query (jabber-iq-query xml-data))
457 xdata)
458 (dolist (x (jabber-xml-get-children query 'x))
459 (if (string= (jabber-xml-get-attribute x 'xmlns) "jabber:x:data")
460 (setq xdata x)))
461 (if (not xdata)
462 (insert "No configuration possible.\n")
463
464 (jabber-init-widget-buffer (jabber-xml-get-attribute xml-data 'from))
465 (setq jabber-buffer-connection jc)
466
467 (jabber-render-xdata-form xdata)
468
469 (widget-create 'push-button :notify #'jabber-muc-submit-config "Submit")
470 (widget-insert "\t")
471 (widget-create 'push-button :notify #'jabber-muc-cancel-config "Cancel")
472 (widget-insert "\n")
473
474 (widget-setup)
475 (widget-minor-mode 1))))
476
477 (defalias 'jabber-groupchat-render-config 'jabber-muc-render-config
478 "Deprecated. See `jabber-muc-render-config' instead.")
479
480 (defun jabber-muc-submit-config (&rest ignore)
481 "Submit MUC configuration form."
482
483 (jabber-send-iq jabber-buffer-connection jabber-submit-to
484 "set"
485 `(query ((xmlns . "http://jabber.org/protocol/muc#owner"))
486 ,(jabber-parse-xdata-form))
487 #'jabber-report-success "MUC configuration"
488 #'jabber-report-success "MUC configuration"))
489
490 (defalias 'jabber-groupchat-submit-config 'jabber-muc-submit-config
491 "Deprecated. See `jabber-muc-submit-config' instead.")
492
493 (defun jabber-muc-cancel-config (&rest ignore)
494 "Cancel MUC configuration form."
495
496 (jabber-send-iq jabber-buffer-connection jabber-submit-to
497 "set"
498 '(query ((xmlns . "http://jabber.org/protocol/muc#owner"))
499 (x ((xmlns . "jabber:x:data") (type . "cancel"))))
500 nil nil nil nil))
501
502 (defalias 'jabber-groupchat-cancel-config 'jabber-muc-cancel-config
503 "Deprecated. See `jabber-muc-cancel-config' instead.")
504
505 (add-to-list 'jabber-jid-muc-menu
506 (cons "Join groupchat" 'jabber-muc-join))
507
508 (defun jabber-muc-join (jc group nickname &optional popup)
509 "Join a groupchat, or change nick.
510 In interactive calls, or if POPUP is non-nil, switch to the
511 groupchat buffer.
512
513 JC is the Jabber connection."
514 (interactive
515 (let ((account (jabber-read-account))
516 (group (jabber-read-jid-completing "group: ")))
517 (list account group (jabber-muc-read-my-nickname account group) t)))
518
519 ;; If the user is already in the room, we don't need as many checks.
520 (if (or (assoc group *jabber-active-groupchats*)
521 ;; Or if the users asked us not to check disco info.
522 jabber-muc-disable-disco-check)
523 (jabber-muc-join-3 jc group nickname nil popup)
524 ;; Else, send a disco request to find out what we are connecting
525 ;; to.
526 (jabber-disco-get-info jc group nil #'jabber-muc-join-2
527 (list group nickname popup))))
528
529 (defalias 'jabber-groupchat-join 'jabber-muc-join
530 "Deprecated. Use `jabber-muc-join' instead.")
531
532 (defun jabber-muc-join-2 (jc closure result)
533 (cl-destructuring-bind (group nickname popup) closure
534 (let* ( ;; Either success...
535 (identities (car result))
536 (features (cadr result))
537 ;; ...or error
538 (condition (when (eq identities 'error) (jabber-error-condition result))))
539 (cond
540 ;; Maybe the room doesn't exist yet.
541 ((eq condition 'item-not-found)
542 (unless (or jabber-silent-mode
543 (y-or-n-p (format "%s doesn't exist. Create it? "
544 (jabber-jid-displayname group))))
545 (error "Non-existent groupchat")))
546
547 ;; Maybe the room doesn't support disco.
548 ((eq condition 'feature-not-implemented)
549 t ;whatever... we will ignore it later
550 )
551 ;; Maybe another error occurred. Report it to user
552 (condition
553 (message "Couldn't query groupchat: %s" (jabber-parse-error result)))
554
555 ;; Bad stanza? Without NS, for example
556 ((and (eq identities 'error) (not condition))
557 (message "Bad error stanza received")))
558
559 ;; Continue only if it is really chat room. If there was an
560 ;; error, give the chat room the benefit of the doubt. (Needed
561 ;; for ejabberd's mod_irc, for example)
562 (when (or condition
563 (cl-find "conference" (if (sequencep identities) identities nil)
564 :key (lambda (i) (aref i 1))
565 :test #'string=))
566 (let ((password
567 ;; Is the room password-protected?
568 (when (member "muc_passwordprotected" features)
569 (or
570 (jabber-get-conference-data jc group nil :password)
571 (read-passwd (format "Password for %s: " (jabber-jid-displayname group)))))))
572
573 (jabber-muc-join-3 jc group nickname password popup))))))
574
575 (defalias 'jabber-groupchat-join-2 'jabber-muc-join-2
576 "Deprecated. See `jabber-muc-join-2' instead.")
577
578 (defun jabber-muc-join-3 (jc group nickname password popup)
579
580 ;; Remember that this is a groupchat _before_ sending the stanza.
581 ;; The response might come quicker than you think.
582
583 (puthash (jabber-jid-symbol group) nickname jabber-pending-groupchats)
584
585 (jabber-send-sexp jc
586 `(presence ((to . ,(format "%s/%s" group nickname)))
587 (x ((xmlns . "http://jabber.org/protocol/muc"))
588 ,@(when password
589 `((password () ,password))))
590 ,@(jabber-presence-children jc)))
591
592 ;; There, stanza sent. Now we just wait for the MUC service to
593 ;; mirror the stanza. This is handled in
594 ;; `jabber-muc-process-presence', where a buffer will be created for
595 ;; the room.
596
597 ;; But if the user interactively asked to join, he/she probably
598 ;; wants the buffer to pop up right now.
599 (when popup
600 (let ((buffer (jabber-muc-create-buffer jc group)))
601 (switch-to-buffer buffer))))
602
603 (defalias 'jabber-groupchat-join-3 'jabber-muc-join-3
604 "Deprecated. See `jabber-muc-join-3' instead.")
605
606 (defun jabber-muc-read-my-nickname (jc group &optional default)
607 "Read nickname for joining GROUP.
608 If DEFAULT is non-nil, return default nick without prompting.
609
610 JC is the Jabber connection."
611 (let ((default-nickname (or
612 (jabber-get-conference-data jc group nil :nick)
613 (cdr (assoc group jabber-muc-default-nicknames))
614 (plist-get (fsm-get-state-data jc) :username))))
615 (if default
616 default-nickname
617 (jabber-read-with-input-method (format "Nickname: (default %s) "
618 default-nickname)
619 nil nil default-nickname))))
620
621 (add-to-list 'jabber-jid-muc-menu
622 (cons "Change nickname" 'jabber-muc-nick))
623
624 (defalias 'jabber-muc-nick 'jabber-muc-join)
625
626 (add-to-list 'jabber-jid-muc-menu
627 (cons "Leave groupchat" 'jabber-muc-leave))
628
629 (defun jabber-muc-leave (jc group)
630 "Leave a groupchat.
631
632 JC is the Jabber connection."
633 (interactive (jabber-muc-argument-list))
634 (let ((whichgroup (assoc group *jabber-active-groupchats*)))
635 ;; send unavailable presence to our own nick in room
636 (jabber-send-sexp jc
637 `(presence ((to . ,(format "%s/%s" group (cdr whichgroup)))
638 (type . "unavailable"))))))
639
640 (defalias 'jabber-groupchat-leave 'jabber-muc-leave
641 "Deprecated. Use `jabber-muc-leave' instead.")
642
643 (add-to-list 'jabber-jid-muc-menu
644 (cons "List participants" 'jabber-muc-names))
645
646 (defun jabber-muc-names ()
647 "Print names, affiliations, and roles of participants in current buffer."
648 (interactive)
649 (ewoc-enter-last jabber-chat-ewoc (list :notice
650 (jabber-muc-print-names
651 (cdr (assoc jabber-group jabber-muc-participants)))
652 :time (current-time))))
653
654 (defun jabber-muc-format-names (participant)
655 "Format one participant name."
656 (format-spec jabber-muc-print-names-format
657 (list
658 (cons ?n (car participant))
659 (cons ?a (plist-get (cdr participant) 'affiliation))
660 (cons ?j (or (plist-get (cdr participant) 'jid) "")))))
661
662 (defun jabber-muc-print-names (participants)
663 "Format and return data in PARTICIPANTS."
664 (let ((mlist) (plist) (vlist) (nlist))
665 (mapcar (lambda (x)
666 (let ((role (plist-get (cdr x) 'role)))
667 (cond ((string= role "moderator")
668 (add-to-list 'mlist x))
669 ((string= role "participant")
670 (add-to-list 'plist x))
671 ((string= role "visitor")
672 (add-to-list 'vlist x))
673 ((string= role "none")
674 (add-to-list 'nlist x)))))
675 participants)
676 (concat
677 (apply 'concat "\nModerators:\n" (mapcar 'jabber-muc-format-names mlist))
678 (apply 'concat "\nParticipants:\n" (mapcar 'jabber-muc-format-names plist))
679 (apply 'concat "\nVisitors:\n" (mapcar 'jabber-muc-format-names vlist))
680 (apply 'concat "\nNones:\n" (mapcar 'jabber-muc-format-names nlist)))))
681
682 (add-to-list 'jabber-jid-muc-menu
683 (cons "Set topic" 'jabber-muc-set-topic))
684
685 (defun jabber-muc-set-topic (jc group topic)
686 "Set topic of GROUP to TOPIC.
687
688 JC is the Jabber connection."
689 (interactive
690 (jabber-muc-argument-list
691 (list (jabber-read-with-input-method "New topic: " jabber-muc-topic))))
692 (jabber-send-message jc group topic nil "groupchat"))
693
694 (defun jabber-muc-snarf-topic (xml-data)
695 "Record subject (topic) of the given <message/>, if any.
696
697 XML-DATA is the parsed tree data from the stream (stanzas)
698 obtained from `xml-parse-region'."
699 (let ((new-topic (jabber-xml-path xml-data '(subject ""))))
700 (when new-topic
701 (setq jabber-muc-topic new-topic))))
702
703 (add-to-list 'jabber-jid-muc-menu
704 (cons "Set role (kick, voice, op)" 'jabber-muc-set-role))
705
706 (defun jabber-muc-set-role (jc group nickname role reason)
707 "Set role of NICKNAME in GROUP to ROLE, specifying REASON.
708
709 JC is the Jabber connection."
710 (interactive
711 (jabber-muc-argument-list
712 (let ((nickname (jabber-muc-read-nickname jabber-group "Nickname: ")))
713 (list nickname
714 (completing-read "New role: " '(("none") ("visitor") ("participant") ("moderator")) nil t nil 'jabber-role-history)
715 (read-string "Reason: ")))))
716 (unless (or (zerop (length nickname)) (zerop (length role)))
717 (jabber-send-iq jc group "set"
718 `(query ((xmlns . "http://jabber.org/protocol/muc#admin"))
719 (item ((nick . ,nickname)
720 (role . ,role))
721 ,(unless (zerop (length reason))
722 `(reason () ,reason))))
723 'jabber-report-success "Role change"
724 'jabber-report-success "Role change")))
725
726 (add-to-list 'jabber-jid-muc-menu
727 (cons "Set affiliation (ban, member, admin)" 'jabber-muc-set-affiliation))
728
729 (defun jabber-muc-set-affiliation (jc group nickname-or-jid nickname-p affiliation reason)
730 "Set affiliation of NICKNAME-OR-JID in GROUP to AFFILIATION.
731 If NICKNAME-P is non-nil, NICKNAME-OR-JID is a nickname in the
732 group, else it is a JID.
733
734 JC is the Jabber connection."
735 (interactive
736 (jabber-muc-argument-list
737 (let ((nickname-p (y-or-n-p "Specify user by room nickname? ")))
738 (list
739 (if nickname-p
740 (jabber-muc-read-nickname jabber-group "Nickname: ")
741 (jabber-read-jid-completing "User: "))
742 nickname-p
743 (completing-read "New affiliation: "
744 '(("none") ("outcast") ("member") ("admin") ("owner")) nil t nil 'jabber-affiliation-history)
745 (read-string "Reason: ")))))
746 (let ((jid
747 (if nickname-p
748 (let ((participants (cdr (assoc group jabber-muc-participants))))
749 (unless participants
750 (error "Couldn't find group %s" group))
751 (let ((participant (cdr (assoc nickname-or-jid participants))))
752 (unless participant
753 (error "Couldn't find %s in group %s" nickname-or-jid group))
754 (or (plist-get participant 'jid)
755 (error "JID of %s in group %s is unknown" nickname-or-jid group))))
756 nickname-or-jid)))
757 (jabber-send-iq jc group "set"
758 `(query ((xmlns . "http://jabber.org/protocol/muc#admin"))
759 (item ((jid . ,jid)
760 (affiliation . ,affiliation))
761 ,(unless (zerop (length reason))
762 `(reason () ,reason))))
763 'jabber-report-success "Affiliation change"
764 'jabber-report-success "Affiliation change")))
765
766 (add-to-list 'jabber-jid-muc-menu
767 (cons "Invite someone to chatroom" 'jabber-muc-invite))
768
769 (defun jabber-muc-invite (jc jid group reason)
770 "Invite JID to GROUP, stating REASON.
771
772 JC is the Jabber connection."
773 (interactive
774 (list (jabber-read-account)
775 (jabber-read-jid-completing
776 "Invite whom: "
777 ;; The current room is _not_ a good default for whom to invite.
778 (remq (jabber-jid-symbol jabber-group) (jabber-concat-rosters)))
779 (jabber-muc-read-completing "To group: ")
780 (jabber-read-with-input-method "Reason: ")))
781 (jabber-send-sexp
782 jc
783 `(message ((to . ,group))
784 (x ((xmlns . "http://jabber.org/protocol/muc#user"))
785 (invite ((to . ,jid))
786 ,(unless (zerop (length reason))
787 `(reason nil ,reason)))))))
788
789 (add-to-list 'jabber-body-printers 'jabber-muc-print-invite)
790
791 (defun jabber-muc-print-invite (xml-data who mode)
792 "Print MUC invitation.
793
794 XML-DATA is the parsed tree data from the stream (stanzas)
795 obtained from `xml-parse-region'."
796 (cl-dolist (x (jabber-xml-get-children xml-data 'x))
797 (when (string= (jabber-xml-get-attribute x 'xmlns) "http://jabber.org/protocol/muc#user")
798 (let ((invitation (car (jabber-xml-get-children x 'invite))))
799 (when invitation
800 (when (eql mode :insert)
801 (let ((group (jabber-xml-get-attribute xml-data 'from))
802 (inviter (jabber-xml-get-attribute invitation 'from))
803 (reason (car (jabber-xml-node-children (car (jabber-xml-get-children invitation 'reason))))))
804 ;; XXX: password
805 (insert "You have been invited to MUC room " (jabber-jid-displayname group))
806 (when inviter
807 (insert " by " (jabber-jid-displayname inviter)))
808 (insert ".")
809 (when reason
810 (insert " Reason: " reason))
811 (insert "\n\n")
812
813 (let ((action
814 `(lambda (&rest ignore) (interactive)
815 (jabber-muc-join jabber-buffer-connection ,group
816 (jabber-muc-read-my-nickname jabber-buffer-connection ,group)))))
817 (if (fboundp 'insert-button)
818 (insert-button "Accept"
819 'action action)
820 ;; Simple button replacement
821 (let ((keymap (make-keymap)))
822 (define-key keymap "\r" action)
823 (insert (jabber-propertize "Accept"
824 'keymap keymap
825 'face 'highlight))))
826
827 (insert "\t")
828
829 (let ((action
830 `(lambda (&rest ignore) (interactive)
831 (let ((reason
832 (jabber-read-with-input-method
833 "Reason: ")))
834 (jabber-send-sexp
835 jabber-buffer-connection
836 (list 'message
837 (list (cons 'to ,group))
838 (list 'x
839 (list (cons 'xmlns "http://jabber.org/protocol/muc#user"))
840 (list 'decline
841 (list (cons 'to ,inviter))
842 (unless (zerop (length reason))
843 (list 'reason nil reason))))))))))
844 (if (fboundp 'insert-button)
845 (insert-button "Decline"
846 'action action)
847 ;; Simple button replacement
848 (let ((keymap (make-keymap)))
849 (define-key keymap "\r" action)
850 (insert (jabber-propertize "Decline"
851 'keymap keymap
852 'face 'highlight))))))))
853 (cl-return t))))))
854
855 (defun jabber-muc-autojoin (jc)
856 "Join rooms specified in account bookmarks and global `jabber-muc-autojoin'.
857
858 JC is the Jabber connection."
859 (interactive (list (jabber-read-account)))
860 (let ((nickname (plist-get (fsm-get-state-data jc) :username)))
861 (when (bound-and-true-p jabber-muc-autojoin)
862 (dolist (group jabber-muc-autojoin)
863 (jabber-muc-join jc group (or
864 (cdr (assoc group jabber-muc-default-nicknames))
865 (plist-get (fsm-get-state-data jc) :username)))))
866 (jabber-get-bookmarks
867 jc
868 (lambda (jc bookmarks)
869 (dolist (bookmark bookmarks)
870 (setq bookmark (jabber-parse-conference-bookmark bookmark))
871 (when (and bookmark (plist-get bookmark :autojoin))
872 (jabber-muc-join jc (plist-get bookmark :jid)
873 (or (plist-get bookmark :nick)
874 (plist-get (fsm-get-state-data jc) :username)))))))))
875
876 ;;;###autoload
877 (defun jabber-muc-message-p (message)
878 "Return non-nil if MESSAGE is a groupchat message.
879 That does not include private messages in a groupchat, but does
880 include groupchat invites."
881 ;; Public groupchat messages have type "groupchat" and are from
882 ;; room@server/nick. Public groupchat errors have type "error" and
883 ;; are from room@server.
884 (let ((from (jabber-xml-get-attribute message 'from))
885 (type (jabber-xml-get-attribute message 'type)))
886 (or
887 (string= type "groupchat")
888 (and (string= type "error")
889 (gethash (jabber-jid-symbol from) jabber-pending-groupchats))
890 (jabber-xml-path message '(("http://jabber.org/protocol/muc#user" . "x") invite)))))
891
892 ;;;###autoload
893 (defun jabber-muc-sender-p (jid)
894 "Return non-nil if JID is a full JID of an MUC participant."
895 (and (assoc (jabber-jid-user jid) *jabber-active-groupchats*)
896 (jabber-jid-resource jid)))
897
898 ;;;###autoload
899 (defun jabber-muc-private-message-p (message)
900 "Return non-nil if MESSAGE is a private message in a groupchat."
901 (let ((from (jabber-xml-get-attribute message 'from))
902 (type (jabber-xml-get-attribute message 'type)))
903 (and
904 (not (string= type "groupchat"))
905 (jabber-muc-sender-p from))))
906
907 (add-to-list 'jabber-jid-muc-menu
908 (cons "Open private chat" 'jabber-muc-private))
909
910 (defun jabber-muc-private (jc group nickname)
911 "Open private chat with NICKNAME in GROUP.
912
913 JC is the Jabber connection."
914 (interactive
915 (jabber-muc-argument-list
916 (list (jabber-muc-read-nickname jabber-group "Nickname: "))))
917 (switch-to-buffer (jabber-muc-private-create-buffer jabber-buffer-connection group nickname)))
918
919 (defun jabber-muc-presence-p (presence)
920 "Return non-nil if PRESENCE is presence from groupchat."
921 (let ((from (jabber-xml-get-attribute presence 'from))
922 (type (jabber-xml-get-attribute presence 'type))
923 (muc-marker (cl-find-if
924 (lambda (x) (equal (jabber-xml-get-attribute x 'xmlns)
925 "http://jabber.org/protocol/muc#user"))
926 (jabber-xml-get-children presence 'x))))
927 ;; This is MUC presence if it has an MUC-namespaced tag...
928 (or muc-marker
929 ;; ...or if it is error presence from a room we tried to join.
930 (and (string= type "error")
931 (gethash (jabber-jid-symbol from) jabber-pending-groupchats)))))
932
933 (defun jabber-muc-parse-affiliation (x-muc)
934 "Parse X-MUC in the muc#user namespace and return a plist.
935 Return nil if X-MUC is nil."
936 ;; XXX: parse <actor/> and <reason/> tags? or maybe elsewhere?
937 (apply 'nconc (mapcar (lambda (prop) (list (car prop) (cdr prop)))
938 (jabber-xml-node-attributes
939 (car (jabber-xml-get-children x-muc 'item))))))
940
941 (defun jabber-muc-print-prompt (xml-data &optional local dont-print-nick-p)
942 "Print MUC prompt for message in XML-DATA."
943 (let ((nick (jabber-jid-resource (jabber-xml-get-attribute xml-data 'from)))
944 (timestamp (jabber-message-timestamp xml-data)))
945 (if (stringp nick)
946 (insert (jabber-propertize
947 (format-spec jabber-groupchat-prompt-format
948 (list
949 (cons ?t (format-time-string
950 (if timestamp
951 jabber-chat-delayed-time-format
952 jabber-chat-time-format)
953 timestamp))
954 (cons ?n (if dont-print-nick-p "" nick))
955 (cons ?u nick)
956 (cons ?r nick)
957 (cons ?j (concat jabber-group "/" nick))))
958 'face (if local ;Message from you.
959 (if jabber-muc-colorize-local ;; If colorization enable...
960 ;; ...colorize nick
961 (list ':foreground (jabber-muc-nick-get-color nick))
962 ;; otherwise, use default face.
963 'jabber-chat-prompt-local)
964 ;; Message from other participant.
965 (if jabber-muc-colorize-foreign ;If colorization enable...
966 ;; ... colorize nick
967 (list ':foreground (jabber-muc-nick-get-color nick))
968 ;; otherwise, use default face.
969 'jabber-chat-prompt-foreign))
970 'help-echo (concat (format-time-string "On %Y-%m-%d %H:%M:%S" timestamp) " from " nick " in " jabber-group)))
971 (jabber-muc-system-prompt))))
972
973 (defun jabber-muc-private-print-prompt (xml-data)
974 "Print prompt for private MUC message in XML-DATA."
975 (let ((nick (jabber-jid-resource (jabber-xml-get-attribute xml-data 'from)))
976 (group (jabber-jid-user (jabber-xml-get-attribute xml-data 'from)))
977 (timestamp (jabber-message-timestamp xml-data)))
978 (insert (jabber-propertize
979 (format-spec jabber-muc-private-foreign-prompt-format
980 (list
981 (cons ?t (format-time-string
982 (if timestamp
983 jabber-chat-delayed-time-format
984 jabber-chat-time-format)
985 timestamp))
986 (cons ?n nick)
987 (cons ?g (or (jabber-jid-rostername group)
988 (jabber-jid-username group)))))
989 'face 'jabber-chat-prompt-foreign
990 'help-echo (concat (format-time-string "On %Y-%m-%d %H:%M:%S" timestamp) " from " nick " in " jabber-group)))))
991
992 (defun jabber-muc-system-prompt (&rest ignore)
993 "Print system prompt for MUC."
994 (insert (jabber-propertize
995 (format-spec jabber-groupchat-prompt-format
996 (list
997 (cons ?t (format-time-string jabber-chat-time-format))
998 (cons ?n "")
999 (cons ?u "")
1000 (cons ?r "")
1001 (cons ?j jabber-group)))
1002 'face 'jabber-chat-prompt-system
1003 'help-echo (format-time-string "System message on %Y-%m-%d %H:%M:%S"))))
1004
1005 (add-to-list 'jabber-message-chain 'jabber-muc-process-message)
1006
1007 (defun jabber-muc-process-message (jc xml-data)
1008 "If XML-DATA is a groupchat message, handle it as such.
1009
1010 JC is the Jabber connection."
1011 (when (jabber-muc-message-p xml-data)
1012 (defvar printers nil)
1013 (let* ((from (jabber-xml-get-attribute xml-data 'from))
1014 (group (jabber-jid-user from))
1015 (nick (jabber-jid-resource from))
1016 (error-p (jabber-xml-get-children xml-data 'error))
1017 (type (cond
1018 (error-p :muc-error)
1019 ((string= nick (cdr (assoc group *jabber-active-groupchats*)))
1020 :muc-local)
1021 (t :muc-foreign)))
1022 (body-text (car (jabber-xml-node-children
1023 (car (jabber-xml-get-children
1024 xml-data 'body)))))
1025
1026 (printers (append jabber-muc-printers jabber-chat-printers)))
1027
1028 (with-current-buffer (jabber-muc-create-buffer jc group)
1029 (jabber-muc-snarf-topic xml-data)
1030 ;; Call alert hooks only when something is output
1031 (when (or error-p
1032 (run-hook-with-args-until-success 'printers xml-data type :printp))
1033 (jabber-maybe-print-rare-time
1034 (ewoc-enter-last jabber-chat-ewoc (list type xml-data :time (current-time))))
1035
1036 ;; ...except if the message is part of history, in which
1037 ;; case we don't want an alert.
1038 (let ((children-namespaces (mapcar (lambda (x) (when (listp x) (jabber-xml-get-attribute x 'xmlns)))
1039 (jabber-xml-node-children xml-data))))
1040 (unless (or (member "urn:xmpp:delay" children-namespaces)
1041 (member "jabber:x:delay" children-namespaces))
1042 (dolist (hook '(jabber-muc-hooks jabber-alert-muc-hooks))
1043 (run-hook-with-args hook
1044 nick group (current-buffer) body-text
1045 (funcall jabber-alert-muc-function
1046 nick group (current-buffer) body-text))))))))))
1047
1048 (defface jabber-muc-presence-dim
1049 '((t (:foreground "dark grey" :weight light :slant italic)))
1050 "face for diminished presence notifications.")
1051
1052 (defcustom jabber-muc-decorate-presence-patterns nil
1053 "List of regular expressions and face pairs.
1054 When a presence notification matches a pattern, display it with
1055 associated face. Ignore notification if face is ‘nil’."
1056 :type '(repeat
1057 :tag "Patterns"
1058 (cons :format "%v"
1059 (regexp :tag "Regexp")
1060 (choice
1061 (const :tag "Ignore" nil)
1062 (face :tag "Face" :value jabber-muc-presence-dim))))
1063 :group 'jabber-alerts)
1064
1065 (defun jabber-muc-maybe-decorate-presence (node)
1066 "Filter presence notifications."
1067 (cl-destructuring-bind (key msg &key time) node
1068 (let* ((match (cl-find-if
1069 (lambda (pair)
1070 (string-match (car pair) msg))
1071 jabber-muc-decorate-presence-patterns))
1072 (face (cdr-safe match)))
1073 (if match
1074 (when face
1075 (jabber-maybe-print-rare-time
1076 (ewoc-enter-last
1077 jabber-chat-ewoc
1078 (list key
1079 (propertize msg 'face face)
1080 :time time))))
1081 (jabber-maybe-print-rare-time
1082 (ewoc-enter-last jabber-chat-ewoc node))))))
1083
1084 (defun jabber-muc-process-presence (jc presence)
1085 (let* ((from (jabber-xml-get-attribute presence 'from))
1086 (type (jabber-xml-get-attribute presence 'type))
1087 (x-muc (cl-find-if
1088 (lambda (x) (equal (jabber-xml-get-attribute x 'xmlns)
1089 "http://jabber.org/protocol/muc#user"))
1090 (jabber-xml-get-children presence 'x)))
1091 (group (jabber-jid-user from))
1092 (nickname (jabber-jid-resource from))
1093 (symbol (jabber-jid-symbol from))
1094 (our-nickname (gethash symbol jabber-pending-groupchats))
1095 (item (car (jabber-xml-get-children x-muc 'item)))
1096 (actor (jabber-xml-get-attribute (car (jabber-xml-get-children item 'actor)) 'jid))
1097 (reason (car (jabber-xml-node-children (car (jabber-xml-get-children item 'reason)))))
1098 (error-node (car (jabber-xml-get-children presence 'error)))
1099 (status-codes (if error-node
1100 (list (jabber-xml-get-attribute error-node 'code))
1101 (mapcar
1102 (lambda (status-element)
1103 (jabber-xml-get-attribute status-element 'code))
1104 (jabber-xml-get-children x-muc 'status)))))
1105 ;; handle leaving a room
1106 (cond
1107 ((or (string= type "unavailable") (string= type "error"))
1108 ;; error from room itself? or are we leaving?
1109 (if (or (null nickname)
1110 (member "110" status-codes)
1111 (string= nickname our-nickname))
1112 ;; Assume that an error means that we were thrown out of the
1113 ;; room...
1114 (let* ((leavingp t)
1115 (message (cond
1116 ((string= type "error")
1117 (cond
1118 ;; ...except for certain cases.
1119 ((or (member "406" status-codes)
1120 (member "409" status-codes))
1121 (setq leavingp nil)
1122 (concat "Nickname change not allowed"
1123 (when error-node
1124 (concat ": " (jabber-parse-error error-node)))))
1125 (t
1126 (concat "Error entering room"
1127 (when error-node
1128 (concat ": " (jabber-parse-error error-node)))))))
1129 ((member "301" status-codes)
1130 (concat "You have been banned"
1131 (when actor (concat " by " actor))
1132 (when reason (concat " - '" reason "'"))))
1133 ((member "307" status-codes)
1134 (concat "You have been kicked"
1135 (when actor (concat " by " actor))
1136 (when reason (concat " - '" reason "'"))))
1137 (t
1138 "You have left the chatroom"))))
1139 (when leavingp
1140 (jabber-muc-remove-groupchat group))
1141 ;; If there is no buffer for this groupchat, don't bother
1142 ;; creating one just to tell that user left the room.
1143 (let ((buffer (get-buffer (jabber-muc-get-buffer group))))
1144 (if buffer
1145 (with-current-buffer buffer
1146 (jabber-muc-maybe-decorate-presence
1147 (list (if (string= type "error")
1148 :muc-error
1149 :muc-notice)
1150 message
1151 :time (current-time))))
1152 (message "%s: %s" (jabber-jid-displayname group) message))))
1153 ;; or someone else?
1154 (let* ((plist (jabber-muc-participant-plist group nickname))
1155 (jid (plist-get plist 'jid))
1156 (name (concat nickname
1157 (when jid
1158 (concat " <"
1159 (jabber-jid-user jid)
1160 ">")))))
1161 (jabber-muc-remove-participant group nickname)
1162 (with-current-buffer (jabber-muc-create-buffer jc group)
1163 (jabber-muc-maybe-decorate-presence
1164 (list :muc-notice
1165 (cond
1166 ((member "301" status-codes)
1167 (concat name " has been banned"
1168 (when actor (concat " by " actor))
1169 (when reason (concat " - '" reason "'"))))
1170 ((member "307" status-codes)
1171 (concat name " has been kicked"
1172 (when actor (concat " by " actor))
1173 (when reason (concat " - '" reason "'"))))
1174 ((member "303" status-codes)
1175 (concat name " changes nickname to "
1176 (jabber-xml-get-attribute item 'nick)))
1177 (t
1178 (concat name " has left the chatroom")))
1179 :time (current-time)))))))
1180 (t
1181 ;; someone is entering
1182
1183 (when (or (member "110" status-codes) (string= nickname our-nickname))
1184 ;; This is us. We just succeeded in entering the room.
1185 ;;
1186 ;; The MUC server is supposed to send a 110 code whenever this
1187 ;; is our presence ("self-presence"), but at least one
1188 ;; (ejabberd's mod_irc) doesn't, so check the nickname as well.
1189 ;;
1190 ;; This check might give incorrect results if the server
1191 ;; changed our nickname to avoid collision with an existing
1192 ;; participant, but even in this case the window where we have
1193 ;; incorrect information should be very small, as we should be
1194 ;; getting our own 110+210 presence shortly.
1195 (let ((whichgroup (assoc group *jabber-active-groupchats*)))
1196 (if whichgroup
1197 (setcdr whichgroup nickname)
1198 (add-to-list '*jabber-active-groupchats* (cons group nickname))))
1199 ;; The server may have changed our nick. Record the new one.
1200 (puthash symbol nickname jabber-pending-groupchats))
1201
1202 ;; Whoever enters, we create a buffer (if it didn't already
1203 ;; exist), and print a notice. This is where autojoined MUC
1204 ;; rooms have buffers created for them. We also remember some
1205 ;; metadata.
1206 (let ((old-plist (jabber-muc-participant-plist group nickname))
1207 (new-plist (jabber-muc-parse-affiliation x-muc)))
1208 (jabber-muc-modify-participant group nickname new-plist)
1209 (let ((report (jabber-muc-report-delta nickname old-plist new-plist
1210 reason actor)))
1211 (when report
1212 (with-current-buffer (jabber-muc-create-buffer jc group)
1213 (jabber-muc-maybe-decorate-presence
1214 (list :muc-notice report
1215 :time (current-time)))
1216 ;; Did the server change our nick?
1217 (when (member "210" status-codes)
1218 (ewoc-enter-last
1219 jabber-chat-ewoc
1220 (list :muc-notice
1221 (concat "Your nick was changed to " nickname " by the server")
1222 :time (current-time))))
1223 ;; Was this room just created? If so, it's a locked
1224 ;; room. Notify the user.
1225 (when (member "201" status-codes)
1226 (ewoc-enter-last
1227 jabber-chat-ewoc
1228 (list :muc-notice
1229 (with-temp-buffer
1230 (insert "This room was just created, and is locked to other participants.\n"
1231 "To unlock it, ")
1232 (insert-text-button
1233 "configure the room"
1234 'action (apply-partially 'call-interactively 'jabber-muc-get-config))
1235 (insert " or ")
1236 (insert-text-button
1237 "accept the default configuration"
1238 'action (apply-partially 'call-interactively 'jabber-muc-instant-config))
1239 (insert ".")
1240 (buffer-string))
1241 :time (current-time))))))))))))
1242
1243 (provide 'jabber-muc)
1244
1245 ;;; arch-tag: 1ff7ab35-1717-46ae-b803-6f5b3fb2cd7d