From fa6d49e3bf664e7862548223df9b0882c11eefb1 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Tue, 11 Jan 2022 20:37:30 +0100 Subject: [PATCH] Update PEP native bookmarks to :1 version, fixes --- libdino/src/service/entity_info.vala | 20 +++-- libdino/src/service/muc_manager.vala | 65 ++++++++++++---- xmpp-vala/src/module/xep/0060_pubsub.vala | 78 +++++++++++++++---- xmpp-vala/src/module/xep/0402_bookmarks2.vala | 18 ++++- 4 files changed, 139 insertions(+), 42 deletions(-) diff --git a/libdino/src/service/entity_info.vala b/libdino/src/service/entity_info.vala index b80d4b59..d7d0bc9e 100644 --- a/libdino/src/service/entity_info.vala +++ b/libdino/src/service/entity_info.vala @@ -32,12 +32,13 @@ public class EntityInfo : StreamInteractionModule, Object { this.entity_capabilities_storage = new EntityCapabilitiesStorage(db); stream_interactor.account_added.connect(on_account_added); - stream_interactor.stream_negotiated.connect((account, stream) => { - var cache = new CapsCacheImpl(account, this); - stream.get_module(ServiceDiscovery.Module.IDENTITY).cache = cache; - - string? hash = EntityCapabilities.get_server_caps_hash(stream); - entity_caps_hashes[account.bare_jid.domain_jid] = hash; + stream_interactor.connection_manager.stream_opened.connect((account, stream) => { + stream.received_features_node.connect(() => { + string? hash = EntityCapabilities.get_server_caps_hash(stream); + if (hash != null) { + entity_caps_hashes[account.bare_jid.domain_jid] = hash; + } + }); }); stream_interactor.module_manager.initialize_account_modules.connect(initialize_modules); @@ -169,14 +170,14 @@ public class EntityInfo : StreamInteractionModule, Object { return identities; } + identities = new HashSet(Identity.hash_func, Identity.equals_func); var qry = db.entity_identity.select().with(db.entity_identity.entity, "=", entity); foreach (Row row in qry) { - if (identities == null) identities = new HashSet(Identity.hash_func, Identity.equals_func); var identity = new Identity(row[db.entity_identity.category], row[db.entity_identity.type], row[db.entity_identity.entity_name]); identities.add(identity); } - if (entity_identity.size == 0) { + if (identities.size == 0) { return null; } entity_identity[entity] = identities; @@ -202,6 +203,9 @@ public class EntityInfo : StreamInteractionModule, Object { } private void on_account_added(Account account) { + var cache = new CapsCacheImpl(account, this); + stream_interactor.module_manager.get_module(account, ServiceDiscovery.Module.IDENTITY).cache = cache; + stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_available.connect((stream, presence) => on_received_available_presence(account, presence)); } diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index a508f819..5d0b05b9 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -386,21 +386,6 @@ public class MucManager : StreamInteractionModule, Object { private_room_occupant_updated(account, room, occupant); } }); - - bookmarks_provider[account] = stream_interactor.module_manager.get_module(account, Xep.Bookmarks.Module.IDENTITY); - - bookmarks_provider[account].received_conferences.connect( (stream, conferences) => { - sync_autojoin_active(account, conferences); - bookmarks_updated(account, conferences); - }); - bookmarks_provider[account].conference_added.connect( (stream, conference) => { - // TODO join (for Bookmarks2) - conference_added(account, conference); - }); - bookmarks_provider[account].conference_removed.connect( (stream, jid) => { - // TODO part (for Bookmarks2) - conference_removed(account, jid); - }); } private async void search_default_muc_server(Account account) { @@ -434,7 +419,7 @@ public class MucManager : StreamInteractionModule, Object { } private async void on_stream_negotiated(Account account, XmppStream stream) { - if (bookmarks_provider[account] == null) return; + yield initialize_bookmarks_provider(account); Set? conferences = yield bookmarks_provider[account].get_conferences(stream); @@ -449,6 +434,31 @@ public class MucManager : StreamInteractionModule, Object { } } + private async void initialize_bookmarks_provider(Account account) { + if (bookmarks_provider.has_key(account)) return; + + // Use PEP native bookmarks (urn:xmpp:bookmarks:1) if conversion is available, legacy bookmarks (storage:bookmarks) otherwise. + bool has_feature = yield stream_interactor.get_module(EntityInfo.IDENTITY).has_feature(account, account.bare_jid, Xep.Bookmarks2.NS_URI_COMPAT); + if (has_feature) { + debug("[%s] Using PEP native bookmarks (urn:xmpp:bookmarks:1)", account.bare_jid.to_string()); + bookmarks_provider[account] = stream_interactor.module_manager.get_module(account, Xep.Bookmarks2.Module.IDENTITY); + } else { + debug("[%s] Using legacy bookmarks (storage:bookmarks)", account.bare_jid.to_string()); + bookmarks_provider[account] = stream_interactor.module_manager.get_module(account, Xep.Bookmarks.Module.IDENTITY); + } + + bookmarks_provider[account].received_conferences.connect( (stream, conferences) => { + sync_autojoin_active(account, conferences); + bookmarks_updated(account, conferences); + }); + bookmarks_provider[account].conference_added.connect( (stream, conference) => { + on_conference_added(account, conference); + }); + bookmarks_provider[account].conference_removed.connect( (stream, jid) => { + on_conference_removed(account, jid); + }); + } + private void on_invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason) { if (!invites.has_key(account)) { invites[account] = new LinkedList(Jid.equals_func); @@ -544,6 +554,29 @@ public class MucManager : StreamInteractionModule, Object { }); } + private void on_conference_added(Account account, Xmpp.Conference conference) { + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(conference.jid, account, Conversation.Type.GROUPCHAT); + if (conversation != null) { + if (!conversation.active && conference.autojoin) { + join.begin(account, conference.jid, conference.nick, conference.password); + } else if (conversation.active && !conference.autojoin) { + part(account, conference.jid); + } + } + if (conference.autojoin) { + join.begin(account, conference.jid, conference.nick, conference.password); + } + conference_added(account, conference); + } + + private void on_conference_removed(Account account, Jid jid) { + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account, Conversation.Type.GROUPCHAT); + if (conversation != null && conversation.active) { + part(account, jid); + } + conference_removed(account, jid); + } + private void self_ping(Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; diff --git a/xmpp-vala/src/module/xep/0060_pubsub.vala b/xmpp-vala/src/module/xep/0060_pubsub.vala index 02e35394..95eee020 100644 --- a/xmpp-vala/src/module/xep/0060_pubsub.vala +++ b/xmpp-vala/src/module/xep/0060_pubsub.vala @@ -4,6 +4,7 @@ namespace Xmpp.Xep.Pubsub { public const string NS_URI = "http://jabber.org/protocol/pubsub"; private const string NS_URI_EVENT = NS_URI + "#event"; private const string NS_URI_OWNER = NS_URI + "#owner"; + public const string NS_URI_ERROR = NS_URI + "#errors"; public const string ACCESS_MODEL_AUTHORIZE = "authorize"; public const string ACCESS_MODEL_OPEN = "open"; @@ -66,7 +67,11 @@ namespace Xmpp.Xep.Pubsub { }); } - public async bool publish(XmppStream stream, Jid? jid, string node_id, string? item_id, StanzaNode content, string? access_model=null) { + public async bool publish( + XmppStream stream, Jid? jid, string node_id, string? item_id, StanzaNode content, + PublishOptions? publish_options = null, + bool try_reconfiguring = true + ) { StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns(); StanzaNode publish_node = new StanzaNode.build("publish", NS_URI).put_attribute("node", node_id); pubsub_node.put_node(publish_node); @@ -75,7 +80,8 @@ namespace Xmpp.Xep.Pubsub { items_node.put_node(content); publish_node.put_node(items_node); - if (access_model != null) { + // Send along our requirements for the node + if (publish_options != null) { StanzaNode publish_options_node = new StanzaNode.build("publish-options", NS_URI); pubsub_node.put_node(publish_options_node); @@ -83,23 +89,30 @@ namespace Xmpp.Xep.Pubsub { DataForms.DataForm.HiddenField form_type_field = new DataForms.DataForm.HiddenField() { var="FORM_TYPE" }; form_type_field.set_value_string(NS_URI + "#publish-options"); data_form.add_field(form_type_field); - if (access_model != null) { - DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="pubsub#access_model" }; - field.set_value_string(access_model); + + foreach (string field_name in publish_options.settings.keys) { + DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var=field_name }; + field.set_value_string(publish_options.settings[field_name]); data_form.add_field(field); } publish_options_node.put_node(data_form.get_submit_node()); } Iq.Stanza iq = new Iq.Stanza.set(pubsub_node); - bool ok = true; - stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, result_iq) => { - ok = !result_iq.is_error(); - Idle.add(publish.callback); - }); - yield; - return ok; + // If the node was configured differently before, reconfigure it to meet our requirements and try again + Iq.Stanza iq_result = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + if (iq_result.is_error()) { + if (publish_options == null || !try_reconfiguring) return false; + bool precondition_not_met = iq_result.get_error().error_node.get_subnode("precondition-not-met", NS_URI_ERROR) != null; + if (precondition_not_met) { + bool success = yield change_node_config(stream, jid, node_id, publish_options); + if (!success) return false; + return yield publish(stream, jid, node_id, item_id, content, publish_options, false); + } + } + + return true; } public async bool retract_item(XmppStream stream, Jid? jid, string node_id, string item_id) { @@ -139,7 +152,7 @@ namespace Xmpp.Xep.Pubsub { return DataForms.DataForm.create_from_node(data_form_node); } - public async void submit_node_config(XmppStream stream, DataForms.DataForm data_form, string node_id) { + public async bool submit_node_config(XmppStream stream, DataForms.DataForm data_form, string node_id) { StanzaNode submit_node = data_form.get_submit_node(); StanzaNode pubsub_node = new StanzaNode.build("pubsub", Pubsub.NS_URI_OWNER).add_self_xmlns(); @@ -149,7 +162,9 @@ namespace Xmpp.Xep.Pubsub { Iq.Stanza iq = new Iq.Stanza.set(pubsub_node); - yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + Iq.Stanza iq_result = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + + return !iq_result.is_error(); } public override void attach(XmppStream stream) { @@ -192,7 +207,42 @@ namespace Xmpp.Xep.Pubsub { retract_listeners[node].on_result(stream, message.from, id); } } + } + private async bool change_node_config(XmppStream stream, Jid jid, string node, PublishOptions publish_options) { + DataForms.DataForm? data_form = yield stream.get_module(Pubsub.Module.IDENTITY).request_node_config(stream, null, node); + if (data_form == null) return false; + + foreach (DataForms.DataForm.Field field in data_form.fields) { + if (publish_options.settings.has_key(field.var) && publish_options.settings[field.var] != field.get_value_string()) { + field.set_value_string(publish_options.settings[field.var]); + } + } + return yield stream.get_module(Pubsub.Module.IDENTITY).submit_node_config(stream, data_form, node); + } + } + + public class PublishOptions { + public HashMap settings = new HashMap(); + + public PublishOptions set_persist_items(bool persist) { + settings["pubsub#persist_items"] = persist.to_string(); + return this; + } + + public PublishOptions set_max_items(string max) { + settings["pubsub#max_items"] = max; + return this; + } + + public PublishOptions set_send_last_published_item(string send) { + settings["pubsub#send_last_published_item"] = send.to_string(); + return this; + } + + public PublishOptions set_access_model(string model) { + settings["pubsub#access_model"] = model; + return this; } } diff --git a/xmpp-vala/src/module/xep/0402_bookmarks2.vala b/xmpp-vala/src/module/xep/0402_bookmarks2.vala index 9572cc63..cafe1435 100644 --- a/xmpp-vala/src/module/xep/0402_bookmarks2.vala +++ b/xmpp-vala/src/module/xep/0402_bookmarks2.vala @@ -2,7 +2,8 @@ using Gee; namespace Xmpp.Xep.Bookmarks2 { -private const string NS_URI = "urn:xmpp:bookmarks:0"; +public const string NS_URI = "urn:xmpp:bookmarks:1"; +public const string NS_URI_COMPAT = NS_URI + "#compat"; public class Module : BookmarksProvider, XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0402_bookmarks2"); @@ -35,14 +36,23 @@ public class Module : BookmarksProvider, XmppStreamModule { public async void add_conference(XmppStream stream, Conference conference) { StanzaNode conference_node = new StanzaNode.build("conference", NS_URI).add_self_xmlns() - .put_attribute("autojoin", conference.autojoin ? "true" : "false"); + .put_attribute("autojoin", conference.autojoin.to_string()); if (conference.name != null) { conference_node.put_attribute("name", conference.name); } if (conference.nick != null) { conference_node.put_node((new StanzaNode.build("nick", NS_URI)).put_node(new StanzaNode.text(conference.nick))); } - yield stream.get_module(Pubsub.Module.IDENTITY).publish(stream, stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid, NS_URI, conference.jid.to_string(), conference_node, Xmpp.Xep.Pubsub.ACCESS_MODEL_WHITELIST); + if (conference.password != null) { + conference_node.put_node((new StanzaNode.build("password", NS_URI)).put_node(new StanzaNode.text(conference.password))); + } + var publish_options = new Pubsub.PublishOptions() + .set_persist_items(true) + .set_max_items("max") + .set_send_last_published_item("never") + .set_access_model("whitelist"); + + yield stream.get_module(Pubsub.Module.IDENTITY).publish(stream, stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid, NS_URI, conference.jid.to_string(), conference_node, publish_options); } public async void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) { @@ -92,7 +102,7 @@ public class Module : BookmarksProvider, XmppStreamModule { if (conference_node.name != "conference" || conference_node.ns_uri != NS_URI) return null; conference.name = conference_node.get_attribute("name", NS_URI); - conference.autojoin = conference_node.get_attribute("autojoin", NS_URI) == "true"; + conference.autojoin = conference_node.get_attribute_bool("autojoin", false, NS_URI); conference.nick = conference_node.get_deep_string_content("nick"); return conference; }