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