aboutsummaryrefslogtreecommitdiff
path: root/xmpp-vala
diff options
context:
space:
mode:
authorMarvin W <git@larma.de>2026-07-18 18:10:27 +0200
committerMarvin W <git@larma.de>2026-08-02 12:44:10 +0200
commit64f09ce50063e6ccc135a535fe98f58751d7ac7d (patch)
treef8e407f0ab94a44ef1208202df3778ca10f89cb5 /xmpp-vala
parent45723ac201c1905571f9f78c8c970828db97139a (diff)
Add support for XEP-0490 Message Displayed Synchronization
Diffstat (limited to 'xmpp-vala')
-rw-r--r--xmpp-vala/meson.build1
-rw-r--r--xmpp-vala/src/module/xep/0060_pubsub.vala11
-rw-r--r--xmpp-vala/src/module/xep/0490_message_displayed_synchronization.vala92
3 files changed, 99 insertions, 5 deletions
diff --git a/xmpp-vala/meson.build b/xmpp-vala/meson.build
index 85bdaadc..3f00ea7b 100644
--- a/xmpp-vala/meson.build
+++ b/xmpp-vala/meson.build
@@ -136,6 +136,7 @@ sources = files(
'src/module/xep/0461_replies.vala',
'src/module/xep/0463_muc_affiliations_versioning.vala',
'src/module/xep/0482_call_invites.vala',
+ 'src/module/xep/0490_message_displayed_synchronization.vala',
'src/file_content_type.vala',
'src/util.vala',
'src/weak_map.vala',
diff --git a/xmpp-vala/src/module/xep/0060_pubsub.vala b/xmpp-vala/src/module/xep/0060_pubsub.vala
index c6395eb9..c9af4263 100644
--- a/xmpp-vala/src/module/xep/0060_pubsub.vala
+++ b/xmpp-vala/src/module/xep/0060_pubsub.vala
@@ -41,9 +41,9 @@ namespace Xmpp.Xep.Pubsub {
retract_listeners.unset(node);
}
- public async Gee.List<StanzaNode>? request_all(XmppStream stream, Jid jid, string node) { // TODO multiple nodes gehen auch
+ public async Gee.List<StanzaNode>? request_all(XmppStream stream, Jid? jid, string node) { // TODO multiple nodes gehen auch
Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node)));
- request_iq.to = jid;
+ if (jid != null) request_iq.to = jid;
Iq.Stanza iq_res = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, request_iq);
@@ -56,9 +56,9 @@ namespace Xmpp.Xep.Pubsub {
}
public delegate void OnResult(XmppStream stream, Jid jid, string? id, StanzaNode? node);
- public void request(XmppStream stream, Jid jid, string node, owned OnResult listener) { // TODO multiple nodes gehen auch
+ public void request(XmppStream stream, Jid? jid, string node, owned OnResult listener) { // TODO multiple nodes gehen auch
Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node)));
- request_iq.to = jid;
+ if (jid != null) request_iq.to = jid;
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, request_iq, (stream, iq) => {
StanzaNode event_node = iq.stanza.get_subnode("pubsub", NS_URI);
StanzaNode items_node = event_node != null ? event_node.get_subnode("items", NS_URI) : null;
@@ -100,6 +100,7 @@ namespace Xmpp.Xep.Pubsub {
}
Iq.Stanza iq = new Iq.Stanza.set(pubsub_node);
+ if (jid != null) iq.to = jid;
// 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);
@@ -222,7 +223,7 @@ namespace Xmpp.Xep.Pubsub {
}
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);
+ DataForms.DataForm? data_form = yield stream.get_module(Pubsub.Module.IDENTITY).request_node_config(stream, jid, node);
if (data_form == null) return false;
foreach (DataForms.DataForm.Field field in data_form.fields) {
diff --git a/xmpp-vala/src/module/xep/0490_message_displayed_synchronization.vala b/xmpp-vala/src/module/xep/0490_message_displayed_synchronization.vala
new file mode 100644
index 00000000..38437735
--- /dev/null
+++ b/xmpp-vala/src/module/xep/0490_message_displayed_synchronization.vala
@@ -0,0 +1,92 @@
+using Gee;
+namespace Xmpp.Xep.MessageDisplayedSynchronization {
+ public const string NS_URI = "urn:xmpp:mds:displayed:0";
+ public const string NS_URI_SERVER_ASSIST = "urn:xmpp:mds:server-assist:0";
+
+ public class Module : XmppStreamModule {
+ public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0490_message_displayed_synchronization");
+
+ public signal void on_message_displayed(Jid conversation_jid, Jid stanza_id_by_jid, string stanza_id);
+
+ private void handle_displayed_node_item(XmppStream stream, string? id, StanzaNode? item) {
+ if (id == null || item == null) return;
+ Jid conversation_jid = Jid.from_string(id);
+ StanzaNode? stanza_id_node = item.get_subnode("stanza-id", UniqueStableStanzaIDs.NS_URI);
+ if (stanza_id_node == null || stanza_id_node.get_attribute("by") == null || stanza_id_node.get_attribute("id") == null) return;
+ Jid stanza_id_by_jid = Jid.from_string(stanza_id_node.get_attribute("by"));
+ string stanza_id = stanza_id_node.get_attribute("id");
+ if (stream.has_flag(Flag.IDENTITY)) {
+ stream.get_flag(Flag.IDENTITY).learn_mds(conversation_jid, stanza_id_by_jid, stanza_id);
+ }
+ on_message_displayed(conversation_jid, stanza_id_by_jid, stanza_id);
+ }
+
+ private void on_pubsub_item(XmppStream stream, Jid jid, string id, StanzaNode? item) {
+ if (!jid.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid)) {
+ warning("Received alleged mds item from %s, ignoring", jid.to_string());
+ return;
+ }
+ handle_displayed_node_item(stream, id, item);
+ }
+
+ private void on_initial_presence_sent(XmppStream stream) {
+ var pubsub = stream.get_module(Pubsub.Module.IDENTITY);
+ pubsub.request_all.begin(stream, null, NS_URI, (_, res) => {
+ Gee.List<StanzaNode>? items = pubsub.request_all.end(res);
+ foreach (StanzaNode node_item in items) {
+ handle_displayed_node_item(stream, node_item.get_attribute("id"), node_item.get_subnode("displayed", NS_URI));
+ }
+ });
+ }
+
+ public async bool update_message_displayed(XmppStream stream, Jid conversation_jid, Jid stanza_id_by_jid, string stanza_id) {
+ var pubsub = stream.get_module(Pubsub.Module.IDENTITY);
+ var node = new StanzaNode.build("displayed", NS_URI).add_self_xmlns()
+ .put_node(new StanzaNode.build("stanza-id", UniqueStableStanzaIDs.NS_URI).add_self_xmlns()
+ .put_attribute("by", stanza_id_by_jid.to_string())
+ .put_attribute("id", stanza_id));
+ var options = new Pubsub.PublishOptions()
+ .set_persist_items(true)
+ .set_max_items("max")
+ .set_send_last_published_item("never")
+ .set_access_model("whitelist");
+ return yield pubsub.publish(stream, stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid, NS_URI, conversation_jid.to_string(), node, options);
+ }
+
+ public override void attach(XmppStream stream) {
+ stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NS_URI, on_pubsub_item, null, null);
+ stream.get_module(Presence.Module.IDENTITY).initial_presence_sent.connect(on_initial_presence_sent);
+ if (!stream.has_flag(Flag.IDENTITY)) {
+ stream.add_flag(new Flag());
+ }
+ }
+
+ public override void detach(XmppStream stream) {
+ stream.get_module(Presence.Module.IDENTITY).initial_presence_sent.disconnect(on_initial_presence_sent);
+ stream.get_module(Pubsub.Module.IDENTITY).remove_filtered_notification(stream, NS_URI);
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return IDENTITY.id; }
+
+ }
+
+ public class Flag : XmppStreamFlag {
+ public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "mds");
+
+ private Map<Jid, Jid> stanza_id_bys = new HashMap<Jid, Jid>(Jid.hash_func, Jid.equals_func);
+ private Map<Jid, string> stanza_ids = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
+
+ public void learn_mds(Jid jid, Jid stanza_by, string stanza_id) {
+ stanza_id_bys[jid] = stanza_by;
+ stanza_ids[jid] = stanza_id;
+ }
+
+ public bool is_displayed(Jid conversation_jid, Jid stanza_by, string stanza_id) {
+ return stanza_id_bys[conversation_jid].equals(stanza_by) && stanza_ids[conversation_jid] == stanza_id;
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return IDENTITY.id; }
+ }
+} \ No newline at end of file