diff options
| -rw-r--r-- | dino.doap | 8 | ||||
| -rw-r--r-- | dino.doap.in | 8 | ||||
| -rw-r--r-- | libdino/meson.build | 1 | ||||
| -rw-r--r-- | libdino/src/application.vala | 2 | ||||
| -rw-r--r-- | libdino/src/service/contact_model.vala | 2 | ||||
| -rw-r--r-- | libdino/src/service/database.vala | 34 | ||||
| -rw-r--r-- | libdino/src/service/muc_affiliations.vala | 82 | ||||
| -rw-r--r-- | libdino/src/service/muc_manager.vala | 68 | ||||
| -rw-r--r-- | main/src/ui/widgets/avatar_picture.vala | 10 | ||||
| -rw-r--r-- | xmpp-vala/meson.build | 1 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0045_muc/module.vala | 62 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0272_muji.vala | 2 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0463_muc_affiliations_versioning.vala | 24 |
13 files changed, 263 insertions, 41 deletions
@@ -780,6 +780,14 @@ </implements> <implements> <xmpp:SupportedXep> + <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0463.html" /> + <xmpp:status>complete</xmpp:status> + <xmpp:version>0.2.0</xmpp:version> + <xmpp:since>0.6</xmpp:since> + </xmpp:SupportedXep> + </implements> + <implements> + <xmpp:SupportedXep> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0482.html"/> <xmpp:status>partial</xmpp:status> <xmpp:version>0.1.0</xmpp:version> diff --git a/dino.doap.in b/dino.doap.in index 7716efc5..e234e44c 100644 --- a/dino.doap.in +++ b/dino.doap.in @@ -600,6 +600,14 @@ </implements> <implements> <xmpp:SupportedXep> + <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0463.html" /> + <xmpp:status>complete</xmpp:status> + <xmpp:version>0.2.0</xmpp:version> + <xmpp:since>0.6</xmpp:since> + </xmpp:SupportedXep> + </implements> + <implements> + <xmpp:SupportedXep> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0482.html" /> <xmpp:status>partial</xmpp:status> <xmpp:version>0.1.0</xmpp:version> diff --git a/libdino/meson.build b/libdino/meson.build index 5b537251..73b1c512 100644 --- a/libdino/meson.build +++ b/libdino/meson.build @@ -63,6 +63,7 @@ sources = files( 'src/service/message_processor.vala', 'src/service/message_storage.vala', 'src/service/module_manager.vala', + 'src/service/muc_affiliations.vala', 'src/service/muc_manager.vala', 'src/service/notification_events.vala', 'src/service/occupant_id_store.vala', diff --git a/libdino/src/application.vala b/libdino/src/application.vala index ef2d883e..19650495 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -42,7 +42,7 @@ public interface Application : GLib.Application { Calls.start(stream_interactor, db); ConversationManager.start(stream_interactor, db); OccupantIdStore.start(stream_interactor, db); - MucManager.start(stream_interactor); + MucManager.start(stream_interactor, db); AvatarManager.start(stream_interactor, db); RosterManager.start(stream_interactor, db); FileManager.start(stream_interactor, db); diff --git a/libdino/src/service/contact_model.vala b/libdino/src/service/contact_model.vala index 312df4f7..33f6126b 100644 --- a/libdino/src/service/contact_model.vala +++ b/libdino/src/service/contact_model.vala @@ -27,7 +27,7 @@ namespace Dino { stream_interactor.get_module(MucManager.IDENTITY).room_info_updated.connect((account, jid) => { check_update_models(account, jid, Conversation.Type.GROUPCHAT); }); - stream_interactor.get_module(MucManager.IDENTITY).private_room_occupant_updated.connect((account, room, occupant) => { + stream_interactor.get_module(MucManager.IDENTITY).private_room_occupants_updated.connect((account, room) => { check_update_models(account, room, Conversation.Type.GROUPCHAT); }); stream_interactor.get_module(MucManager.IDENTITY).subject_set.connect((account, jid, subject) => { diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 54f5ab42..7a5b940a 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -7,7 +7,7 @@ using Dino.Entities; namespace Dino { public class Database : Qlite.Database { - private const int VERSION = 31; + private const int VERSION = 32; public class AccountTable : Table { public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true }; @@ -385,6 +385,32 @@ public class Database : Qlite.Database { } } + public class MucAffiliationTable : Table { + public Column<int> account_id = new Column.Integer("account_id"); + public Column<int> muc_jid_id = new Column.Integer("muc_jid_id"); + public Column<int> real_jid_id = new Column.Integer("real_jid_id"); + public Column<int> affiliation = new Column.Integer("affiliation"); + + internal MucAffiliationTable(Database db) { + base(db, "muc_affiliation"); + init({account_id, muc_jid_id, real_jid_id, affiliation}); + index("muc_affiliation_idx", {muc_jid_id}); + unique({account_id, muc_jid_id, real_jid_id}, "REPLACE"); + } + } + + public class MucAffiliationVersionTable : Table { + public Column<int> account_id = new Column.Integer("account_id"); + public Column<int> muc_jid_id = new Column.Integer("muc_jid_id"); + public Column<string> version = new Column.Text("version"); + + internal MucAffiliationVersionTable(Database db) { + base(db, "muc_affiliation_version"); + init({account_id, muc_jid_id, version}); + unique({account_id, muc_jid_id}, "REPLACE"); + } + } + public class ReactionTable : Table { public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column<int> account_id = new Column.Integer("account_id") { not_null = true }; @@ -473,6 +499,8 @@ public class Database : Qlite.Database { public RosterTable roster { get; private set; } public MamCatchupTable mam_catchup { get; private set; } public ReactionTable reaction { get; private set; } + public MucAffiliationTable muc_affiliation { get; private set; } + public MucAffiliationVersionTable muc_affiliation_version { get; private set; } public SettingsTable settings { get; private set; } public AccountSettingsTable account_settings { get; private set; } public ConversationSettingsTable conversation_settings { get; private set; } @@ -507,10 +535,12 @@ public class Database : Qlite.Database { roster = new RosterTable(this); mam_catchup = new MamCatchupTable(this); reaction = new ReactionTable(this); + muc_affiliation = new MucAffiliationTable(this); + muc_affiliation_version = new MucAffiliationVersionTable(this); settings = new SettingsTable(this); account_settings = new AccountSettingsTable(this); conversation_settings = new ConversationSettingsTable(this); - init({ account, jid, entity, content_item, message, message_occupant_id, body_meta, message_correction, reply, real_jid, occupantid, file_transfer, file_hashes, file_thumbnails, sfs_sources, call, call_counterpart, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, reaction, settings, account_settings, conversation_settings }); + init({ account, jid, entity, content_item, message, message_occupant_id, body_meta, message_correction, reply, real_jid, occupantid, file_transfer, file_hashes, file_thumbnails, sfs_sources, call, call_counterpart, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, reaction, muc_affiliation, muc_affiliation_version, settings, account_settings, conversation_settings }); try { exec("PRAGMA journal_mode = WAL"); diff --git a/libdino/src/service/muc_affiliations.vala b/libdino/src/service/muc_affiliations.vala new file mode 100644 index 00000000..41107a93 --- /dev/null +++ b/libdino/src/service/muc_affiliations.vala @@ -0,0 +1,82 @@ +using Gee; + +using Dino.Entities; +using Xmpp; +using Xmpp.Xep; + +namespace Dino { + public class MucAffiliations { + + private Database db; + + public MucAffiliations(Database db) { + this.db = db; + } + + public string? get_mav_version(Account account, Jid muc_jid) { + Qlite.RowOption version_rowopt = db.muc_affiliation_version.select() + .with(db.muc_affiliation_version.account_id, "=", account.id) + .with(db.muc_affiliation_version.muc_jid_id, "=", db.get_jid_id(muc_jid)) + .single() + .row(); + + return version_rowopt.is_present() ? version_rowopt.inner[db.muc_affiliation_version.version] : null; + } + + public void clear_affiliations(Account account, Jid muc_jid) { + db.muc_affiliation.delete() + .with(db.muc_affiliation.account_id, "=", account.id) + .with(db.muc_affiliation.muc_jid_id, "=", db.get_jid_id(muc_jid)) + .perform(); + } + + public void clear_mav_version(Account account, Jid muc_jid) { + db.muc_affiliation_version.delete() + .with(db.muc_affiliation_version.account_id, "=", account.id) + .with(db.muc_affiliation_version.muc_jid_id, "=", db.get_jid_id(muc_jid)) + .perform(); + } + + public void update_affiliations(Account account, Jid muc_jid, HashMap<Jid, Muc.Affiliation> affiliations, string? mav_since, string? mav_until) { + // Update version (if MUC affiliation versioning is used) + if (mav_until != null && mav_since == null) { + clear_affiliations(account, muc_jid); + } else if (mav_until != null) { + string? previous_version = get_mav_version(account, muc_jid); + if (mav_since != previous_version) { + warning("Got an unexpected affiliations version 'since'. Expected %s; got %s", previous_version, mav_since); + clear_affiliations(account, muc_jid); + clear_mav_version(account, muc_jid); + } + } + + if (mav_until != null) { + db.muc_affiliation_version.upsert() + .value(db.muc_affiliation_version.account_id, account.id, true) + .value(db.muc_affiliation_version.muc_jid_id, db.get_jid_id(muc_jid), true) + .value(db.muc_affiliation_version.version, mav_until) + .perform(); + } + + // Update affiliations + foreach (Jid real_jid in affiliations.keys) { + Muc.Affiliation affiliation = affiliations[real_jid]; + + if (affiliation == Muc.Affiliation.NONE) { + db.muc_affiliation.delete() + .with(db.muc_affiliation.account_id, "=", account.id) + .with(db.muc_affiliation.muc_jid_id, "=", db.get_jid_id(muc_jid)) + .with(db.muc_affiliation.real_jid_id, "=", db.get_jid_id(real_jid)) + .perform(); + } else { + db.muc_affiliation.insert() + .value(db.muc_affiliation.account_id, account.id) + .value(db.muc_affiliation.muc_jid_id, db.get_jid_id(muc_jid)) + .value(db.muc_affiliation.real_jid_id, db.get_jid_id(real_jid)) + .value(db.muc_affiliation.affiliation, (int)affiliation) + .perform(); + } + } + } + } +}
\ No newline at end of file diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index 29c6c5ca..b47a52bf 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -12,7 +12,7 @@ public class MucManager : StreamInteractionModule, Object { public signal void left(Account account, Jid jid); public signal void subject_set(Account account, Jid jid, string? subject); public signal void room_info_updated(Account account, Jid muc_jid); - public signal void private_room_occupant_updated(Account account, Jid room, Jid occupant); + public signal void private_room_occupants_updated(Account account, Jid room); public signal void invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason); public signal void voice_request_received(Account account, Jid room_jid, Jid from_jid, string nick); public signal void received_occupant_role(Account account, Jid jid, Xep.Muc.Role? role); @@ -21,6 +21,9 @@ public class MucManager : StreamInteractionModule, Object { public signal void conference_removed(Account account, Jid jid); private StreamInteractor stream_interactor; + private Database db; + private MucAffiliations muc_affiliations; + private HashMap<Account, HashSet<Jid>> mucs_joined = new HashMap<Account, HashSet<Jid>>(Account.hash_func, Account.equals_func); private HashMap<Account, HashSet<Jid>> mucs_joining = new HashMap<Account, HashSet<Jid>>(Account.hash_func, Account.equals_func); private HashMap<Account, HashMap<Jid, Cancellable>> mucs_sync_cancellables = new HashMap<Account, HashMap<Jid, Cancellable>>(Account.hash_func, Account.equals_func); @@ -31,13 +34,16 @@ public class MucManager : StreamInteractionModule, Object { public HashMap<Account, Jid> default_muc_server = new HashMap<Account, Jid>(Account.hash_func, Account.equals_func); private HashMap<Account, HashMap<Jid, string>> own_occupant_ids = new HashMap<Account, HashMap<Jid, string>>(Account.hash_func, Account.equals_func); - public static void start(StreamInteractor stream_interactor) { - MucManager m = new MucManager(stream_interactor); + public static void start(StreamInteractor stream_interactor, Database db) { + MucManager m = new MucManager(stream_interactor, db); stream_interactor.add_module(m); } - private MucManager(StreamInteractor stream_interactor) { + private MucManager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; + this.db = db; + this.muc_affiliations = new MucAffiliations(db); + this.received_message_listener = new ReceivedMessageListener(stream_interactor); stream_interactor.account_added.connect(on_account_added); stream_interactor.stream_negotiated.connect(on_stream_negotiated); @@ -86,7 +92,9 @@ public class MucManager : StreamInteractionModule, Object { } mucs_joining[account].add(jid); - Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since, receive_history, null); + string? mav_version = muc_affiliations.get_mav_version(account, jid); + + Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since, receive_history, null, mav_version); mucs_joining[account].remove(jid); @@ -100,6 +108,14 @@ public class MucManager : StreamInteractionModule, Object { joined_conversation.nickname = nick; stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(joined_conversation); + bool can_do_affiliation_versioning = yield entity_info.has_feature(account, jid, Xmpp.Xep.MucAffiliationsVersioning.NS_URI); + if (!can_do_affiliation_versioning) { + stream.get_module(Xep.Muc.Module.IDENTITY).fetch_affiliations.begin(stream, jid.bare_jid, () => { + muc_affiliations.clear_affiliations(account, jid); + persist_muc_affiliations(account, jid.bare_jid); + }); + } + if (can_do_mam) { var history_sync = stream_interactor.get_module(MessageProcessor.IDENTITY).history_sync; if (conversation == null) { @@ -371,11 +387,16 @@ public class MucManager : StreamInteractionModule, Object { } public Gee.List<Jid>? get_offline_members(Jid jid, Account account) { - Xep.Muc.Flag? flag = get_muc_flag(account); - if (flag != null) { - return flag.get_offline_members(jid); + Gee.List<Jid> ret = new ArrayList<Jid>(Jid.equals_func); + + var qry = db.muc_affiliation.select() + .with(db.muc_affiliation.account_id, "=", account.id) + .with(db.muc_affiliation.muc_jid_id, "=", db.get_jid_id(jid)); + + foreach (Qlite.Row affiliation_row in qry) { + ret.add(db.get_jid_by_id(affiliation_row[db.muc_affiliation.real_jid_id])); } - return null; + return ret; } public Gee.List<Jid>? get_other_offline_members(Jid jid, Account account) { @@ -444,14 +465,15 @@ public class MucManager : StreamInteractionModule, Object { stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_occupant_role.connect( (stream, from_jid, role) => { received_occupant_role(account, from_jid, role); }); + stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_affiliations.connect( (stream, muc_jid, affiliations, mav_since, mav_until) => { + muc_affiliations.update_affiliations(account, muc_jid, affiliations, mav_since, mav_until); + if (is_private_room(account, muc_jid)) { + private_room_occupants_updated(account, muc_jid); + } + }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).room_info_updated.connect( (stream, muc_jid) => { room_info_updated(account, muc_jid); }); - stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_occupant_jid.connect( (stream, room, occupant) => { - if (is_private_room(account, room.bare_jid)) { - private_room_occupant_updated(account, room, occupant); - } - }); stream_interactor.module_manager.get_module(account, Xep.OccupantIds.Module.IDENTITY).received_own_occupant_id.connect( (stream, jid, occupant_id) => { if (!own_occupant_ids.has_key(account)) { own_occupant_ids[account] = new HashMap<Jid, string>(Jid.hash_bare_func, Jid.equals_bare_func); @@ -555,6 +577,24 @@ public class MucManager : StreamInteractionModule, Object { }); } + private void persist_muc_affiliations(Account account, Jid muc_jid) { + Xep.Muc.Flag? flag = get_muc_flag(account); + if (flag == null) return; + + var affiliations = new HashMap<Jid, Muc.Affiliation>(); + + Gee.List<Jid> offline_members = flag.get_offline_members(muc_jid); + foreach (Jid offline_member in offline_members) { + Xep.Muc.Affiliation affiliation = flag.get_affiliation(muc_jid, offline_member); + affiliations[offline_member] = affiliation; + } + + muc_affiliations.update_affiliations(account, muc_jid, affiliations, null, null); + if (is_private_room(account, muc_jid)) { + private_room_occupants_updated(account, muc_jid); + } + } + private void join_all_active(Account account) { Gee.List<Conversation> conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account); foreach (Conversation conversation in conversations) { diff --git a/main/src/ui/widgets/avatar_picture.vala b/main/src/ui/widgets/avatar_picture.vala index 6765364c..bfad4d22 100644 --- a/main/src/ui/widgets/avatar_picture.vala +++ b/main/src/ui/widgets/avatar_picture.vala @@ -96,17 +96,17 @@ public class Dino.Ui.ViewModel.CompatAvatarPictureModel : AvatarPictureModel { private static void connect_signals_weak(CompatAvatarPictureModel model_) { WeakRef model_weak = WeakRef(model_); - ulong muc_manager_private_room_occupant_updated_handler_id = 0; + ulong muc_manager_private_room_occupants_updated_handler_id = 0; ulong muc_manager_proom_info_updated_handler_id = 0; ulong avatar_manager_received_avatar_handler_id = 0; ulong avatar_manager_fetched_avatar_handler_id = 0; - muc_manager_private_room_occupant_updated_handler_id = model_.muc_manager.private_room_occupant_updated.connect((muc_manager, account, room, jid) => { + muc_manager_private_room_occupants_updated_handler_id = model_.muc_manager.private_room_occupants_updated.connect((muc_manager, account, room) => { CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get(); if (model != null) { model.on_room_updated(account, room); - } else if (muc_manager_private_room_occupant_updated_handler_id != 0) { - muc_manager.disconnect(muc_manager_private_room_occupant_updated_handler_id); - muc_manager_private_room_occupant_updated_handler_id = 0; + } else if (muc_manager_private_room_occupants_updated_handler_id != 0) { + muc_manager.disconnect(muc_manager_private_room_occupants_updated_handler_id); + muc_manager_private_room_occupants_updated_handler_id = 0; } }); muc_manager_proom_info_updated_handler_id = model_.muc_manager.room_info_updated.connect((muc_manager, account, room) => { diff --git a/xmpp-vala/meson.build b/xmpp-vala/meson.build index 940b9877..f0ba7260 100644 --- a/xmpp-vala/meson.build +++ b/xmpp-vala/meson.build @@ -134,6 +134,7 @@ sources = files( 'src/module/xep/0446_file_metadata_element.vala', 'src/module/xep/0447_stateless_file_sharing.vala', 'src/module/xep/0461_replies.vala', + 'src/module/xep/0463_muc_affiliations_versioning.vala', 'src/module/xep/0482_call_invites.vala', 'src/file_content_type.vala', 'src/util.vala', diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index 88ff819f..bdaabb3e 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -2,10 +2,10 @@ using Gee; namespace Xmpp.Xep.Muc { -private const string NS_URI = "http://jabber.org/protocol/muc"; +public const string NS_URI = "http://jabber.org/protocol/muc"; private const string NS_URI_ADMIN = NS_URI + "#admin"; private const string NS_URI_OWNER = NS_URI + "#owner"; -private const string NS_URI_USER = NS_URI + "#user"; +public const string NS_URI_USER = NS_URI + "#user"; private const string NS_URI_REQUEST = NS_URI + "#request"; public enum MucEnterError { @@ -69,8 +69,7 @@ public class JoinResult { public class Module : XmppStreamModule { public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0045_muc_module"); - public signal void received_occupant_affiliation(XmppStream stream, Jid jid, Affiliation? affiliation); - public signal void received_occupant_jid(XmppStream stream, Jid jid, Jid? real_jid); + public signal void received_affiliations(XmppStream stream, Jid muc_jid, HashMap<Jid, Affiliation> affiliations, string? mav_since, string? mav_until); public signal void received_occupant_role(XmppStream stream, Jid jid, Role? role); public signal void subject_set(XmppStream stream, string? subject, Jid jid); public signal void invite_received(XmppStream stream, Jid room_jid, Jid from_jid, string? password, string? reason); @@ -86,7 +85,7 @@ public class Module : XmppStreamModule { received_pipeline_listener = new ReceivedPipelineListener(this); } - public async JoinResult? enter(XmppStream stream, Jid bare_jid, string nick, string? password, DateTime? history_since, bool receive_history, StanzaNode? additional_node) { + public async JoinResult? enter(XmppStream stream, Jid bare_jid, string nick, string? password, DateTime? history_since, bool receive_history, StanzaNode? additional_node, string? mav_since) { try { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid.with_resource(nick); @@ -105,6 +104,8 @@ public class Module : XmppStreamModule { history_node.set_attribute("maxchars", "0"); } } + x_node.put_node(Xep.MucAffiliationsVersioning.get_join_presence_node(mav_since)); + presence.stanza.put_node(x_node); if (additional_node != null) { @@ -360,11 +361,6 @@ public class Module : XmppStreamModule { query_room_info.begin(stream, bare_jid); - // TODO only query that if we actually have the rights to - query_affiliation.begin(stream, bare_jid, "member"); - query_affiliation.begin(stream, bare_jid, "admin"); - query_affiliation.begin(stream, bare_jid, "owner"); - flag.finish_muc_enter(bare_jid); var join_result = new JoinResult() { nick=presence.from.resourcepart, newly_created=status_codes.contains(StatusCode.NEW_ROOM_CREATED) }; flag.enter_futures[bare_jid].set_value(join_result); @@ -377,17 +373,22 @@ public class Module : XmppStreamModule { if (affiliation_str != null) { affiliation = parse_affiliation(affiliation_str); flag.set_affiliation(presence.from.bare_jid, presence.from, affiliation); - received_occupant_affiliation(stream, presence.from, affiliation); } string? jid_ = x_node.get_deep_attribute("item", "jid"); if (jid_ != null) { try { - Jid jid = new Jid(jid_); - flag.set_real_jid(presence.from, jid); + Jid real_jid = new Jid(jid_); + flag.set_real_jid(presence.from, real_jid); if (affiliation != null) { - stream.get_flag(Flag.IDENTITY).set_offline_member(presence.from, jid, affiliation); + stream.get_flag(Flag.IDENTITY).set_offline_member(presence.from, real_jid, affiliation); + var affiliations = new HashMap<Jid, Affiliation>(); + affiliations[real_jid.bare_jid] = affiliation; + + string? mav_since = MucAffiliationsVersioning.get_mav_since(x_node); + string? mav_until = MucAffiliationsVersioning.get_mav_until(x_node); + + received_affiliations(stream, presence.from.bare_jid, affiliations, mav_since, mav_until); } - received_occupant_jid(stream, presence.from, jid); } catch (InvalidJidError e) { warning("Received invalid occupant jid: %s", e.message); } @@ -468,6 +469,12 @@ public class Module : XmppStreamModule { room_info_updated(stream, jid); } + public async void fetch_affiliations(XmppStream stream, Jid muc_jid) { + query_affiliation.begin(stream, muc_jid, "member"); + query_affiliation.begin(stream, muc_jid, "admin"); + yield query_affiliation(stream, muc_jid, "owner"); + } + private async Gee.List<Jid>? query_affiliation(XmppStream stream, Jid jid, string affiliation) { Iq.Stanza iq = new Iq.Stanza.get( new StanzaNode.build("query", NS_URI_ADMIN) @@ -492,7 +499,6 @@ public class Module : XmppStreamModule { Jid jid_ = new Jid(jid__); stream.get_flag(Flag.IDENTITY).set_offline_member(iq_result.from, jid_, parse_affiliation(affiliation_)); ret_jids.add(jid_); - received_occupant_jid(stream, iq_result.from, jid_); } catch (InvalidJidError e) { warning("Received invalid occupant jid: %s", e.message); } @@ -509,7 +515,7 @@ public class Module : XmppStreamModule { return ret; } - private static Affiliation parse_affiliation(string affiliation_str) { + public static Affiliation parse_affiliation(string affiliation_str) { Affiliation affiliation; switch (affiliation_str) { case "admin": @@ -559,6 +565,7 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> { if (message.type_ == MessageStanza.TYPE_NORMAL) { StanzaNode? x_node = message.stanza.get_subnode("x", NS_URI_USER); if (x_node != null) { + // Parse Invite StanzaNode? invite_node = x_node.get_subnode("invite", NS_URI_USER); string? password = null; StanzaNode? password_node = x_node.get_subnode("password", NS_URI_USER); @@ -580,6 +587,27 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> { return true; } } + + // Parse affiliation update + Gee.List<StanzaNode> item_nodes = x_node.get_subnodes("item", Muc.NS_URI_USER); + if (item_nodes.size > 0) { + var affiliations = new HashMap<Jid, Affiliation>(); + foreach (StanzaNode item_node in item_nodes) { + string jid_str = item_node.get_attribute("jid", Muc.NS_URI_USER); + Jid real_jid = new Jid(jid_str); + string affiliation_str = item_node.get_attribute("affiliation", Muc.NS_URI_USER); + Muc.Affiliation affiliation = Muc.Module.parse_affiliation(affiliation_str); + + affiliations[real_jid] = affiliation; + + stream.get_flag(Flag.IDENTITY).set_offline_member(message.from, real_jid, affiliation); + } + + string? mav_since = MucAffiliationsVersioning.get_mav_since(x_node); + string? mav_until = MucAffiliationsVersioning.get_mav_until(x_node); + + outer.received_affiliations(stream, message.from, affiliations, mav_since, mav_until); + } } StanzaNode? x_field_node = message.stanza.get_subnode("x", DataForms.NS_URI); diff --git a/xmpp-vala/src/module/xep/0272_muji.vala b/xmpp-vala/src/module/xep/0272_muji.vala index 243ed93e..8226da10 100644 --- a/xmpp-vala/src/module/xep/0272_muji.vala +++ b/xmpp-vala/src/module/xep/0272_muji.vala @@ -15,7 +15,7 @@ namespace Xmpp.Xep.Muji { group_call.our_nick = "%08x".printf(Random.next_int()); debug(@"[%s] MUJI joining as %s", stream.get_flag(Bind.Flag.IDENTITY).my_jid.to_string(), group_call.our_nick); - Xep.Muc.JoinResult? result = yield stream.get_module(Muc.Module.IDENTITY).enter(stream, muc_jid, group_call.our_nick, null, null, false, initial_muji_node); + Xep.Muc.JoinResult? result = yield stream.get_module(Muc.Module.IDENTITY).enter(stream, muc_jid, group_call.our_nick, null, null, false, initial_muji_node, null); if (result == null || result.nick == null) return null; debug(@"[%s] MUJI joining as %s done", stream.get_flag(Bind.Flag.IDENTITY).my_jid.to_string(), group_call.our_nick); diff --git a/xmpp-vala/src/module/xep/0463_muc_affiliations_versioning.vala b/xmpp-vala/src/module/xep/0463_muc_affiliations_versioning.vala new file mode 100644 index 00000000..ba7edcb9 --- /dev/null +++ b/xmpp-vala/src/module/xep/0463_muc_affiliations_versioning.vala @@ -0,0 +1,24 @@ +using Gee; + +namespace Xmpp.Xep.MucAffiliationsVersioning { + + public const string NS_URI = "urn:xmpp:muc:affiliations:1"; + + public static StanzaNode get_join_presence_node(string? since) { + var stanza_node = new StanzaNode.build("mav", NS_URI).add_self_xmlns(); + if (since != null) { + stanza_node.put_attribute("since", since); + } + return stanza_node; + } + + public static string? get_mav_since(StanzaNode outer_node) { + StanzaNode? mav_node = outer_node.get_subnode("mav", MucAffiliationsVersioning.NS_URI); + return mav_node != null ? mav_node.get_attribute("since", MucAffiliationsVersioning.NS_URI) : null; + } + + public static string? get_mav_until(StanzaNode outer_node) { + StanzaNode? mav_node = outer_node.get_subnode("mav", MucAffiliationsVersioning.NS_URI); + return mav_node != null ? mav_node.get_attribute("until", MucAffiliationsVersioning.NS_URI) : null; + } +}
\ No newline at end of file |
