From 4247922e8cc85b488997ebef2121e6f3055e1e26 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Thu, 18 May 2017 23:14:44 +0200 Subject: [PATCH] Member affiliation in occupant list --- libdino/src/service/muc_manager.vala | 21 ++++ main/src/ui/occupant_menu/list.vala | 53 ++++++++- xmpp-vala/src/module/xep/0045_muc/flag.vala | 65 ++++++++--- xmpp-vala/src/module/xep/0045_muc/module.vala | 109 +++++++++++++++--- 4 files changed, 210 insertions(+), 38 deletions(-) diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index 17870b19..bf9b4cda 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -132,6 +132,27 @@ public class MucManager : StreamInteractionModule, Object { return null; } + public Xep.Muc.Affiliation? get_affiliation(Jid muc_jid, Jid jid, Account account) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + return stream.get_flag(Xep.Muc.Flag.IDENTITY).get_affiliation(muc_jid.to_string(), jid.to_string()); + } + return null; + } + + public Gee.List? get_offline_members(Jid jid, Account account) { + Gee.List ret = new ArrayList(Jid.equals_func); + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + Gee.List? members = stream.get_flag(Xep.Muc.Flag.IDENTITY).get_offline_members(jid.to_string()); + if (members == null) return null; + foreach (string member in members) { + ret.add(new Jid(member)); + } + } + return ret; + } + public Jid? get_message_real_jid(Entities.Message message) { if (message.real_jid != null) { return new Jid(message.real_jid); diff --git a/main/src/ui/occupant_menu/list.vala b/main/src/ui/occupant_menu/list.vala index 1e96ece4..ff30b180 100644 --- a/main/src/ui/occupant_menu/list.vala +++ b/main/src/ui/occupant_menu/list.vala @@ -83,11 +83,41 @@ public class List : Box { } private void header(ListBoxRow row, ListBoxRow? before_row) { - if (row.get_header() == null && before_row != null) { - row.set_header(new Separator(Orientation.HORIZONTAL)); + ListRow c1 = row as ListRow; + Xmpp.Xep.Muc.Affiliation a1 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.account); + if (before_row != null) { + ListRow c2 = before_row as ListRow; + Xmpp.Xep.Muc.Affiliation a2 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.account); + if (a1 != a2) { + row.set_header(generate_header_widget(a1, false)); + } else if (row.get_header() != null){ + row.set_header(null); + } + } else { + row.set_header(generate_header_widget(a1, true)); } } + private Widget generate_header_widget(Xmpp.Xep.Muc.Affiliation affiliation, bool top) { + string aff_str; + switch (affiliation) { + case Xmpp.Xep.Muc.Affiliation.OWNER: + aff_str = _("Owner"); break; + case Xmpp.Xep.Muc.Affiliation.ADMIN: + aff_str = _("Admin"); break; + case Xmpp.Xep.Muc.Affiliation.MEMBER: + aff_str = _("Member"); break; + default: + aff_str = _("User"); break; + } + Box box = new Box(Orientation.VERTICAL, 0) { visible=true }; + Label label = new Label("") { margin_left=10, margin_top=top?5:15, xalign=0, visible=true }; + label.set_markup(@"$(Markup.escape_text(aff_str))"); + box.add(label); + box.add(new Separator(Orientation.HORIZONTAL) { visible=true }); + return box; + } + private bool filter(ListBoxRow r) { if (r.get_type().is_a(typeof(ListRow))) { ListRow row = r as ListRow; @@ -102,10 +132,27 @@ public class List : Box { if (row1.get_type().is_a(typeof(ListRow)) && row2.get_type().is_a(typeof(ListRow))) { ListRow c1 = row1 as ListRow; ListRow c2 = row2 as ListRow; - return c1.name_label.label.collate(c2.name_label.label); + int affiliation1 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); + int affiliation2 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); + if (affiliation1 < affiliation2) return -1; + else if (affiliation1 > affiliation2) return 1; + else return c1.name_label.label.collate(c2.name_label.label); } return 0; } + + private int get_affiliation_ranking(Xmpp.Xep.Muc.Affiliation affiliation) { + switch (affiliation) { + case Xmpp.Xep.Muc.Affiliation.OWNER: + return 1; + case Xmpp.Xep.Muc.Affiliation.ADMIN: + return 2; + case Xmpp.Xep.Muc.Affiliation.MEMBER: + return 3; + default: + return 4; + } + } } } \ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0045_muc/flag.vala b/xmpp-vala/src/module/xep/0045_muc/flag.vala index e5e0af6e..159f0193 100644 --- a/xmpp-vala/src/module/xep/0045_muc/flag.vala +++ b/xmpp-vala/src/module/xep/0045_muc/flag.vala @@ -11,21 +11,32 @@ public class Flag : XmppStreamFlag { private HashMap own_nicks = new HashMap(); private HashMap subjects = new HashMap(); private HashMap subjects_by = new HashMap(); + private HashMap occupant_real_jids = new HashMap(); - private HashMap occupant_affiliation = new HashMap(); - private HashMap occupant_role = new HashMap(); + private HashMap> affiliations = new HashMap>(); + private HashMap occupant_role = new HashMap(); public string? get_real_jid(string full_jid) { return occupant_real_jids[full_jid]; } - public void set_real_jid(string full_jid, string real_jid) { occupant_real_jids[full_jid] = real_jid; } + public Gee.List get_offline_members(string full_jid) { + Gee.List ret = new ArrayList(); + foreach (string muc_jid in affiliations.keys) { + foreach (string jid in affiliations[muc_jid].keys) { + if (!jid.has_prefix(muc_jid)) ret.add(jid); + } + } + return ret; + } - public string? get_occupant_affiliation(string full_jid) { return occupant_affiliation[full_jid]; } + public Affiliation? get_affiliation(string muc_jid, string full_jid) { + if (affiliations.has_key(muc_jid) && affiliations[muc_jid].has_key(full_jid)) return affiliations[muc_jid][full_jid]; + return Affiliation.NONE; + } - public void set_occupant_affiliation(string full_jid, string affiliation) { occupant_affiliation[full_jid] = affiliation; } - - public string? get_occupant_role(string full_jid) { return occupant_role[full_jid]; } - - public void set_occupant_role(string full_jid, string role) { occupant_role[full_jid] = role; } + public Role? get_occupant_role(string full_jid) { + if (occupant_role.has_key(full_jid)) return occupant_role[full_jid]; + return Role.NONE; + } public string? get_muc_nick(string bare_jid) { return own_nicks[bare_jid]; } @@ -42,22 +53,41 @@ public class Flag : XmppStreamFlag { public string? get_muc_subject(string bare_jid) { return subjects[bare_jid]; } - public void set_muc_subject(string full_jid, string? subject) { + internal void set_real_jid(string full_jid, string real_jid) { occupant_real_jids[full_jid] = real_jid; } + + internal void set_offline_member(string muc_jid, string real_jid, Affiliation affiliation) { + set_affiliation(muc_jid, real_jid, affiliation); + } + + internal void set_affiliation(string muc_jid, string full_jid, Affiliation affiliation) { + if (!affiliations.has_key(muc_jid)) affiliations[muc_jid] = new HashMap(); + if (affiliation == Affiliation.NONE) { + affiliations[muc_jid].unset(full_jid); + } else { + affiliations[muc_jid][full_jid] = affiliation; + } + } + + internal void set_occupant_role(string full_jid, Role role) { + occupant_role[full_jid] = role; + } + + internal void set_muc_subject(string full_jid, string? subject) { string bare_jid = get_bare_jid(full_jid); subjects[bare_jid] = subject; subjects_by[bare_jid] = full_jid; } - public void start_muc_enter(string bare_jid, string presence_id) { + internal void start_muc_enter(string bare_jid, string presence_id) { enter_ids[bare_jid] = presence_id; } - public void finish_muc_enter(string bare_jid, string? nick = null) { + internal void finish_muc_enter(string bare_jid, string? nick = null) { if (nick != null) own_nicks[bare_jid] = nick; enter_ids.unset(bare_jid); } - public void left_muc(XmppStream stream, string muc) { + internal void left_muc(XmppStream stream, string muc) { own_nicks.unset(muc); subjects.unset(muc); subjects_by.unset(muc); @@ -69,15 +99,16 @@ public class Flag : XmppStreamFlag { } } - public void remove_occupant_info(string full_jid) { + internal void remove_occupant_info(string full_jid) { occupant_real_jids.unset(full_jid); - occupant_affiliation.unset(full_jid); + string bare_jid = get_bare_jid(full_jid); + if (affiliations.has_key(full_jid)) affiliations[bare_jid].unset(full_jid); occupant_role.unset(full_jid); } - public override string get_ns() { return NS_URI; } + internal override string get_ns() { return NS_URI; } - public override string get_id() { return IDENTITY.id; } + internal override string get_id() { return IDENTITY.id; } } } \ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index badb7d60..9893f8a3 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -8,17 +8,6 @@ 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, @@ -28,12 +17,27 @@ public enum MucEnterError { ROOM_DOESNT_EXIST } +public enum Affiliation { + ADMIN, + MEMBER, + NONE, + OUTCAST, + OWNER +} + +public enum Role { + MODERATOR, + NONE, + PARTICIPANT, + VISITOR +} + 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_affiliation(XmppStream stream, string jid, Affiliation? 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 received_occupant_role(XmppStream stream, string jid, Role? role); public signal void subject_set(XmppStream stream, string subject, string jid); public signal void room_configuration_changed(XmppStream stream, string jid, StatusCode code); @@ -92,6 +96,12 @@ public class Module : XmppStreamModule { if (stream.get_module(ServiceDiscovery.Module.IDENTITY) != null) { stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); } + + room_entered.connect((stream, jid, nick) => { + query_affiliation(stream, jid, "member", null, null); + query_affiliation(stream, jid, "admin", null, null); + query_affiliation(stream, jid, "owner", null, null); + }); } public override void detach(XmppStream stream) { @@ -167,8 +177,10 @@ public class Module : XmppStreamModule { flag.finish_muc_enter(bare_jid, get_resource_part(presence.from)); } } - string? affiliation = x_node.get_deep_attribute("item", "affiliation"); - if (affiliation != null) { + string? affiliation_str = x_node.get_deep_attribute("item", "affiliation"); + if (affiliation_str != null) { + Affiliation affiliation = parse_affiliation(affiliation_str); + flag.set_affiliation(get_bare_jid(presence.from), presence.from, affiliation); received_occupant_affiliation(stream, presence.from, affiliation); } string? jid = x_node.get_deep_attribute("item", "jid"); @@ -176,8 +188,10 @@ public class Module : XmppStreamModule { flag.set_real_jid(presence.from, jid); received_occupant_jid(stream, presence.from, jid); } - string? role = x_node.get_deep_attribute("item", "role"); - if (role != null) { + string? role_str = x_node.get_deep_attribute("item", "role"); + if (role_str != null) { + Role role = parse_role(role_str); + flag.set_occupant_role(presence.from, role); received_occupant_role(stream, presence.from, role); } } @@ -211,13 +225,72 @@ public class Module : XmppStreamModule { } } - private ArrayList get_status_codes(StanzaNode x_node) { + [CCode (has_target = false)] public delegate void OnResult(XmppStream stream, Gee.List jids, Object? store); + private void query_affiliation(XmppStream stream, string jid, string affiliation, OnResult? on_result, Object? store) { + Iq.Stanza iq = new Iq.Stanza.get( + new StanzaNode.build("query", NS_URI_ADMIN) + .add_self_xmlns() + .put_node(new StanzaNode.build("item", NS_URI_ADMIN) + .put_attribute("affiliation", affiliation)) + ) { to=jid }; + stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, iq, store) => { + Tuple tuple = store as Tuple; + if (iq.is_error()) return; + StanzaNode? query_node = iq.stanza.get_subnode("query", NS_URI_ADMIN); + if (query_node == null) return; + Gee.List item_nodes = query_node.get_subnodes("item", NS_URI_ADMIN); + Gee.List ret_jids = new ArrayList(); + foreach (StanzaNode item in item_nodes) { + string? jid_ = item.get_attribute("jid"); + string? affiliation_ = item.get_attribute("affiliation"); + if (jid_ != null && affiliation_ != null) { + stream.get_flag(Muc.Flag.IDENTITY).set_offline_member(iq.from, jid_, parse_affiliation(affiliation_)); + ret_jids.add(jid_); + } + } + if (tuple.a != null) tuple.a(stream, ret_jids, tuple.b); + }, Tuple.create(on_result, store)); + } + + private static 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; } + + private static Affiliation parse_affiliation(string affiliation_str) { + Affiliation affiliation; + switch (affiliation_str) { + case "admin": + affiliation = Affiliation.ADMIN; break; + case "member": + affiliation = Affiliation.MEMBER; break; + case "outcast": + affiliation = Affiliation.OUTCAST; break; + case "owner": + affiliation = Affiliation.OWNER; break; + default: + affiliation = Affiliation.NONE; break; + } + return affiliation; + } + + private static Role parse_role(string role_str) { + Role role; + switch (role_str) { + case "moderator": + role = Role.MODERATOR; break; + case "participant": + role = Role.PARTICIPANT; break; + case "visitor": + role = Role.VISITOR; break; + default: + role = Role.NONE; break; + } + return role; + } } }