using Gee; using Xmpp.Core; namespace Xmpp.Xep.Muc { private const string NS_URI = "http://jabber.org/protocol/muc"; private const string NS_URI_ADMIN = NS_URI + "#admin"; private const string NS_URI_USER = NS_URI + "#user"; public const string AFFILIATION_ADMIN = "admin"; public const string AFFILIATION_MEMBER = "member"; public const string AFFILIATION_NONE = "none"; public const string AFFILIATION_OUTCAST = "outcast"; public const string AFFILIATION_OWNER = "owner"; public const string ROLE_MODERATOR = "moderator"; public const string ROLE_NONE = "none"; public const string ROLE_PARTICIPANT = "participant"; public const string ROLE_VISITOR = "visitor"; public enum MucEnterError { PASSWORD_REQUIRED, NOT_IN_MEMBER_LIST, BANNED, NICK_CONFLICT, OCCUPANT_LIMIT_REACHED, ROOM_DOESNT_EXIST } public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0045_muc_module"); public signal void received_occupant_affiliation(XmppStream stream, string jid, string? affiliation); public signal void received_occupant_jid(XmppStream stream, string jid, string? real_jid); public signal void received_occupant_role(XmppStream stream, string jid, string? role); public signal void subject_set(XmppStream stream, string subject, string jid); public void enter(XmppStream stream, string bare_jid, string nick, string? password, ListenerHolder.OnSuccess success_listener, ListenerHolder.OnError error_listener, Object? store) { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid + "/" + nick; StanzaNode x_node = new StanzaNode.build("x", NS_URI).add_self_xmlns(); if (password != null) { x_node.put_node(new StanzaNode.build("password", NS_URI).put_node(new StanzaNode.text(password))); } presence.stanza.put_node(x_node); stream.get_flag(Flag.IDENTITY).start_muc_enter(bare_jid, presence.id, new ListenerHolder(success_listener, error_listener, store)); stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence); } public void exit(XmppStream stream, string jid) { string nick = stream.get_flag(Flag.IDENTITY).get_muc_nick(jid); Presence.Stanza presence = new Presence.Stanza(); presence.to = jid + "/" + nick; presence.type_ = Presence.Stanza.TYPE_UNAVAILABLE; stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence); } public void change_subject(XmppStream stream, string jid, string subject) { Message.Stanza message = new Message.Stanza(); message.to = jid; message.type_ = Message.Stanza.TYPE_GROUPCHAT; message.stanza.put_node((new StanzaNode.build("subject")).put_node(new StanzaNode.text(subject))); stream.get_module(Message.Module.IDENTITY).send_message(stream, message); } public void change_nick(XmppStream stream, string jid, string new_nick) { Presence.Stanza presence = new Presence.Stanza(); presence.to = jid + "/" + new_nick; stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence); } public void kick(XmppStream stream, string jid, string nick) { change_role(stream, jid, nick, "none"); } public override void attach(XmppStream stream) { stream.add_flag(new Muc.Flag()); Message.Module.require(stream); stream.get_module(Message.Module.IDENTITY).received_message.connect(on_received_message); Presence.Module.require(stream); stream.get_module(Presence.Module.IDENTITY).received_presence.connect(on_received_presence); stream.get_module(Presence.Module.IDENTITY).received_available.connect(on_received_available); stream.get_module(Presence.Module.IDENTITY).received_unavailable.connect(on_received_unavailable); if (stream.get_module(ServiceDiscovery.Module.IDENTITY) != null) { stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); } } public override void detach(XmppStream stream) { stream.get_module(Message.Module.IDENTITY).received_message.disconnect(on_received_message); stream.get_module(Presence.Module.IDENTITY).received_presence.disconnect(on_received_presence); stream.get_module(Presence.Module.IDENTITY).received_available.disconnect(on_received_available); stream.get_module(Presence.Module.IDENTITY).received_unavailable.disconnect(on_received_unavailable); } public static void require(XmppStream stream) { Presence.Module.require(stream); if (stream.get_module(IDENTITY) == null) stream.add_module(new Muc.Module()); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } private void change_role(XmppStream stream, string jid, string nick, string new_role) { StanzaNode query = new StanzaNode.build("query", NS_URI_ADMIN).add_self_xmlns(); 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)); Iq.Stanza iq = new Iq.Stanza.set(query); iq.to = jid; stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); } private void on_received_message(XmppStream stream, Message.Stanza message) { if (message.type_ == Message.Stanza.TYPE_GROUPCHAT) { StanzaNode? subject_node = message.stanza.get_subnode("subject"); if (subject_node != null) { string subject = subject_node.get_string_content(); stream.get_flag(Flag.IDENTITY).set_muc_subject(message.from, subject); subject_set(stream, subject, message.from); } } } private void on_received_presence(XmppStream stream, Presence.Stanza presence) { Flag flag = stream.get_flag(Flag.IDENTITY); if (presence.is_error() && flag.is_muc_enter_outstanding() && flag.is_occupant(presence.from)) { string bare_jid = get_bare_jid(presence.from); ErrorStanza? error_stanza = presence.get_error(); if (flag.get_enter_id(bare_jid) == error_stanza.original_id) { ListenerHolder listener = flag.get_enter_listener(bare_jid); MucEnterError? error = null; if (error_stanza.condition == ErrorStanza.CONDITION_NOT_AUTHORIZED && ErrorStanza.TYPE_AUTH == error_stanza.type_) { error = MucEnterError.PASSWORD_REQUIRED; } else if (ErrorStanza.CONDITION_REGISTRATION_REQUIRED == error_stanza.condition && ErrorStanza.TYPE_AUTH == error_stanza.type_) { error = MucEnterError.NOT_IN_MEMBER_LIST; } else if (ErrorStanza.CONDITION_FORBIDDEN == error_stanza.condition && ErrorStanza.TYPE_AUTH == error_stanza.type_) { error = MucEnterError.BANNED; } else if (ErrorStanza.CONDITION_CONFLICT == error_stanza.condition && ErrorStanza.TYPE_CANCEL == error_stanza.type_) { error = MucEnterError.NICK_CONFLICT; } else if (ErrorStanza.CONDITION_SERVICE_UNAVAILABLE == error_stanza.condition && ErrorStanza.TYPE_WAIT == error_stanza.type_) { error = MucEnterError.OCCUPANT_LIMIT_REACHED; } else if (ErrorStanza.CONDITION_ITEM_NOT_FOUND == error_stanza.condition && ErrorStanza.TYPE_CANCEL == error_stanza.type_) { error = MucEnterError.ROOM_DOESNT_EXIST; } if (error == null) listener.on_error(stream, error, listener.reference); flag.finish_muc_enter(bare_jid); } } } private void on_received_available(XmppStream stream, Presence.Stanza presence) { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.is_occupant(presence.from)) { StanzaNode? x_node = presence.stanza.get_subnode("x", NS_URI_USER); if (x_node != null) { ArrayList status_codes = get_status_codes(x_node); if (status_codes.contains(StatusCode.SELF_PRESENCE)) { string bare_jid = get_bare_jid(presence.from); ListenerHolder listener = flag.get_enter_listener(bare_jid); listener.on_success(stream, listener.reference); flag.finish_muc_enter(bare_jid, get_resource_part(presence.from)); } string? affiliation = x_node["item", "affiliation"].val; if (affiliation != null) { received_occupant_affiliation(stream, presence.from, affiliation); } string? jid = x_node["item", "jid"].val; if (jid != null) { flag.set_real_jid(presence.from, jid); received_occupant_jid(stream, presence.from, jid); } string? role = x_node["item", "role"].val; if (role != null) { received_occupant_role(stream, presence.from, role); } } } } private void on_received_unavailable(XmppStream stream, string jid) { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.is_occupant(jid)) { flag.remove_occupant_info(jid); } } private ArrayList get_status_codes(StanzaNode x_node) { ArrayList ret = new ArrayList(); foreach (StanzaNode status_node in x_node.get_subnodes("status", NS_URI_USER)) { ret.add(int.parse(status_node.get_attribute("code"))); } return ret; } } public enum StatusCode { /** Inform user that any occupant is allowed to see the user's full JID */ JID_VISIBLE = 100, /** Inform user that his or her affiliation changed while not in the room */ AFFILIATION_CHANGED = 101, /** Inform occupants that room now shows unavailable members */ SHOWS_UNAVIABLE_MEMBERS = 102, /** Inform occupants that room now does not show unavailable members */ SHOWS_UNAVIABLE_MEMBERS_NOT = 103, /** Inform occupants that a non-privacy-related room configuration change has occurred */ CONFIG_CHANGE_NON_PRIVACY = 104, /** Inform user that presence refers to itself */ SELF_PRESENCE = 110, /** Inform occupants that room logging is now enabled */ LOGGING_ENABLED = 170, /** Inform occupants that room logging is now disabled */ LOGGING_DISABLED = 171, /** Inform occupants that the room is now non-anonymous */ NON_ANONYMOUS = 172, /** Inform occupants that the room is now semi-anonymous */ SEMI_ANONYMOUS = 173, /** Inform user that a new room has been created */ NEW_ROOM_CREATED = 201, /** Inform user that service has assigned or modified occupant's roomnick */ MODIFIED_NICK = 210, /** Inform user that he or she has been banned from the room */ BANNED = 301, /** Inform all occupants of new room nickname */ ROOM_NICKNAME = 303, /** Inform user that he or she has been kicked from the room */ KICKED = 307, /** Inform user that he or she is being removed from the room */ REMOVED_AFFILIATION_CHANGE = 321, /** Inform user that he or she is being removed from the room because the room has been changed to members-only and the user is not a member */ REMOVED_MEMBERS_ONLY = 322, /** Inform user that he or she is being removed from the room because the MUC service is being shut down */ REMOVED_SHUTDOWN = 332 } public class ListenerHolder { [CCode (has_target = false)] public delegate void OnSuccess(XmppStream stream, Object? store); public OnSuccess on_success { get; private set; } [CCode (has_target = false)] public delegate void OnError(XmppStream stream, MucEnterError error, Object? store); public OnError on_error { get; private set; } public Object? reference { get; private set; } public ListenerHolder(OnSuccess on_success, OnError on_error, Object? reference = null) { this.on_success = on_success; this.reference = reference; } } }