aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfiaxh <git@lightrise.org>2026-05-22 17:09:04 +0200
committerfiaxh <git@lightrise.org>2026-05-22 17:11:37 +0200
commitddbb00ee56ca8ddc5db3ed9b6f4003bb6020ed6b (patch)
tree9e59681e4c89f747ce4d83ab3cb5b0aedc4f523b
parent9abf8f6869bc35c76bdfa86416012d4e7f0dda78 (diff)
Add support for MUC affiliations versioning (XEP-0463), add affiliation storage
-rw-r--r--dino.doap8
-rw-r--r--dino.doap.in8
-rw-r--r--libdino/meson.build1
-rw-r--r--libdino/src/application.vala2
-rw-r--r--libdino/src/service/contact_model.vala2
-rw-r--r--libdino/src/service/database.vala34
-rw-r--r--libdino/src/service/muc_affiliations.vala82
-rw-r--r--libdino/src/service/muc_manager.vala68
-rw-r--r--main/src/ui/widgets/avatar_picture.vala10
-rw-r--r--xmpp-vala/meson.build1
-rw-r--r--xmpp-vala/src/module/xep/0045_muc/module.vala62
-rw-r--r--xmpp-vala/src/module/xep/0272_muji.vala2
-rw-r--r--xmpp-vala/src/module/xep/0463_muc_affiliations_versioning.vala24
13 files changed, 263 insertions, 41 deletions
diff --git a/dino.doap b/dino.doap
index f069775a..dee0dd2b 100644
--- a/dino.doap
+++ b/dino.doap
@@ -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