1using Gee; 2 3namespace Xmpp.Xep.Muc { 4 5private const string NS_URI = "http://jabber.org/protocol/muc"; 6private const string NS_URI_ADMIN = NS_URI + "#admin"; 7private const string NS_URI_OWNER = NS_URI + "#owner"; 8private const string NS_URI_USER = NS_URI + "#user"; 9private const string NS_URI_REQUEST = NS_URI + "#request"; 10 11public enum MucEnterError { 12 NONE, 13 PASSWORD_REQUIRED, 14 BANNED, 15 ROOM_DOESNT_EXIST, 16 CREATION_RESTRICTED, 17 USE_RESERVED_ROOMNICK, 18 NOT_IN_MEMBER_LIST, 19 NICK_CONFLICT, 20 OCCUPANT_LIMIT_REACHED, 21} 22 23public enum Affiliation { 24 NONE, 25 ADMIN, 26 MEMBER, 27 OUTCAST, 28 OWNER 29} 30 31public enum Role { 32 NONE, 33 MODERATOR, 34 PARTICIPANT, 35 VISITOR 36} 37 38public enum Feature { 39 REGISTER, 40 ROOMCONFIG, 41 ROOMINFO, 42 HIDDEN, 43 MEMBERS_ONLY, 44 MODERATED, 45 NON_ANONYMOUS, 46 OPEN, 47 PASSWORD_PROTECTED, 48 PERSISTENT, 49 PUBLIC, 50 ROOMS, 51 SEMI_ANONYMOUS, 52 STABLE_ID, 53 TEMPORARY, 54 UNMODERATED, 55 UNSECURED 56} 57 58public class JoinResult { 59 public MucEnterError? muc_error; 60 public string? stanza_error; 61 public string? nick; 62} 63 64public class Module : XmppStreamModule { 65 public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0045_muc_module"); 66 67 public signal void received_occupant_affiliation(XmppStream stream, Jid jid, Affiliation? affiliation); 68 public signal void received_occupant_jid(XmppStream stream, Jid jid, Jid? real_jid); 69 public signal void received_occupant_role(XmppStream stream, Jid jid, Role? role); 70 public signal void subject_set(XmppStream stream, string? subject, Jid jid); 71 public signal void invite_received(XmppStream stream, Jid room_jid, Jid from_jid, string? password, string? reason); 72 public signal void voice_request_received(XmppStream stream, Jid room_jid, Jid from_jid, string nick); 73 public signal void room_info_updated(XmppStream stream, Jid muc_jid); 74 75 public signal void self_removed_from_room(XmppStream stream, Jid jid, StatusCode code); 76 public signal void removed_from_room(XmppStream stream, Jid jid, StatusCode? code); 77 78 private ReceivedPipelineListener received_pipeline_listener; 79 80 public Module() { 81 received_pipeline_listener = new ReceivedPipelineListener(this); 82 } 83 84 public async JoinResult? enter(XmppStream stream, Jid bare_jid, string nick, string? password, DateTime? history_since) { 85 try { 86 Presence.Stanza presence = new Presence.Stanza(); 87 presence.to = bare_jid.with_resource(nick); 88 89 StanzaNode x_node = new StanzaNode.build("x", NS_URI).add_self_xmlns(); 90 if (password != null) { 91 x_node.put_node(new StanzaNode.build("password", NS_URI).put_node(new StanzaNode.text(password))); 92 } 93 if (history_since != null) { 94 StanzaNode history_node = new StanzaNode.build("history", NS_URI); 95 history_node.set_attribute("since", DateTimeProfiles.to_datetime(history_since)); 96 x_node.put_node(history_node); 97 } 98 presence.stanza.put_node(x_node); 99 100 stream.get_flag(Flag.IDENTITY).start_muc_enter(bare_jid, presence.id); 101 102 query_room_info.begin(stream, bare_jid); 103 stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence); 104 105 var promise = new Promise<JoinResult?>(); 106 stream.get_flag(Flag.IDENTITY).enter_futures[bare_jid] = promise; 107 try { 108 JoinResult? enter_result = yield promise.future.wait_async(); 109 stream.get_flag(Flag.IDENTITY).enter_futures.unset(bare_jid); 110 return enter_result; 111 } catch (Gee.FutureError e) { 112 return null; 113 } 114 } catch (InvalidJidError e) { 115 return new JoinResult() { muc_error = MucEnterError.NICK_CONFLICT }; 116 } 117 } 118 119 public void exit(XmppStream stream, Jid jid) { 120 try { 121 string nick = stream.get_flag(Flag.IDENTITY).get_muc_nick(jid); 122 Presence.Stanza presence = new Presence.Stanza(); 123 presence.to = jid.with_resource(nick); 124 presence.type_ = Presence.Stanza.TYPE_UNAVAILABLE; 125 stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence); 126 } catch (InvalidJidError e) { 127 warning("Tried to leave room with invalid nick: %s", e.message); 128 } 129 } 130 131 public void change_subject(XmppStream stream, Jid jid, string subject) { 132 MessageStanza message = new MessageStanza(); 133 message.to = jid; 134 message.type_ = MessageStanza.TYPE_GROUPCHAT; 135 message.stanza.put_node((new StanzaNode.build("subject")).put_node(new StanzaNode.text(subject))); 136 stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, message); 137 } 138 139 public void change_nick(XmppStream stream, Jid jid, string new_nick) { 140 // TODO: Return if successful 141 try { 142 Presence.Stanza presence = new Presence.Stanza(); 143 presence.to = jid.with_resource(new_nick); 144 stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence); 145 } catch (InvalidJidError e) { 146 warning("Tried to change nick to invalid nick: %s", e.message); 147 } 148 } 149 150 public void invite(XmppStream stream, Jid to_muc, Jid jid) { 151 MessageStanza message = new MessageStanza(); 152 message.to = to_muc; 153 StanzaNode invite_node = new StanzaNode.build("x", NS_URI_USER).add_self_xmlns() 154 .put_node(new StanzaNode.build("invite", NS_URI_USER).put_attribute("to", jid.to_string())); 155 message.stanza.put_node(invite_node); 156 stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, message); 157 } 158 159 public void request_voice(XmppStream stream, Jid to_muc) { 160 MessageStanza message = new MessageStanza() { to=to_muc }; 161 162 DataForms.DataForm submit_node = new DataForms.DataForm(); 163 submit_node.get_submit_node(); 164 165 DataForms.DataForm.Field field_node = new DataForms.DataForm.Field() { var="FORM_TYPE" }; 166 field_node.set_value_string(NS_URI_REQUEST); 167 168 DataForms.DataForm.ListSingleField single_field = new DataForms.DataForm.ListSingleField(new StanzaNode.build("field", DataForms.NS_URI)) { var="muc#role", label="Requested role", value="participant" }; 169 170 submit_node.add_field(field_node); 171 submit_node.add_field(single_field); 172 173 message.stanza.put_node(submit_node.stanza_node); 174 175 stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, message); 176 } 177 178 public void kick(XmppStream stream, Jid jid, string nick) { 179 change_role(stream, jid, nick, "none"); 180 } 181 182 /* XEP 0046: "A user cannot be kicked by a moderator with a lower affiliation." (XEP 0045 8.2) */ 183 public bool kick_possible(XmppStream stream, Jid occupant) { 184 try { 185 Jid muc_jid = occupant.bare_jid; 186 Flag flag = stream.get_flag(Flag.IDENTITY); 187 string own_nick = flag.get_muc_nick(muc_jid); 188 Affiliation my_affiliation = flag.get_affiliation(muc_jid, muc_jid.with_resource(own_nick)); 189 Affiliation other_affiliation = flag.get_affiliation(muc_jid, occupant); 190 switch (my_affiliation) { 191 case Affiliation.MEMBER: 192 if (other_affiliation == Affiliation.ADMIN || other_affiliation == Affiliation.OWNER) return false; 193 break; 194 case Affiliation.ADMIN: 195 if (other_affiliation == Affiliation.OWNER) return false; 196 break; 197 } 198 return true; 199 } catch (InvalidJidError e) { 200 warning("Tried to kick with invalid nick: %s", e.message); 201 return false; 202 } 203 } 204 205 public void change_role(XmppStream stream, Jid jid, string nick, string new_role) { 206 StanzaNode query = new StanzaNode.build("query", NS_URI_ADMIN).add_self_xmlns(); 207 query.put_node(new StanzaNode.build("item", NS_URI_ADMIN).put_attribute("nick", nick, NS_URI_ADMIN).put_attribute("role", new_role, NS_URI_ADMIN)); 208 Iq.Stanza iq = new Iq.Stanza.set(query) { to=jid }; 209 stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); 210 } 211 212 public void change_affiliation(XmppStream stream, Jid jid, string nick, string new_affiliation) { 213 StanzaNode query = new StanzaNode.build("query", NS_URI_ADMIN).add_self_xmlns(); 214 query.put_node(new StanzaNode.build("item", NS_URI_ADMIN).put_attribute("nick", nick, NS_URI_ADMIN).put_attribute("affiliation", new_affiliation, NS_URI_ADMIN)); 215 Iq.Stanza iq = new Iq.Stanza.set(query) { to=jid }; 216 stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); 217 } 218 219 public async DataForms.DataForm? get_config_form(XmppStream stream, Jid jid) { 220 Iq.Stanza get_iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_OWNER).add_self_xmlns()) { to=jid }; 221 Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, get_iq); 222 223 StanzaNode? x_node = result_iq.stanza.get_deep_subnode(NS_URI_OWNER + ":query", DataForms.NS_URI + ":x"); 224 if (x_node != null) { 225 DataForms.DataForm data_form = DataForms.DataForm.create_from_node(x_node); 226 return data_form; 227 } 228 return null; 229 } 230 231 public void set_config_form(XmppStream stream, Jid jid, DataForms.DataForm data_form) { 232 StanzaNode stanza_node = new StanzaNode.build("query", NS_URI_OWNER); 233 stanza_node.add_self_xmlns().put_node(data_form.get_submit_node()); 234 Iq.Stanza set_iq = new Iq.Stanza.set(stanza_node) { to=jid }; 235 stream.get_module(Iq.Module.IDENTITY).send_iq(stream, set_iq); 236 } 237 238 public override void attach(XmppStream stream) { 239 stream.add_flag(new Flag()); 240 stream.get_module(MessageModule.IDENTITY).received_message.connect(on_received_message); 241 stream.get_module(MessageModule.IDENTITY).received_pipeline.connect(received_pipeline_listener); 242 stream.get_module(Presence.Module.IDENTITY).received_presence.connect(check_for_enter_error); 243 stream.get_module(Presence.Module.IDENTITY).received_available.connect(on_received_available); 244 stream.get_module(Presence.Module.IDENTITY).received_unavailable.connect(on_received_unavailable); 245 stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); 246 } 247 248 public override void detach(XmppStream stream) { 249 stream.get_module(MessageModule.IDENTITY).received_message.disconnect(on_received_message); 250 stream.get_module(MessageModule.IDENTITY).received_pipeline.disconnect(received_pipeline_listener); 251 stream.get_module(Presence.Module.IDENTITY).received_presence.disconnect(check_for_enter_error); 252 stream.get_module(Presence.Module.IDENTITY).received_available.disconnect(on_received_available); 253 stream.get_module(Presence.Module.IDENTITY).received_unavailable.disconnect(on_received_unavailable); 254 stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI); 255 } 256 257 public override string get_ns() { return NS_URI; } 258 public override string get_id() { return IDENTITY.id; } 259 260 private void on_received_message(XmppStream stream, MessageStanza message) { 261 if (message.type_ == MessageStanza.TYPE_GROUPCHAT) { 262 StanzaNode? subject_node = message.stanza.get_subnode("subject"); 263 if (subject_node != null) { 264 string subject = subject_node.get_string_content(); 265 stream.get_flag(Flag.IDENTITY).set_muc_subject(message.from, subject); 266 subject_set(stream, subject, message.from); 267 } 268 269 StanzaNode? x_node = message.stanza.get_subnode("x", NS_URI_USER); 270 if (x_node != null) { 271 Gee.List<int> status_codes = get_status_codes(x_node); 272 if (!status_codes.is_empty) { 273 if (status_codes.contains(StatusCode.CONFIG_CHANGE_NON_PRIVACY) || 274 status_codes.contains(StatusCode.NON_ANONYMOUS) || 275 status_codes.contains(StatusCode.SEMI_ANONYMOUS)) { 276 query_room_info.begin(stream, message.from.bare_jid); 277 } 278 } 279 } 280 } 281 } 282 283 private void check_for_enter_error(XmppStream stream, Presence.Stanza presence) { 284 Flag flag = stream.get_flag(Flag.IDENTITY); 285 if (presence.is_error() && flag.is_muc_enter_outstanding() && flag.is_occupant(presence.from)) { 286 Jid bare_jid = presence.from.bare_jid; 287 ErrorStanza? error_stanza = presence.get_error(); 288 if (flag.get_enter_id(bare_jid) == presence.id) { 289 MucEnterError error = MucEnterError.NONE; 290 switch (error_stanza.condition) { 291 case ErrorStanza.CONDITION_NOT_AUTHORIZED: 292 if (ErrorStanza.TYPE_AUTH == error_stanza.type_) error = MucEnterError.PASSWORD_REQUIRED; 293 break; 294 case ErrorStanza.CONDITION_REGISTRATION_REQUIRED: 295 if (ErrorStanza.TYPE_AUTH == error_stanza.type_) error = MucEnterError.NOT_IN_MEMBER_LIST; 296 break; 297 case ErrorStanza.CONDITION_FORBIDDEN: 298 if (ErrorStanza.TYPE_AUTH == error_stanza.type_) error = MucEnterError.BANNED; 299 break; 300 case ErrorStanza.CONDITION_SERVICE_UNAVAILABLE: 301 if (ErrorStanza.TYPE_WAIT == error_stanza.type_) error = MucEnterError.OCCUPANT_LIMIT_REACHED; 302 break; 303 case ErrorStanza.CONDITION_ITEM_NOT_FOUND: 304 if (ErrorStanza.TYPE_CANCEL == error_stanza.type_) error = MucEnterError.ROOM_DOESNT_EXIST; 305 break; 306 case ErrorStanza.CONDITION_CONFLICT: 307 if (ErrorStanza.TYPE_CANCEL == error_stanza.type_) error = MucEnterError.NICK_CONFLICT; 308 break; 309 case ErrorStanza.CONDITION_NOT_ALLOWED: 310 if (ErrorStanza.TYPE_CANCEL == error_stanza.type_) error = MucEnterError.CREATION_RESTRICTED; 311 break; 312 case ErrorStanza.CONDITION_NOT_ACCEPTABLE: 313 if (ErrorStanza.TYPE_CANCEL == error_stanza.type_) error = MucEnterError.USE_RESERVED_ROOMNICK; 314 break; 315 } 316 if (error != MucEnterError.NONE) { 317 flag.enter_futures[bare_jid].set_value(new JoinResult() {muc_error=error}); 318 } else { 319 flag.enter_futures[bare_jid].set_value(new JoinResult() {stanza_error=error_stanza.condition}); 320 } 321 flag.finish_muc_enter(bare_jid); 322 } 323 } 324 } 325 326 private void on_received_available(XmppStream stream, Presence.Stanza presence) { 327 Flag flag = stream.get_flag(Flag.IDENTITY); 328 if (flag.is_occupant(presence.from)) { 329 StanzaNode? x_node = presence.stanza.get_subnode("x", NS_URI_USER); 330 if (x_node != null) { 331 ArrayList<int> status_codes = get_status_codes(x_node); 332 if (status_codes.contains(StatusCode.SELF_PRESENCE)) { 333 Jid bare_jid = presence.from.bare_jid; 334 if (flag.get_enter_id(bare_jid) != null) { 335 336 query_affiliation.begin(stream, bare_jid, "member"); 337 query_affiliation.begin(stream, bare_jid, "admin"); 338 query_affiliation.begin(stream, bare_jid, "owner"); 339 340 flag.finish_muc_enter(bare_jid); 341 flag.enter_futures[bare_jid].set_value(new JoinResult() {nick=presence.from.resourcepart}); 342 } 343 344 flag.set_muc_nick(presence.from); 345 } 346 string? affiliation_str = x_node.get_deep_attribute("item", "affiliation"); 347 Affiliation? affiliation = null; 348 if (affiliation_str != null) { 349 affiliation = parse_affiliation(affiliation_str); 350 flag.set_affiliation(presence.from.bare_jid, presence.from, affiliation); 351 received_occupant_affiliation(stream, presence.from, affiliation); 352 } 353 string? jid_ = x_node.get_deep_attribute("item", "jid"); 354 if (jid_ != null) { 355 try { 356 Jid jid = new Jid(jid_); 357 flag.set_real_jid(presence.from, jid); 358 if (affiliation != null) { 359 stream.get_flag(Flag.IDENTITY).set_offline_member(presence.from, jid, affiliation); 360 } 361 received_occupant_jid(stream, presence.from, jid); 362 } catch (InvalidJidError e) { 363 warning("Received invalid occupant jid: %s", e.message); 364 } 365 } 366 string? role_str = x_node.get_deep_attribute("item", "role"); 367 if (role_str != null) { 368 Role role = parse_role(role_str); 369 flag.set_occupant_role(presence.from, role); 370 received_occupant_role(stream, presence.from, role); 371 } 372 } 373 } 374 } 375 376 private void on_received_unavailable(XmppStream stream, Presence.Stanza presence) { 377 Flag flag = stream.get_flag(Flag.IDENTITY); 378 if (!flag.is_occupant(presence.from)) return; 379 380 StanzaNode? x_node = presence.stanza.get_subnode("x", NS_URI_USER); 381 if (x_node == null) return; 382 383 ArrayList<int> status_codes = get_status_codes(x_node); 384 385 if (StatusCode.SELF_PRESENCE in status_codes) { 386 flag.remove_occupant_info(presence.from); 387 } 388 389 foreach (StatusCode code in USER_REMOVED_CODES) { 390 if (code in status_codes) { 391 if (StatusCode.SELF_PRESENCE in status_codes) { 392 flag.left_muc(stream, presence.from.bare_jid); 393 self_removed_from_room(stream, presence.from, code); 394 Presence.Flag presence_flag = stream.get_flag(Presence.Flag.IDENTITY); 395 presence_flag.remove_presence(presence.from.bare_jid); 396 } else { 397 removed_from_room(stream, presence.from, code); 398 } 399 } 400 } 401 } 402 403 private async void query_room_info(XmppStream stream, Jid jid) { 404 ServiceDiscovery.InfoResult? info_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, jid); 405 if (info_result == null) return; 406 407 Gee.List<Feature> features = new ArrayList<Feature>(); 408 409 foreach (ServiceDiscovery.Identity identity in info_result.identities) { 410 if (identity.category == "conference" && identity.name != null) { 411 stream.get_flag(Flag.IDENTITY).set_room_name(jid, identity.name); 412 } 413 } 414 415 foreach (string feature in info_result.features) { 416 Feature? parsed = null; 417 switch (feature) { 418 case "http://jabber.org/protocol/muc#register": parsed = Feature.REGISTER; break; 419 case "http://jabber.org/protocol/muc#roomconfig": parsed = Feature.ROOMCONFIG; break; 420 case "http://jabber.org/protocol/muc#roominfo": parsed = Feature.ROOMINFO; break; 421 case "http://jabber.org/protocol/muc#stable_id": parsed = Feature.STABLE_ID; break; 422 case "muc_hidden": parsed = Feature.HIDDEN; break; 423 case "muc_membersonly": parsed = Feature.MEMBERS_ONLY; break; 424 case "muc_moderated": parsed = Feature.MODERATED; break; 425 case "muc_nonanonymous": parsed = Feature.NON_ANONYMOUS; break; 426 case "muc_open": parsed = Feature.OPEN; break; 427 case "muc_passwordprotected": parsed = Feature.PASSWORD_PROTECTED; break; 428 case "muc_persistent": parsed = Feature.PERSISTENT; break; 429 case "muc_public": parsed = Feature.PUBLIC; break; 430 case "muc_rooms": parsed = Feature.ROOMS; break; 431 case "muc_semianonymous": parsed = Feature.SEMI_ANONYMOUS; break; 432 case "muc_temporary": parsed = Feature.TEMPORARY; break; 433 case "muc_unmoderated": parsed = Feature.UNMODERATED; break; 434 case "muc_unsecured": parsed = Feature.UNSECURED; break; 435 } 436 if (parsed != null) features.add(parsed); 437 } 438 stream.get_flag(Flag.IDENTITY).set_room_features(jid, features); 439 room_info_updated(stream, jid); 440 } 441 442 private async Gee.List<Jid>? query_affiliation(XmppStream stream, Jid jid, string affiliation) { 443 Iq.Stanza iq = new Iq.Stanza.get( 444 new StanzaNode.build("query", NS_URI_ADMIN) 445 .add_self_xmlns() 446 .put_node(new StanzaNode.build("item", NS_URI_ADMIN) 447 .put_attribute("affiliation", affiliation)) 448 ) { to=jid }; 449 450 451 Iq.Stanza iq_result = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); 452 if (iq_result.is_error()) return null; 453 454 StanzaNode? query_node = iq_result.stanza.get_subnode("query", NS_URI_ADMIN); 455 if (query_node == null) return null; 456 457 Gee.List<StanzaNode> item_nodes = query_node.get_subnodes("item", NS_URI_ADMIN); 458 Gee.List<Jid> ret_jids = new ArrayList<Jid>(Jid.equals_func); 459 foreach (StanzaNode item in item_nodes) { 460 string jid__ = item.get_attribute("jid"); 461 string? affiliation_ = item.get_attribute("affiliation"); 462 if (jid__ != null && affiliation_ != null) { 463 try { 464 Jid jid_ = new Jid(jid__); 465 stream.get_flag(Flag.IDENTITY).set_offline_member(iq_result.from, jid_, parse_affiliation(affiliation_)); 466 ret_jids.add(jid_); 467 received_occupant_jid(stream, iq_result.from, jid_); 468 } catch (InvalidJidError e) { 469 warning("Received invalid occupant jid: %s", e.message); 470 } 471 } 472 } 473 return ret_jids; 474 } 475 476 private static ArrayList<int> get_status_codes(StanzaNode x_node) { 477 ArrayList<int> ret = new ArrayList<int>(); 478 foreach (StanzaNode status_node in x_node.get_subnodes("status", NS_URI_USER)) { 479 ret.add(int.parse(status_node.get_attribute("code"))); 480 } 481 return ret; 482 } 483 484 private static Affiliation parse_affiliation(string affiliation_str) { 485 Affiliation affiliation; 486 switch (affiliation_str) { 487 case "admin": 488 affiliation = Affiliation.ADMIN; break; 489 case "member": 490 affiliation = Affiliation.MEMBER; break; 491 case "outcast": 492 affiliation = Affiliation.OUTCAST; break; 493 case "owner": 494 affiliation = Affiliation.OWNER; break; 495 default: 496 affiliation = Affiliation.NONE; break; 497 } 498 return affiliation; 499 } 500 501 private static Role parse_role(string role_str) { 502 Role role; 503 switch (role_str) { 504 case "moderator": 505 role = Role.MODERATOR; break; 506 case "participant": 507 role = Role.PARTICIPANT; break; 508 case "visitor": 509 role = Role.VISITOR; break; 510 default: 511 role = Role.NONE; break; 512 } 513 return role; 514 } 515} 516 517public class ReceivedPipelineListener : StanzaListener<MessageStanza> { 518 519 private const string[] after_actions_const = {"EXTRACT_MESSAGE_2"}; 520 521 public override string action_group { get { return ""; } } 522 public override string[] after_actions { get { return after_actions_const; } } 523 524 Module outer; 525 526 public ReceivedPipelineListener(Module outer) { 527 this.outer = outer; 528 } 529 530 public override async bool run(XmppStream stream, MessageStanza message) { 531 if (message.type_ == MessageStanza.TYPE_NORMAL) { 532 StanzaNode? x_node = message.stanza.get_subnode("x", NS_URI_USER); 533 if (x_node != null) { 534 StanzaNode? invite_node = x_node.get_subnode("invite", NS_URI_USER); 535 string? password = null; 536 StanzaNode? password_node = x_node.get_subnode("password", NS_URI_USER); 537 if (password_node != null) password = password_node.get_string_content(); 538 if (invite_node != null) { 539 Jid? from_jid = null; 540 try { 541 string from = invite_node.get_attribute("from"); 542 if (from != null) from_jid = new Jid(from); 543 } catch (InvalidJidError e) { 544 warning("Received invite from invalid jid: %s", e.message); 545 } 546 if (from_jid != null) { 547 StanzaNode? reason_node = invite_node.get_subnode("reason", NS_URI_USER); 548 string? reason = null; 549 if (reason_node != null) reason = reason_node.get_string_content(); 550 bool is_mam_message = Xep.MessageArchiveManagement.MessageFlag.get_flag(message) != null; // TODO 551 if (!is_mam_message) outer.invite_received(stream, message.from, from_jid, password, reason); 552 return true; 553 } 554 } 555 } 556 557 StanzaNode? x_field_node = message.stanza.get_subnode("x", DataForms.NS_URI); 558 if (x_field_node != null){ 559 Gee.List<StanzaNode>? fields = x_field_node.get_subnodes("field", DataForms.NS_URI); 560 Jid? from_jid = null; 561 string? nick = null; 562 563 if (fields.size!=0){ 564 foreach (var field_node in fields){ 565 string? var_ = field_node.get_attribute("var"); 566 if (var_ == "muc#jid"){ 567 StanzaNode? value_node = field_node.get_subnode("value", DataForms.NS_URI); 568 try { 569 if (value_node != null) from_jid = new Jid(value_node.get_string_content()); 570 } catch (InvalidJidError e) { 571 return false; 572 } 573 } 574 else if (var_ == "muc#roomnick"){ 575 StanzaNode? value_node = field_node.get_subnode("value", DataForms.NS_URI); 576 if (value_node != null) nick = value_node.get_string_content(); 577 } 578 else if (var_ == "muc#role"){ 579 StanzaNode? value_node = field_node.get_subnode("value", DataForms.NS_URI); 580 if (value_node != null) { 581 if (value_node.get_string_content() != "participant") { 582 warning("Voice request with role other than participant"); 583 } 584 } 585 } 586 } 587 if (from_jid == null || nick == null) { 588 warning("Voice request without from_jid or nick"); 589 return false; 590 } 591 592 outer.voice_request_received(stream, message.from, from_jid, nick); 593 return true; 594 } 595 } 596 } 597 return false; 598 } 599} 600 601} 602