1# $Id$
2
3# Multi-User Chat support (XEP-0045)
4
5###############################################################################
6
7package require xmpp::muc
8
9namespace eval muc {
10    set winid 0
11    custom::defvar options(gen_enter_exit_msgs) 1 \
12	[::msgcat::mc "Generate status messages when occupants\
13	    enter/exit MUC compatible conference rooms."] \
14	-type boolean -group Chat
15    custom::defvar options(gen_muc_status_change_msgs) 0 \
16	[::msgcat::mc "Generate groupchat messages when occupant\
17	    changes his/her status and/or status message."] \
18	-type boolean -group Chat
19    custom::defvar options(gen_muc_position_change_msgs) 0 \
20	[::msgcat::mc "Generate groupchat messages when occupant's\
21	    room position (affiliation and/or role) changes."] \
22	-type boolean -group Chat
23    custom::defvar options(propose_configure) 0 \
24	[::msgcat::mc "Propose to configure newly created MUC room.\
25		       If set to false then the default room configuration\
26		       is automatically accepted."] \
27	-type boolean -group Chat
28    custom::defvar options(history_maxchars) 10000 \
29	[::msgcat::mc "Maximum number of characters in the history in MUC\
30		       compatible conference rooms."] \
31	-type integer -group Chat
32    custom::defvar options(history_maxstanzas) 20 \
33	[::msgcat::mc "Maximum number of stanzas in the history in MUC\
34		       compatible conference rooms."] \
35	-type integer -group Chat
36    custom::defvar options(request_only_unseen_history) 1 \
37	[::msgcat::mc "Request only unseen (which aren't displayed in the\
38		       chat window) messages in the history in MUC compatible\
39		       conference rooms."] \
40	-type boolean -group Chat
41    custom::defvar options(retry_with_different_nick) 0 \
42	[::msgcat::mc "Retry to join MUC room with a different nickname\
43		       (with added _ suffix) in case of name conflicts."] \
44	-type boolean -group Chat
45    custom::defvar options(report_muc_rooms) 0 \
46	[::msgcat::mc "Report the list of current MUC rooms on\
47		       disco#items query."] \
48	-type boolean -group IQ
49}
50
51set ::NS(muc)       http://jabber.org/protocol/muc
52set ::NS(muc#owner) http://jabber.org/protocol/muc#owner
53set ::NS(muc#user)  http://jabber.org/protocol/muc#user
54
55set ::NS(muc#rooms)  http://jabber.org/protocol/muc#rooms
56
57###############################################################################
58
59proc set_our_groupchat_nick {chatid nick} {
60    global groupchats
61
62    debugmsg conference "SET NICK: $chatid '$nick'"
63
64    set xlib [chat::get_xlib $chatid]
65    set group [::xmpp::jid::normalize [chat::get_jid $chatid]]
66    set groupchats(nick,$xlib,$group) $nick
67}
68
69proc get_our_groupchat_nick {chatid} {
70    global groupchats
71
72    debugmsg conference "GET NICK: $chatid"
73
74    set xlib [chat::get_xlib $chatid]
75    set group [::xmpp::jid::normalize [chat::get_jid $chatid]]
76    return $groupchats(nick,$xlib,$group)
77}
78
79###############################################################################
80
81proc muc::get_real_jid {xlib jid} {
82    variable tokens
83
84    set group [::xmpp::jid::removeResource $jid]
85    set nick [::xmpp::jid::resource $jid]
86    set chatid [chat::chatid $xlib $group]
87
88    if {![info exists tokens($chatid)]} {
89	return ""
90    } else {
91	return [::xmpp::muc::realJid $tokens($chatid) $nick]
92    }
93}
94
95proc muc::get_affiliation {xlib jid} {
96    variable tokens
97
98    set group [::xmpp::jid::removeResource $jid]
99    set nick [::xmpp::jid::resource $jid]
100    set chatid [chat::chatid $xlib $group]
101
102    if {![info exists tokens($chatid)]} {
103	return ""
104    } else {
105	return [::xmpp::muc::affiliation $tokens($chatid) $nick]
106    }
107}
108
109proc muc::get_role {xlib jid} {
110    variable tokens
111
112    set group [::xmpp::jid::removeResource $jid]
113    set nick [::xmpp::jid::resource $jid]
114    set chatid [chat::chatid $xlib $group]
115
116    if {![info exists tokens($chatid)]} {
117	return ""
118    } else {
119	return [::xmpp::muc::role $tokens($chatid) $nick]
120    }
121}
122
123proc muc::whois {xlib user reschatid} {
124    set group [::xmpp::jid::stripResource $user]
125    set chatid [chat::chatid $xlib $group]
126    set nick [chat::get_nick $xlib $user groupchat]
127
128    set real_jid [get_real_jid $xlib $user]
129
130    if {$real_jid != ""} {
131	chat::add_message $reschatid $group info \
132	    [::msgcat::mc "whois '%s': %s" $nick $real_jid] {}
133    } else {
134	chat::add_message $reschatid $group error \
135	    [::msgcat::mc "whois '%s': no info" $nick] {}
136    }
137}
138
139###############################################################################
140
141proc muc::change_item_attr {xlib user attr value dir reason reschatid} {
142    variable tokens
143
144    set group [::xmpp::jid::stripResource $user]
145    set chatid [chat::chatid $xlib $group]
146    set nick  [chat::get_nick $xlib $user groupchat]
147
148    if {![info exists tokens($chatid)]} {
149	chat::add_message $reschatid $group error \
150		"$attr $value '$nick':\
151		 [::msgcat::mc {You must join the room to set %s} $attr]" {}
152	return
153    }
154
155    set args [list -command \
156		   [list muc::test_error_res \
157			 "$attr $value '$nick'" $xlib $group $reschatid]]
158    if {![string equal $reason ""]} {
159	lappend args -reason $reason
160    }
161
162    switch -- $attr/$dir {
163	affiliation/up   { set command ::xmpp::muc::raiseAffiliation }
164	affiliation/down { set command ::xmpp::muc::lowerAffiliation }
165	role/up   { set command ::xmpp::muc::raiseRole }
166	role/down { set command ::xmpp::muc::lowerRole }
167    }
168
169    eval [list $command $tokens($chatid) $nick $value] $args
170}
171
172###############################################################################
173
174proc muc::unban {xlib group jid} {
175    ::xmpp::muc::unsetOutcast $xlib $group $jid \
176		-command [list muc::test_unban_res $xlib $group $jid]
177}
178
179proc muc::test_unban_res {xlib group jid status msg} {
180    switch -- $status {
181	ok {
182	    chat::add_message [chat::chatid $xlib $group] $group info \
183		    [format "affiliation none '%s': %s" \
184			    $jid [::msgcat::mc "User is unbanned"]] {}
185	}
186	default {
187	    chat::add_message [chat::chatid $xlib $group] $group error \
188		    [format "affiliation none '%s': %s" \
189			    $jid [error_to_string $msg]] {}
190	}
191    }
192}
193
194###############################################################################
195
196proc muc::request_config_dialog {chatid} {
197    variable winid
198
199    set w .muc_req_config$winid
200    incr winid
201
202    if {[winfo exists $w]} {
203	destroy $w
204    }
205
206    Dialog $w -title [::msgcat::mc "Room is created"] \
207	-modal none -separator 1 -anchor e -default 0 -cancel 1
208
209    set wf [$w getframe]
210    message $wf.message -aspect 50000 \
211	-text [::msgcat::mc "Room %s is successfully created" \
212		    [chat::get_jid $chatid]]
213
214    pack $wf.message -pady 2m
215
216    $w add -text [::msgcat::mc "Configure room"] \
217	-command "[list destroy $w]
218		  [list [namespace current]::request_config $chatid]"
219    $w add -text [::msgcat::mc "Accept default config"] \
220	-command "[list destroy $w]
221		  [list [namespace current]::request_instant_room $chatid]"
222
223    $w draw
224}
225
226proc muc::request_instant_room {chatid} {
227    ::xmpp::sendIQ [chat::get_xlib $chatid] set \
228	-query [::xmpp::xml::create query \
229			-xmlns $::NS(muc#owner) \
230			-subelement [::xmpp::data::submitForm {}]] \
231	-to [chat::get_jid $chatid]
232}
233
234proc muc::request_config {chatid} {
235    ::xmpp::sendIQ [chat::get_xlib $chatid] get \
236	-query [::xmpp::xml::create query \
237			-xmlns $::NS(muc#owner)] \
238	-to [chat::get_jid $chatid] \
239	-command [list muc::receive_config $chatid]
240}
241
242proc muc::receive_config {chatid res child} {
243    set group [chat::get_jid $chatid]
244    if {![string equal $res ok]} {
245	chat::add_message $chatid $group error \
246	    [::msgcat::mc "Configure form: %s" [error_to_string $child]] \
247	    {}
248	return
249    }
250
251    ::xmpp::xml::split $child tag xmlns attrs cdata subels
252
253    data::draw_window $subels \
254	[list muc::send_config $chatid] \
255	-cancelCommand [list muc::cancel_config $chatid] \
256	-title [::msgcat::mc "Configure room %s" $group]
257    return
258}
259
260###############################################################################
261
262proc muc::send_config {chatid w restags} {
263    set xlib [chat::get_xlib $chatid]
264    set group [chat::get_jid $chatid]
265    ::xmpp::sendIQ $xlib set \
266	-query [::xmpp::xml::create query \
267			   -xmlns $::NS(muc#owner) \
268			   -subelements $restags] \
269	-to $group \
270	-command [list muc::test_error_res \
271		       [::msgcat::mc "Sending configure form"] $xlib $group $chatid]
272    destroy $w
273}
274
275###############################################################################
276
277proc muc::cancel_config {chatid w} {
278    set xlib [chat::get_xlib $chatid]
279    set group [chat::get_jid $chatid]
280    ::xmpp::sendIQ $xlib set \
281	-query [::xmpp::xml::create query \
282			   -xmlns $::NS(muc#owner) \
283			   -subelement [::xmpp::data::cancelForm]] \
284	-to $group \
285	-command [list muc::test_error_res \
286		       [::msgcat::mc "Cancelling configure form"] $xlib $group $chatid]
287    destroy $w
288}
289
290###############################################################################
291
292proc muc::test_error_res {op xlib group reschatid res child} {
293    if {![string equal $res ok]} {
294	set chatid [chat::chatid $xlib $group]
295	chat::add_message $reschatid $group error \
296	    [format "%s: %s" $op [error_to_string $child]] {}
297	return
298    }
299}
300
301###############################################################################
302
303proc muc::report_muc_event {chatid action nick args} {
304    variable options
305
306    debugmsg conference "MUC EVENT: $chatid $action $nick $args"
307
308    if {![chat::is_opened $chatid]} return
309
310    set xlib [chat::get_xlib $chatid]
311    set group [chat::get_jid $chatid]
312    set user $group/$nick
313
314    switch -- $action {
315	disconnect {
316	    set msg [::msgcat::mc "Disconnected"]
317	    foreach {key val} $args {
318		switch -- $key {
319		    -error { set msg [::xmpp::stanzaerror::message $val] }
320		}
321	    }
322
323	    chat::add_message $chatid $group error $msg {}
324	    client:presence $xlib $group unavailable "" {}
325	}
326	enter {
327	    hook::run chat_user_enter $chatid $nick
328	    report_available $chatid $nick 1
329	}
330	presence {
331	    report_available $chatid $nick 0
332	}
333	exit {
334	    report_unavailable $chatid $nick
335	    hook::run chat_user_exit $chatid $nick
336	}
337	position {
338	    eval [list track_room_position $xlib $user] $args
339	}
340	create {
341	    chat::add_message \
342		    $chatid $group groupchat \
343		    [::msgcat::mc "A new room is created"] {}
344
345	    if {$options(propose_configure)} {
346		request_config_dialog $chatid
347	    } else {
348		# requesting "instant" room as specified in XEP-0045
349		# if the user wants to configure room (s)he can do it later
350		request_instant_room $chatid
351	    }
352	}
353	destroy {
354	    set msg [::msgcat::mc "Room is destroyed"]
355	    foreach {key val} $args {
356		switch -- $key {
357		    -reason {
358			append msg [::msgcat::mc "\nReason: %s" $val]
359		    }
360		    -jid {
361			append msg [::msgcat::mc "\nAlternative venue: %s" $val]
362		    }
363		}
364	    }
365	    if {$options(gen_enter_exit_msgs)} {
366		chat::add_message \
367			$chatid $group groupchat $msg {}
368	    }
369	}
370	ban -
371	kick -
372	demember -
373	members-only {
374	    set real_jid ""
375	    foreach {key val} $args {
376		switch -- $key {
377		    -jid    { set real_jid " ($val)" }
378		    -actor  { set actor $val }
379		    -reason { set reason $val }
380		}
381	    }
382
383	    switch -- $action {
384		ban { set msg [::msgcat::mc "%s has been banned" \
385					    $nick$real_jid] }
386		kick { set msg [::msgcat::mc "%s has been kicked" \
387					     $nick$real_jid] }
388		demember { set msg [::msgcat::mc "%s has been kicked because\
389						  of membership loss" \
390						 $nick$real_jid] }
391		members-only { set msg [::msgcat::mc \
392					    "%s has been kicked because\
393					     room became members-only" \
394					    $nick$real_jid] }
395	    }
396
397	    if {[info exists actor] && $actor != ""} {
398		append msg [::msgcat::mc " by %s" $actor]
399	    }
400
401	    if {[info exists reason] && $reason != ""} {
402		append msg ": $reason"
403	    }
404
405	    if {$options(gen_enter_exit_msgs)} {
406		chat::add_message $chatid $group groupchat $msg {}
407	    }
408	}
409	nick {
410	    set real_jid ""
411	    foreach {key val} $args {
412		switch -- $key {
413		    -jid {
414			set real_jid " ($val)"
415		    }
416		    -nick {
417			set new_nick $val
418		    }
419		}
420	    }
421
422	    # TODO may be this reporting should not be done
423	    # if the $nick is being ignored (MUC ignore)
424	    if {$options(gen_enter_exit_msgs)} {
425		chat::add_message $chatid $group groupchat \
426			    [::msgcat::mc "%s is now known as %s" \
427					 $nick$real_jid $new_nick] {}
428	    }
429	    ::hook::run room_nickname_changed_hook $chatid $nick $new_nick
430	}
431    }
432}
433
434proc muc::report_unavailable {chatid nick} {
435    variable options
436
437    set group [chat::get_jid $chatid]
438
439    if {$options(gen_enter_exit_msgs)} {
440	set message [::msgcat::mc "%s has left" $nick]
441	set xlib [chat::get_xlib $chatid]
442	set error [get_jid_presence_info error $xlib $group/$nick]
443	if {$error != ""} {
444	    set status [::xmpp::stanzaerror::message $error]
445	} else {
446	    set status [get_jid_presence_info status $xlib $group/$nick]
447	}
448	if {$status != ""} {
449	    append message ": $status"
450	} else {
451	    append message ""
452	}
453	chat::add_message $chatid $group groupchat $message {}
454    }
455}
456
457proc muc::report_available {chatid nick entered} {
458    variable options
459
460    set xlib [chat::get_xlib $chatid]
461    set group [chat::get_jid $chatid]
462
463    if {![is_compatible $group]} return
464
465    set jid $group/$nick
466
467    set msg ""
468
469    if {$entered && $options(gen_enter_exit_msgs)} {
470	set real_jid [get_real_jid $xlib $jid]
471	if {$real_jid != ""} {
472	    set occupant "$nick ($real_jid)"
473	} else {
474	    set occupant $nick
475	}
476	set msg [::msgcat::mc "%s has entered" $occupant]
477	if {$options(gen_muc_position_change_msgs)} {
478	    append msg " " [::msgcat::mc "as %s/%s" \
479		[::msgcat::mc [get_affiliation $xlib $jid]] \
480		[::msgcat::mc [get_role $xlib $jid]]]
481	}
482    }
483
484    if {$options(gen_muc_status_change_msgs)} {
485	set status [::get_user_status $xlib $jid]
486	if {$entered && $options(gen_enter_exit_msgs)} {
487	    append msg " " [::msgcat::mc "and"] " "
488	} else {
489	    append msg $nick " "
490	}
491	append msg [::get_long_status_desc $status]
492	set desc [::get_user_status_desc $xlib $jid]
493	if {$desc != {}} {
494	    append msg " ($desc)"
495	}
496    }
497
498    chat::add_message $chatid $group groupchat $msg {}
499}
500
501proc muc::track_room_position {xlib jid args} {
502    variable options
503
504    set group [::xmpp::jid::stripResource $jid]
505    if {![is_compatible $group]} return
506
507    set chatid [chat::chatid $xlib $group]
508
509    if {[chat::is_opened $chatid] && $options(gen_muc_position_change_msgs)} {
510	set nick [chat::get_nick $xlib $jid groupchat]
511
512	foreach {key val} $args {
513	    switch -- $key {
514		-affiliation { set affiliation $val }
515		-role { set role $val }
516	    }
517	}
518
519	if {[info exists affiliation]} {
520	    if {[info exists role]} {
521		set msg [::msgcat::mc "%s has been assigned a new room position: %s/%s" \
522		    $nick [::msgcat::mc $affiliation] [::msgcat::mc $role]]
523	    } else {
524		set msg [::msgcat::mc "%s has been assigned a new affiliation: %s" \
525		    $nick [::msgcat::mc $affiliation]]
526	    }
527	} elseif {[info exists role]} {
528	    set msg [::msgcat::mc "%s has been assigned a new role: %s" \
529		$nick [::msgcat::mc $role]]
530	} else {
531	    set msg ""
532	}
533
534	if {$msg != ""} {
535	    chat::add_message $chatid $group groupchat $msg {}
536	}
537    }
538}
539
540###############################################################################
541
542proc muc::change_nick {chatid nick} {
543    global userstatus textstatus
544    variable tokens
545
546    set xlib [chat::get_xlib $chatid]
547    set group [chat::get_jid $chatid]
548
549    if {![is_compatible $group]} {
550	chat::add_message $chatid $group error \
551	    [::msgcat::mc "Can't change nickname in MUC incompatible rooms"] {}
552	return
553    }
554
555    debugmsg conference "CHANGE_NICK: $chatid $nick"
556
557    eval [list ::xmpp::muc::setNick $tokens($chatid) $nick] \
558	 [presence_args $xlib $userstatus -status $textstatus] \
559	 [list -command [namespace code [list process_change_nick $chatid]]]
560}
561
562proc muc::process_change_nick {chatid status msg} {
563    debugmsg conference "PROCESS_CHANGE_NICK: $chatid $status $msg"
564
565    set xlib [chat::get_xlib $chatid]
566    set group [chat::get_jid $chatid]
567
568    switch -- $status {
569	ok {
570	    set_our_groupchat_nick $chatid $msg
571	}
572	error {
573	    if {[chat::is_opened $chatid]} {
574		chat::add_message $chatid $group error \
575				  [::xmpp::stanzaerror::message $msg] {}
576	    }
577	}
578    }
579    return
580}
581
582###############################################################################
583
584proc muc::request_negotiation {xlib group} {
585    variable muc_compatible
586
587    # It's almost impossible to find MUC-incompatible room now, so the default
588    # value is 1
589    set muc_compatible($group) 1
590
591    disco::request_info $xlib [::xmpp::jid::server $group] \
592	-cache yes \
593	-command [list muc::recv_negotiation1 $xlib $group]
594}
595
596proc muc::recv_negotiation1 {xlib group res identities features extras} {
597    variable muc_compatible
598
599    if {[string equal $res ok] && [lsearch -exact $features $::NS(muc)] >= 0} {
600	set muc_compatible($group) 1
601	set muc_compatible([::xmpp::jid::server $group]) 1
602	return
603    }
604
605    disco::request_info $xlib $group \
606	-cache yes \
607	-command [list muc::recv_negotiation2 $group]
608}
609
610proc muc::recv_negotiation2 {group res identities features extras} {
611    variable muc_compatible
612
613    if {[string equal $res ok] && [lsearch -exact $features $::NS(muc)] >= 0} {
614	set muc_compatible($group) 1
615	return
616    }
617
618    set muc_compatible($group) 0
619}
620
621proc muc::is_compatible {group} {
622    variable muc_compatible
623
624    if {[info exists muc_compatible($group)]} {
625	return $muc_compatible($group)
626    } elseif {[info exists muc_compatible([::xmpp::jid::server $group])]} {
627	return $muc_compatible([::xmpp::jid::server $group])
628    } else {
629	return 0
630    }
631}
632
633proc muc::roster {chatid} {
634    variable tokens
635
636    if {![info exists tokens($chatid)]} {
637	return {}
638    } else {
639	return [::xmpp::muc::roster $tokens($chatid)]
640    }
641}
642
643proc muc::nick {chatid} {
644    variable tokens
645
646    if {![info exists tokens($chatid)]} {
647	return ""
648    } else {
649	return [::xmpp::muc::nick $tokens($chatid)]
650    }
651}
652
653proc muc::status {chatid} {
654    variable tokens
655
656    if {![info exists tokens($chatid)]} {
657	return disconnected
658    } else {
659	return [::xmpp::muc::status $tokens($chatid)]
660    }
661}
662
663###############################################################################
664
665proc muc::add_user_popup_info {infovar xlib user} {
666    upvar 0 $infovar info
667
668    set real_jid [get_real_jid $xlib $user]
669    if {$real_jid != ""} {
670	append info [::msgcat::mc "\n\tJID: %s" $real_jid]
671    }
672    set affiliation [get_affiliation $xlib $user]
673    if {$affiliation != ""} {
674	append info [::msgcat::mc "\n\tAffiliation: %s" $affiliation]
675    }
676}
677
678hook::add roster_user_popup_info_hook muc::add_user_popup_info
679
680###############################################################################
681
682proc muc::set_message_timestamp {chatid from type body x} {
683    variable timestamps
684
685    if {![chat::is_disconnected $chatid]} {
686	set timestamps($chatid) [clock seconds]
687    }
688}
689
690hook::add draw_message_hook muc::set_message_timestamp 15
691
692proc muc::clear_message_timestamp {chatid} {
693    variable timestamps
694
695    catch { unset timestamps($chatid) }
696}
697
698hook::add close_chat_post_hook muc::clear_message_timestamp
699
700###############################################################################
701
702proc muc::new {chatid type} {
703    variable tokens
704
705    # MUC token is created only for groupchat windows
706
707    if {![string equal $type groupchat]} return
708
709    debugmsg conference "NEW_GROUP: $chatid"
710
711    set xlib  [chat::get_xlib $chatid]
712    set group [chat::get_jid  $chatid]
713
714    set tokens($chatid) \
715	[::xmpp::muc::new $xlib $group \
716		-eventcommand  [list muc::report_muc_event $chatid] \
717		-rostercommand [list chat::process_roster_event $chatid]]
718}
719
720hook::add open_chat_pre_hook muc::new
721
722###############################################################################
723
724proc muc::join_group_raise {xlib group nick {password ""}} {
725    if {[llength [connections]] == 0} return
726
727    if {[string equal $xlib ""]} {
728	set xlib [lindex [connections] 0]
729    }
730
731    join_group $xlib $group $nick $password
732
733    chat::activate [chat::chatid $xlib [::xmpp::jid::normalize $group]]
734}
735
736proc muc::join_group {xlib group nick {password ""} {retries 2}} {
737    global userstatus textstatus
738    variable options
739    variable timestamps
740    variable muc_password
741    variable tokens
742
743    set group [::xmpp::jid::normalize $group]
744    set chatid [chat::chatid $xlib $group]
745
746    privacy::add_to_special_list $xlib conference [::xmpp::jid::server $group]
747
748    set_our_groupchat_nick $chatid $nick
749
750    chat::open_window $chatid groupchat
751    update idletasks
752
753    request_negotiation $xlib $group
754
755    set x_args {}
756
757    set muc_password($chatid) $password
758
759    if {$options(history_maxchars) >= 0} {
760	lappend x_args -maxchars $options(history_maxchars)
761    }
762    if {$options(history_maxstanzas) >= 0} {
763	lappend x_args -maxstanzas $options(history_maxstanzas)
764    }
765    if {$options(request_only_unseen_history) && \
766	    [info exists timestamps($chatid)]} {
767	lappend x_args \
768		-seconds [expr {[clock seconds] - $timestamps($chatid) + 2}]
769    }
770
771    debugmsg conference "JOIN: $chatid $nick"
772
773    incr retries -1
774
775    eval [list ::xmpp::muc::join $tokens($chatid) $nick \
776	       -password $password \
777	       -command [namespace code [list process_join $chatid \
778							   $nick \
779							   $password \
780							   $retries]]] \
781	 [presence_args $xlib $userstatus -status $textstatus] \
782	 $x_args
783}
784
785proc muc::process_join {chatid nick password retries status msg} {
786    variable options
787
788    set xlib [chat::get_xlib $chatid]
789    set group [chat::get_jid $chatid]
790
791    debugmsg conference "PROCESS_JOIN: $chatid $status $msg"
792
793    switch -- $status {
794	ok {
795	    set_our_groupchat_nick $chatid $msg
796	    client:presence $xlib $group "" "" {}
797	    hook::run join_group_hook $chatid $msg
798	}
799	error {
800	    set message [::xmpp::stanzaerror::message $msg]
801	    if {$options(retry_with_different_nick) && \
802		    [string equal [::xmpp::stanzaerror::condition $msg] conflict] && \
803		    $retries >= 0} {
804		if {[chat::is_opened $chatid]} {
805		    append message " - " [::msgcat::mc "Retrying with nickname '%s_'" $nick]
806		    chat::add_message $chatid $group error $message {}
807		}
808
809		join_group $xlib $group ${nick}_ $password $retries
810
811	    } else {
812		if {[chat::is_opened $chatid]} {
813		    chat::add_message $chatid $group error $message {}
814		}
815	    }
816	}
817    }
818    return
819}
820
821###############################################################################
822
823proc muc::leave_group {chatid status} {
824    variable tokens
825
826    debugmsg conference "LEAVE_GROUP: $chatid"
827
828    if {[info exists tokens($chatid)]} {
829	if {![string equal $status ""]} {
830	    ::xmpp::muc::leave $tokens($chatid) -status $status
831	} else {
832	    ::xmpp::muc::leave $tokens($chatid)
833	}
834    }
835}
836
837proc muc::reset_group {chatid} {
838    variable tokens
839
840    debugmsg conference "RESSET_GROUP: $chatid"
841
842    ::xmpp::muc::reset $tokens($chatid)
843}
844
845proc muc::free {chatid} {
846    variable tokens
847
848    # This routine is called not only for groupchats, so checking existence of
849    # tokens($chatid) is necessary.
850
851    if {[info exists tokens($chatid)]} {
852	debugmsg conference "FREE_GROUP: $chatid"
853
854	::xmpp::muc::free $tokens($chatid)
855	unset tokens($chatid)
856    }
857}
858
859hook::add close_chat_post_hook muc::free
860
861###############################################################################
862
863proc muc::test_connection {chatid args} {
864    variable tokens
865
866    debugmsg conference "TEST_CONNECTION: $chatid"
867
868    foreach {key val} $args {
869	switch -- $key {
870	    -command { set command $val }
871	}
872    }
873
874    if {![info exists command]} {
875	return -code error "Command is mandatory"
876    }
877
878    if {![info exists tokens($chatid)]} {
879	return -code error "MUC token doesn't exist"
880    }
881
882    set xlib [chat::get_xlib $chatid]
883    set group [chat::get_jid $chatid]
884    set nick [::xmpp::muc::nick $tokens($chatid)]
885
886    ::xmpp::ping::ping $xlib -to $group/$nick \
887	    -timeout 10000 \
888	    -command [list muc::parse_test_connection $command]
889}
890
891proc muc::parse_test_connection {command status msg} {
892    debugmsg conference "TEST_CONNECTION_REPLY: $status $msg"
893
894    switch -- $status {
895	ok {
896	    eval $command connected
897	}
898	error {
899	    lassign [error_type_condition $msg] type condition
900	    switch -- $type/$condition {
901		cancel/not-allowed -
902		modify/not-acceptable {
903		    eval $command disconnected
904		}
905		default {
906		    eval $command connected
907		}
908	    }
909	}
910	default {
911	    eval $command disconnected
912	}
913    }
914}
915
916###############################################################################
917
918proc muc::invite_muc {xlib group jid reason} {
919    # If $jid is a 'real' JID then invite
920    # If $jid is in a conference room try to invite real JID
921
922    set real_jid [get_real_jid $xlib $jid]
923    if {$real_jid == ""} {
924	set real_jid $jid
925    }
926
927    message::send_msg $xlib $group \
928	-xlist [list \
929	    [::xmpp::xml::create x \
930		 -xmlns $::NS(muc#user) \
931		 -subelement [::xmpp::xml::create invite \
932				    -attrs [list to $real_jid] \
933				    -subelement [::xmpp::xml::create reason \
934							-cdata $reason]]]]
935}
936
937proc muc::invite_xconference {xlib group jid reason} {
938    message::send_msg $xlib $jid \
939	-type normal \
940	-subject "Invitation" \
941	-body $reason \
942	-xlist [list [::xmpp::xml::create x \
943			    -xmlns $::NS(xconference) \
944			    -attrs [list jid $group]]]
945}
946
947###############################################################################
948
949proc muc::process_invitation {rowvar bodyvar f x xlib from id type replyP} {
950    upvar 2 $rowvar row
951    upvar 2 $bodyvar body
952
953    foreach xa $x {
954	::xmpp::xml::split $xa tag xmlns attrs cdata subels
955
956	switch -- $xmlns \
957	    $::NS(xconference) {
958		set xconference_group [::xmpp::xml::getAttr $attrs jid]
959		set xconference_password ""
960		if {[string equal $body ""] && ![string equal $cdata ""]} {
961		    set xconference_body $cdata
962		} else {
963		    set xconference_body $body
964		}
965	    } \
966	    $::NS(muc#user) {
967		set password ""
968		set inviter ""
969		foreach ch $subels {
970		    ::xmpp::xml::split $ch stag sxmlns sattrs scdata ssubels
971
972		    switch -- $stag {
973			invite {
974			    set inviter [::xmpp::xml::getAttr $sattrs from]
975			    if {![string equal $inviter ""]} {
976				foreach c [connections] {
977				    set rjid [roster::find_jid $c $inviter]
978				    set name \
979					[roster::itemconfig $c $rjid -name]
980				    if {$rjid != "" && $name != ""} break
981				}
982				if {![string equal $name ""]} {
983				    set inviter "$name ($inviter)"
984				}
985				set muc_body \
986				    [::msgcat::mc \
987					 "%s invites you to conference\
988					  room %s" \
989					 $inviter $from]
990
991				foreach sch $ssubels {
992				    ::xmpp::xml::split $sch sstag ssxmlns ssattrs \
993							    sscdata sssubels
994				    if {[string equal $sstag "reason"]} {
995					append muc_body \
996					    [::msgcat::mc "\nReason is: %s" \
997						 $sscdata]
998				    }
999				}
1000			    }
1001			    # TODO decline
1002			}
1003			password {
1004			    set password $scdata
1005			}
1006		    }
1007		}
1008		if {![string equal $inviter ""]} {
1009		    set muc_group $from
1010		    set muc_password $password
1011		}
1012	    }
1013    }
1014
1015    if {[info exists muc_group] && $muc_group != ""} {
1016	process_x_conference $f $xlib $muc_group $muc_password $row
1017	incr row
1018	set body $muc_body
1019	return
1020    } elseif {[info exists xconference_group] && $xconference_group != ""} {
1021	process_x_conference $f $xlib $xconference_group \
1022	    $xconference_password $row
1023	incr row
1024	set body $xconference_body
1025	return
1026    }
1027
1028    return
1029}
1030
1031hook::add message_process_x_hook muc::process_invitation
1032
1033proc muc::process_x_conference {f xlib group password row} {
1034    label $f.lgroup$row -text [::msgcat::mc "Invited to:"]
1035    button $f.group$row -text $group \
1036	-command [list muc::join_group_raise $xlib \
1037					     $group \
1038					     [get_group_nick $xlib $group] \
1039					     $password]
1040
1041    grid $f.lgroup$row -row $row -column 0 -sticky e
1042    grid $f.group$row  -row $row -column 1 -sticky ew
1043}
1044
1045###############################################################################
1046
1047proc muc::disco_reply {type xlib from lang} {
1048    variable options
1049
1050    if {!$options(report_muc_rooms)} {
1051	return {error cancel not-allowed}
1052    }
1053
1054    switch -- $type {
1055	info {
1056	    return [list result {}]
1057	}
1058	items {
1059	    set res {}
1060	    foreach chatid [lfilter chat::is_groupchat [chat::opened $xlib]] {
1061		set group [chat::get_jid $chatid]
1062		if {[is_compatible $group]} {
1063		    lappend res [list jid $group]
1064		}
1065	    }
1066	    return [list result $res]
1067	}
1068    }
1069}
1070
1071hook::add postload_hook \
1072    [list disco::register_node $::NS(muc#rooms) muc::disco_reply \
1073	  [::trans::trans "Current rooms"]]
1074
1075###############################################################################
1076
1077# vim:ts=8:sw=4:sts=4:noet
1078