aboutsummaryrefslogtreecommitdiff
path: root/libdino
diff options
context:
space:
mode:
authoreerielili <lionel@les-miquelots.net>2024-08-25 13:32:38 +0000
committerGitHub <noreply@github.com>2024-08-25 13:32:38 +0000
commit45755727db79a2935376d24e7bde7eadb0f2f7ca (patch)
tree73715da99c9d980079df6f2d561822364655e04d /libdino
parent62cdea3a5e701c04f3a7fd9d6b5f48e28fef1f72 (diff)
parent51252f74c94c17d56aa75534652bdc5d43a504cb (diff)
downloaddino-45755727db79a2935376d24e7bde7eadb0f2f7ca.tar.gz
dino-45755727db79a2935376d24e7bde7eadb0f2f7ca.zip
Merge branch 'master' into add-yourselfadd-yourself
Diffstat (limited to 'libdino')
-rw-r--r--libdino/CMakeLists.txt1
-rw-r--r--libdino/meson.build1
-rw-r--r--libdino/src/entity/conversation.vala2
-rw-r--r--libdino/src/entity/message.vala89
-rw-r--r--libdino/src/entity/settings.vala18
-rw-r--r--libdino/src/plugin/interfaces.vala7
-rw-r--r--libdino/src/plugin/registry.vala13
-rw-r--r--libdino/src/service/avatar_manager.vala43
-rw-r--r--libdino/src/service/conversation_manager.vala8
-rw-r--r--libdino/src/service/database.vala32
-rw-r--r--libdino/src/service/entity_info.vala35
-rw-r--r--libdino/src/service/fallback_body.vala15
-rw-r--r--libdino/src/service/message_correction.vala28
-rw-r--r--libdino/src/service/message_processor.vala70
-rw-r--r--libdino/src/service/muc_manager.vala21
-rw-r--r--libdino/src/service/replies.vala15
-rw-r--r--libdino/src/util/send_message.vala56
17 files changed, 334 insertions, 120 deletions
diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt
index e4d786c9..34cf9575 100644
--- a/libdino/CMakeLists.txt
+++ b/libdino/CMakeLists.txt
@@ -68,6 +68,7 @@ SOURCES
src/service/util.vala
src/util/display_name.vala
+ src/util/send_message.vala
src/util/util.vala
src/util/weak_map.vala
src/util/weak_timeout.vala
diff --git a/libdino/meson.build b/libdino/meson.build
index 17804d23..559a81b5 100644
--- a/libdino/meson.build
+++ b/libdino/meson.build
@@ -74,6 +74,7 @@ sources = files(
'src/service/stream_interactor.vala',
'src/service/util.vala',
'src/util/display_name.vala',
+ 'src/util/send_message.vala',
'src/util/util.vala',
'src/util/weak_map.vala',
'src/util/weak_timeout.vala',
diff --git a/libdino/src/entity/conversation.vala b/libdino/src/entity/conversation.vala
index 353daeae..4115ae83 100644
--- a/libdino/src/entity/conversation.vala
+++ b/libdino/src/entity/conversation.vala
@@ -33,7 +33,7 @@ public class Conversation : Object {
}
}
}
- public Encryption encryption { get; set; default = Encryption.NONE; }
+ public Encryption encryption { get; set; default = Encryption.UNKNOWN; }
public Message? read_up_to { get; set; }
public int read_up_to_item { get; set; default=-1; }
diff --git a/libdino/src/entity/message.vala b/libdino/src/entity/message.vala
index 912639b1..e5aad25f 100644
--- a/libdino/src/entity/message.vala
+++ b/libdino/src/entity/message.vala
@@ -67,9 +67,10 @@ public class Message : Object {
}
}
public string? edit_to = null;
- public int quoted_item_id = 0;
+ public int quoted_item_id { get; private set; default=0; }
private Gee.List<Xep.FallbackIndication.Fallback> fallbacks = null;
+ private Gee.List<Xep.MessageMarkup.Span> markups = null;
private Database? db;
@@ -142,18 +143,71 @@ public class Message : Object {
notify.connect(on_update);
}
+ public void set_quoted_item(int quoted_content_item_id) {
+ if (id == -1) {
+ warning("Message needs to be persisted before setting quoted item");
+ return;
+ }
+
+ this.quoted_item_id = quoted_content_item_id;
+
+ db.reply.upsert()
+ .value(db.reply.message_id, id, true)
+ .value(db.reply.quoted_content_item_id, quoted_content_item_id)
+ .value_null(db.reply.quoted_message_stanza_id)
+ .value_null(db.reply.quoted_message_from)
+ .perform();
+ }
+
public Gee.List<Xep.FallbackIndication.Fallback> get_fallbacks() {
if (fallbacks != null) return fallbacks;
+ fetch_body_meta();
+
+ return fallbacks;
+ }
+
+ public Gee.List<Xep.MessageMarkup.Span> get_markups() {
+ if (markups != null) return markups;
+ fetch_body_meta();
+
+ return markups;
+ }
+ public void persist_markups(Gee.List<Xep.MessageMarkup.Span> markups, int message_id) {
+ this.markups = markups;
+
+ foreach (var span in markups) {
+ foreach (var ty in span.types) {
+ db.body_meta.insert()
+ .value(db.body_meta.info_type, Xep.MessageMarkup.NS_URI)
+ .value(db.body_meta.message_id, message_id)
+ .value(db.body_meta.info, Xep.MessageMarkup.span_type_to_str(ty))
+ .value(db.body_meta.from_char, span.start_char)
+ .value(db.body_meta.to_char, span.end_char)
+ .perform();
+ }
+ }
+ }
+
+ private void fetch_body_meta() {
var fallbacks_by_ns = new HashMap<string, ArrayList<Xep.FallbackIndication.FallbackLocation>>();
- foreach (Qlite.Row row in db.body_meta.select().with(db.body_meta.message_id, "=", id)) {
- if (row[db.body_meta.info_type] != Xep.FallbackIndication.NS_URI) continue;
+ var markups = new ArrayList<Xep.MessageMarkup.Span>();
- string ns_uri = row[db.body_meta.info];
- if (!fallbacks_by_ns.has_key(ns_uri)) {
- fallbacks_by_ns[ns_uri] = new ArrayList<Xep.FallbackIndication.FallbackLocation>();
+ foreach (Qlite.Row row in db.body_meta.select().with(db.body_meta.message_id, "=", id)) {
+ switch (row[db.body_meta.info_type]) {
+ case Xep.FallbackIndication.NS_URI:
+ string ns_uri = row[db.body_meta.info];
+ if (!fallbacks_by_ns.has_key(ns_uri)) {
+ fallbacks_by_ns[ns_uri] = new ArrayList<Xep.FallbackIndication.FallbackLocation>();
+ }
+ fallbacks_by_ns[ns_uri].add(new Xep.FallbackIndication.FallbackLocation(row[db.body_meta.from_char], row[db.body_meta.to_char]));
+ break;
+ case Xep.MessageMarkup.NS_URI:
+ var types = new ArrayList<Xep.MessageMarkup.SpanType>();
+ types.add(Xep.MessageMarkup.str_to_span_type(row[db.body_meta.info]));
+ markups.add(new Xep.MessageMarkup.Span() { types=types, start_char=row[db.body_meta.from_char], end_char=row[db.body_meta.to_char] });
+ break;
}
- fallbacks_by_ns[ns_uri].add(new Xep.FallbackIndication.FallbackLocation(row[db.body_meta.from_char], row[db.body_meta.to_char]));
}
var fallbacks = new ArrayList<Xep.FallbackIndication.Fallback>();
@@ -161,11 +215,29 @@ public class Message : Object {
fallbacks.add(new Xep.FallbackIndication.Fallback(ns_uri, fallbacks_by_ns[ns_uri].to_array()));
}
this.fallbacks = fallbacks;
- return fallbacks;
+ this.markups = markups;
}
public void set_fallbacks(Gee.List<Xep.FallbackIndication.Fallback> fallbacks) {
+ if (id == -1) {
+ warning("Message needs to be persisted before setting fallbacks");
+ return;
+ }
+
this.fallbacks = fallbacks;
+
+ foreach (var fallback in fallbacks) {
+ foreach (var location in fallback.locations) {
+ db.body_meta.insert()
+ .value(db.body_meta.message_id, id)
+ .value(db.body_meta.info_type, Xep.FallbackIndication.NS_URI)
+ .value(db.body_meta.info, fallback.ns_uri)
+ .value(db.body_meta.from_char, location.from_char)
+ .value(db.body_meta.to_char, location.to_char)
+ .perform();
+ }
+ }
+
}
public void set_type_string(string type) {
@@ -202,6 +274,7 @@ public class Message : Object {
}
public static uint hash_func(Message message) {
+ if (message.body == null) return 0;
return message.body.hash();
}
diff --git a/libdino/src/entity/settings.vala b/libdino/src/entity/settings.vala
index 0b09e9b9..be275efc 100644
--- a/libdino/src/entity/settings.vala
+++ b/libdino/src/entity/settings.vala
@@ -79,6 +79,24 @@ public class Settings : Object {
check_spelling_ = value;
}
}
+
+ public Encryption get_default_encryption(Account account) {
+ string? setting = db.account_settings.get_value(account.id, "default-encryption");
+ if (setting != null) {
+ return (Encryption) int.parse(setting);
+ }
+ return Encryption.NONE;
+ }
+
+ public void set_default_encryption(Account account, Encryption encryption) {
+ db.account_settings.upsert()
+ .value(db.account_settings.key, "default-encryption", true)
+ .value(db.account_settings.account_id, account.id, true)
+ .value(db.account_settings.value, ((int)encryption).to_string())
+ .perform();
+
+
+ }
}
}
diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala
index cfe4d0cb..dd25c5f5 100644
--- a/libdino/src/plugin/interfaces.vala
+++ b/libdino/src/plugin/interfaces.vala
@@ -55,6 +55,13 @@ public abstract class AccountSettingsEntry : Object {
public abstract Object? get_widget(WidgetType type);
}
+public abstract class EncryptionPreferencesEntry : Object {
+ public abstract string id { get; }
+ public virtual Priority priority { get { return Priority.DEFAULT; } }
+
+ public abstract Object? get_widget(Account account, WidgetType type);
+}
+
public interface ContactDetailsProvider : Object {
public abstract string id { get; }
diff --git a/libdino/src/plugin/registry.vala b/libdino/src/plugin/registry.vala
index 6c0234ca..7180aa14 100644
--- a/libdino/src/plugin/registry.vala
+++ b/libdino/src/plugin/registry.vala
@@ -6,6 +6,7 @@ public class Registry {
public HashMap<Entities.Encryption, EncryptionListEntry> encryption_list_entries = new HashMap<Entities.Encryption, EncryptionListEntry>();
public HashMap<string, CallEncryptionEntry> call_encryption_entries = new HashMap<string, CallEncryptionEntry>();
public ArrayList<AccountSettingsEntry> account_settings_entries = new ArrayList<AccountSettingsEntry>();
+ public ArrayList<EncryptionPreferencesEntry> encryption_preferences_entries = new ArrayList<EncryptionPreferencesEntry>();
public ArrayList<ContactDetailsProvider> contact_details_entries = new ArrayList<ContactDetailsProvider>();
public Map<string, TextCommand> text_commands = new HashMap<string, TextCommand>();
public Gee.List<ConversationAdditionPopulator> conversation_addition_populators = new ArrayList<ConversationAdditionPopulator>();
@@ -43,6 +44,18 @@ public class Registry {
}
}
+ public bool register_encryption_preferences_entry(EncryptionPreferencesEntry entry) {
+ lock(encryption_preferences_entries) {
+ foreach(var e in encryption_preferences_entries) {
+ if (e.id == entry.id) return false;
+ }
+ encryption_preferences_entries.add(entry);
+ // TODO: Order by priority
+// encryption_preferences_entries.sort((a,b) => b.name.collate(a.name));
+ return true;
+ }
+ }
+
public bool register_contact_details_entry(ContactDetailsProvider entry) {
lock(contact_details_entries) {
foreach(ContactDetailsProvider e in contact_details_entries) {
diff --git a/libdino/src/service/avatar_manager.vala b/libdino/src/service/avatar_manager.vala
index 3bd38e72..f99f37d4 100644
--- a/libdino/src/service/avatar_manager.vala
+++ b/libdino/src/service/avatar_manager.vala
@@ -160,30 +160,32 @@ public class AvatarManager : StreamInteractionModule, Object {
}
}
+ public void unset_avatar(Account account) {
+ XmppStream stream = stream_interactor.get_stream(account);
+ if (stream == null) return;
+ Xmpp.Xep.UserAvatars.unset_avatar(stream);
+ }
+
private void on_account_added(Account account) {
stream_interactor.module_manager.get_module(account, Xep.UserAvatars.Module.IDENTITY).received_avatar_hash.connect((stream, jid, id) =>
- on_user_avatar_received.begin(account, jid, id)
+ on_user_avatar_received(account, jid, id)
);
+ stream_interactor.module_manager.get_module(account, Xep.UserAvatars.Module.IDENTITY).avatar_removed.connect((stream, jid) => {
+ on_user_avatar_removed(account, jid);
+ });
stream_interactor.module_manager.get_module(account, Xep.VCard.Module.IDENTITY).received_avatar_hash.connect((stream, jid, id) =>
- on_vcard_avatar_received.begin(account, jid, id)
+ on_vcard_avatar_received(account, jid, id)
);
foreach (var entry in get_avatar_hashes(account, Source.USER_AVATARS).entries) {
- on_user_avatar_received.begin(account, entry.key, entry.value);
+ on_user_avatar_received(account, entry.key, entry.value);
}
foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) {
-
- // FIXME: remove. temporary to remove falsely saved avatars.
- if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(entry.key, account)) {
- db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(entry.key)).perform();
- continue;
- }
-
- on_vcard_avatar_received.begin(account, entry.key, entry.value);
+ on_vcard_avatar_received(account, entry.key, entry.value);
}
}
- private async void on_user_avatar_received(Account account, Jid jid_, string id) {
+ private void on_user_avatar_received(Account account, Jid jid_, string id) {
Jid jid = jid_.bare_jid;
if (!user_avatars.has_key(jid) || user_avatars[jid] != id) {
@@ -193,7 +195,14 @@ public class AvatarManager : StreamInteractionModule, Object {
received_avatar(jid, account);
}
- private async void on_vcard_avatar_received(Account account, Jid jid_, string id) {
+ private void on_user_avatar_removed(Account account, Jid jid_) {
+ Jid jid = jid_.bare_jid;
+ user_avatars.unset(jid);
+ remove_avatar_hash(account, jid, Source.USER_AVATARS);
+ received_avatar(jid, account);
+ }
+
+ private void on_vcard_avatar_received(Account account, Jid jid_, string id) {
bool is_gc = stream_interactor.get_module(MucManager.IDENTITY).might_be_groupchat(jid_.bare_jid, account);
Jid jid = is_gc ? jid_ : jid_.bare_jid;
@@ -215,6 +224,14 @@ public class AvatarManager : StreamInteractionModule, Object {
.perform();
}
+ public void remove_avatar_hash(Account account, Jid jid, int type) {
+ db.avatar.delete()
+ .with(db.avatar.jid_id, "=", db.get_jid_id(jid))
+ .with(db.avatar.account_id, "=", account.id)
+ .with(db.avatar.type_, "=", type)
+ .perform();
+ }
+
public HashMap<Jid, string> get_avatar_hashes(Account account, int type) {
HashMap<Jid, string> ret = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
foreach (Row row in db.avatar.select({db.avatar.jid_id, db.avatar.hash})
diff --git a/libdino/src/service/conversation_manager.vala b/libdino/src/service/conversation_manager.vala
index f966ccc7..a757e8af 100644
--- a/libdino/src/service/conversation_manager.vala
+++ b/libdino/src/service/conversation_manager.vala
@@ -48,6 +48,14 @@ public class ConversationManager : StreamInteractionModule, Object {
// Create a new converation
Conversation conversation = new Conversation(jid, account, type);
+ // Set encryption for conversation
+ if (type == Conversation.Type.CHAT ||
+ (type == Conversation.Type.GROUPCHAT && stream_interactor.get_module(MucManager.IDENTITY).is_private_room(account, jid))) {
+ conversation.encryption = Application.get_default().settings.get_default_encryption(account);
+ } else {
+ conversation.encryption = Encryption.NONE;
+ }
+
add_conversation(conversation);
conversation.persist(db);
return conversation;
diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala
index dc1d68f3..eba8b7ca 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 = 26;
+ private const int VERSION = 27;
public class AccountTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
@@ -354,6 +354,29 @@ public class Database : Qlite.Database {
}
}
+ public class AccountSettingsTable : 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 };
+ public Column<string> key = new Column.Text("key") { not_null = true };
+ public Column<string> value = new Column.Text("value");
+
+ internal AccountSettingsTable(Database db) {
+ base(db, "account_settings");
+ init({id, account_id, key, value});
+ unique({account_id, key}, "REPLACE");
+ }
+
+ public string? get_value(int account_id, string key) {
+ var row_opt = select({value})
+ .with(this.account_id, "=", account_id)
+ .with(this.key, "=", key)
+ .single()
+ .row();
+ if (row_opt.is_present()) return row_opt[value];
+ return null;
+ }
+ }
+
public class ConversationSettingsTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
public Column<int> conversation_id = new Column.Integer("conversation_id") {not_null=true};
@@ -388,6 +411,7 @@ public class Database : Qlite.Database {
public MamCatchupTable mam_catchup { get; private set; }
public ReactionTable reaction { get; private set; }
public SettingsTable settings { get; private set; }
+ public AccountSettingsTable account_settings { get; private set; }
public ConversationSettingsTable conversation_settings { get; private set; }
public Map<int, Jid> jid_table_cache = new HashMap<int, Jid>();
@@ -417,8 +441,9 @@ public class Database : Qlite.Database {
mam_catchup = new MamCatchupTable(this);
reaction = new ReactionTable(this);
settings = new SettingsTable(this);
+ account_settings = new AccountSettingsTable(this);
conversation_settings = new ConversationSettingsTable(this);
- init({ account, jid, entity, content_item, message, body_meta, message_correction, reply, real_jid, occupantid, file_transfer, call, call_counterpart, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, reaction, settings, conversation_settings });
+ init({ account, jid, entity, content_item, message, body_meta, message_correction, reply, real_jid, occupantid, file_transfer, call, call_counterpart, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, reaction, settings, account_settings, conversation_settings });
try {
exec("PRAGMA journal_mode = WAL");
@@ -576,6 +601,9 @@ public class Database : Qlite.Database {
foreach(Row row in account.select()) {
try {
Account account = new Account.from_row(this, row);
+ if (account_table_cache.has_key(account.id)) {
+ account = account_table_cache[account.id];
+ }
ret.add(account);
account_table_cache[account.id] = account;
} catch (InvalidJidError e) {
diff --git a/libdino/src/service/entity_info.vala b/libdino/src/service/entity_info.vala
index d1217e81..83e27d4b 100644
--- a/libdino/src/service/entity_info.vala
+++ b/libdino/src/service/entity_info.vala
@@ -90,6 +90,20 @@ public class EntityInfo : StreamInteractionModule, Object {
return info_result.features.contains(feature);
}
+ public bool has_feature_offline(Account account, Jid jid, string feature) {
+ int ret = has_feature_cached_int(account, jid, feature);
+ if (ret == -1) {
+ return db.entity.select()
+ .with(db.entity.account_id, "=", account.id)
+ .with(db.entity.jid_id, "=", db.get_jid_id(jid))
+ .with(db.entity.resource, "=", jid.resourcepart ?? "")
+ .join_with(db.entity_feature, db.entity.caps_hash, db.entity_feature.entity)
+ .with(db.entity_feature.feature, "=", feature)
+ .count() > 0;
+ }
+ return ret == 1;
+ }
+
public bool has_feature_cached(Account account, Jid jid, string feature) {
return has_feature_cached_int(account, jid, feature) == 1;
}
@@ -203,13 +217,24 @@ public class EntityInfo : StreamInteractionModule, Object {
ServiceDiscovery.InfoResult? info_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, jid);
if (info_result == null) return null;
- if (hash != null && EntityCapabilities.Module.compute_hash_for_info_result(info_result) == hash) {
- store_features(hash, info_result.features);
- store_identities(hash, info_result.identities);
+ var computed_hash = EntityCapabilities.Module.compute_hash_for_info_result(info_result);
+
+ if (hash == null || computed_hash == hash) {
+ db.entity.upsert()
+ .value(db.entity.account_id, account.id, true)
+ .value(db.entity.jid_id, db.get_jid_id(jid), true)
+ .value(db.entity.resource, jid.resourcepart ?? "", true)
+ .value(db.entity.last_seen, (long)(new DateTime.now_local()).to_unix())
+ .value(db.entity.caps_hash, computed_hash)
+ .perform();
+
+ store_features(computed_hash, info_result.features);
+ store_identities(computed_hash, info_result.identities);
} else {
- jid_features[jid] = info_result.features;
- jid_identity[jid] = info_result.identities;
+ warning("Claimed entity caps hash from %s doesn't match computed one", jid.to_string());
}
+ jid_features[jid] = info_result.features;
+ jid_identity[jid] = info_result.identities;
return info_result;
}
diff --git a/libdino/src/service/fallback_body.vala b/libdino/src/service/fallback_body.vala
index 13323427..0ce89ade 100644
--- a/libdino/src/service/fallback_body.vala
+++ b/libdino/src/service/fallback_body.vala
@@ -46,20 +46,9 @@ public class Dino.FallbackBody : StreamInteractionModule, Object {
if (fallbacks.is_empty) return false;
foreach (var fallback in fallbacks) {
- if (fallback.ns_uri != Xep.Replies.NS_URI) continue;
-
- foreach (var location in fallback.locations) {
- db.body_meta.insert()
- .value(db.body_meta.message_id, message.id)
- .value(db.body_meta.info_type, Xep.FallbackIndication.NS_URI)
- .value(db.body_meta.info, fallback.ns_uri)
- .value(db.body_meta.from_char, location.from_char)
- .value(db.body_meta.to_char, location.to_char)
- .perform();
- }
-
- message.set_fallbacks(fallbacks);
+ if (fallback.ns_uri != Xep.Replies.NS_URI) continue; // TODO what if it's not
}
+ message.set_fallbacks(fallbacks);
return false;
}
diff --git a/libdino/src/service/message_correction.vala b/libdino/src/service/message_correction.vala
index 8f9770d8..e6401a05 100644
--- a/libdino/src/service/message_correction.vala
+++ b/libdino/src/service/message_correction.vala
@@ -39,27 +39,21 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
});
}
- public void send_correction(Conversation conversation, Message old_message, string correction_text) {
- string stanza_id = old_message.edit_to ?? old_message.stanza_id;
+ public void set_correction(Conversation conversation, Message message, Message old_message) {
+ string reference_stanza_id = old_message.edit_to ?? old_message.stanza_id;
- Message out_message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(correction_text, conversation);
- out_message.edit_to = stanza_id;
- out_message.quoted_item_id = old_message.quoted_item_id;
- outstanding_correction_nodes[out_message.stanza_id] = stanza_id;
- stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(out_message, conversation);
+ outstanding_correction_nodes[message.stanza_id] = reference_stanza_id;
db.message_correction.insert()
- .value(db.message_correction.message_id, out_message.id)
- .value(db.message_correction.to_stanza_id, stanza_id)
- .perform();
+ .value(db.message_correction.message_id, message.id)
+ .value(db.message_correction.to_stanza_id, reference_stanza_id)
+ .perform();
db.content_item.update()
- .with(db.content_item.foreign_id, "=", old_message.id)
- .with(db.content_item.content_type, "=", 1)
- .set(db.content_item.foreign_id, out_message.id)
- .perform();
-
- on_received_correction(conversation, out_message.id);
+ .with(db.content_item.foreign_id, "=", old_message.id)
+ .with(db.content_item.content_type, "=", 1)
+ .set(db.content_item.foreign_id, message.id)
+ .perform();
}
public bool is_own_correction_allowed(Conversation conversation, Message message) {
@@ -145,7 +139,7 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
return false;
}
- private void on_received_correction(Conversation conversation, int message_id) {
+ public void on_received_correction(Conversation conversation, int message_id) {
ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_foreign(conversation, 1, message_id);
if (content_item != null) {
received_correction(content_item);
diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala
index baab37ce..d8ea3e2d 100644
--- a/libdino/src/service/message_processor.vala
+++ b/libdino/src/service/message_processor.vala
@@ -38,6 +38,7 @@ public class MessageProcessor : StreamInteractionModule, Object {
received_pipeline.connect(new FilterMessageListener());
received_pipeline.connect(new StoreMessageListener(this, stream_interactor));
received_pipeline.connect(new StoreContentItemListener(stream_interactor));
+ received_pipeline.connect(new MarkupListener(stream_interactor));
stream_interactor.account_added.connect(on_account_added);
@@ -45,18 +46,6 @@ public class MessageProcessor : StreamInteractionModule, Object {
stream_interactor.stream_resumed.connect(send_unsent_chat_messages);
}
- public Entities.Message send_text(string text, Conversation conversation) {
- Entities.Message message = create_out_message(text, conversation);
- return send_message(message, conversation);
- }
-
- public Entities.Message send_message(Entities.Message message, Conversation conversation) {
- stream_interactor.get_module(ContentItemStore.IDENTITY).insert_message(message, conversation);
- send_xmpp_message(message, conversation);
- message_sent(message, conversation);
- return message;
- }
-
private void convert_sending_to_unsent_msgs(Account account) {
db.message.update()
.with(db.message.account_id, "=", account.id)
@@ -344,6 +333,25 @@ public class MessageProcessor : StreamInteractionModule, Object {
}
}
+ private class MarkupListener : MessageListener {
+
+ public string[] after_actions_const = new string[]{ "STORE" };
+ public override string action_group { get { return "Markup"; } }
+ public override string[] after_actions { get { return after_actions_const; } }
+
+ private StreamInteractor stream_interactor;
+
+ public MarkupListener(StreamInteractor stream_interactor) {
+ this.stream_interactor = stream_interactor;
+ }
+
+ public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) {
+ Gee.List<MessageMarkup.Span> markups = MessageMarkup.get_spans(stanza);
+ message.persist_markups(markups, message.id);
+ return false;
+ }
+ }
+
private class StoreContentItemListener : MessageListener {
public string[] after_actions_const = new string[]{ "DEDUPLICATE", "DECRYPT", "FILTER_EMPTY", "STORE", "CORRECTION", "MESSAGE_REINTERPRETING" };
@@ -406,8 +414,22 @@ public class MessageProcessor : StreamInteractionModule, Object {
new_message.type_ = MessageStanza.TYPE_CHAT;
}
- string? fallback = get_fallback_body_set_infos(message, new_message, conversation);
- new_message.body = fallback == null ? message.body : fallback + message.body;
+ if (message.quoted_item_id != 0) {
+ ContentItem? quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, message.quoted_item_id);
+ if (quoted_content_item != null) {
+ Jid? quoted_sender = message.from;
+ string? quoted_stanza_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, quoted_content_item);
+ if (quoted_sender != null && quoted_stanza_id != null) {
+ Xep.Replies.set_reply_to(new_message, new Xep.Replies.ReplyTo(quoted_sender, quoted_stanza_id));
+ }
+
+ foreach (var fallback in message.get_fallbacks()) {
+ Xep.FallbackIndication.set_fallback(new_message, fallback);
+ }
+ }
+ }
+
+ MessageMarkup.add_spans(new_message, message.get_markups());
build_message_stanza(message, new_message, conversation);
pre_message_send(message, new_message, conversation);
@@ -456,26 +478,6 @@ public class MessageProcessor : StreamInteractionModule, Object {
}
});
}
-
- public string? get_fallback_body_set_infos(Entities.Message message, MessageStanza new_stanza, Conversation conversation) {
- if (message.quoted_item_id == 0) return null;
-
- ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, message.quoted_item_id);
- if (content_item == null) return null;
-
- Jid? quoted_sender = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_sender_for_content_item(conversation, content_item);
- string? quoted_stanza_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, content_item);
- if (quoted_sender != null && quoted_stanza_id != null) {
- Xep.Replies.set_reply_to(new_stanza, new Xep.Replies.ReplyTo(quoted_sender, quoted_stanza_id));
- }
-
- string fallback = FallbackBody.get_quoted_fallback_body(content_item);
-
- var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback.char_count());
- Xep.FallbackIndication.set_fallback(new_stanza, new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location }));
-
- return fallback;
- }
}
public abstract class MessageListener : Xmpp.OrderedListener {
diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala
index 119079f0..111ace22 100644
--- a/libdino/src/service/muc_manager.vala
+++ b/libdino/src/service/muc_manager.vala
@@ -84,11 +84,6 @@ public class MucManager : StreamInteractionModule, Object {
}
mucs_joining[account].add(jid);
- if (!mucs_todo.has_key(account)) {
- mucs_todo[account] = new HashSet<Jid>(Jid.hash_bare_func, Jid.equals_bare_func);
- }
- mucs_todo[account].add(jid.with_resource(nick_));
-
Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since, receive_history, null);
mucs_joining[account].remove(jid);
@@ -127,6 +122,11 @@ public class MucManager : StreamInteractionModule, Object {
enter_errors[jid] = res.muc_error;
}
+ if (!mucs_todo.has_key(account)) {
+ mucs_todo[account] = new HashSet<Jid>(Jid.hash_bare_func, Jid.equals_bare_func);
+ }
+ mucs_todo[account].add(jid.with_resource(res.nick ?? nick_));
+
return res;
}
@@ -232,15 +232,8 @@ public class MucManager : StreamInteractionModule, Object {
//the term `private room` is a short hand for members-only+non-anonymous rooms
public bool is_private_room(Account account, Jid jid) {
- XmppStream? stream = stream_interactor.get_stream(account);
- if (stream == null) {
- return false;
- }
- Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY);
- if (flag == null) {
- return false;
- }
- return flag.has_room_feature(jid, Xep.Muc.Feature.NON_ANONYMOUS) && flag.has_room_feature(jid, Xep.Muc.Feature.MEMBERS_ONLY);
+ var entity_info = stream_interactor.get_module(EntityInfo.IDENTITY);
+ return entity_info.has_feature_offline(account, jid, "muc_membersonly") && entity_info.has_feature_offline(account, jid, "muc_nonanonymous");
}
public bool is_moderated_room(Account account, Jid jid) {
diff --git a/libdino/src/service/replies.vala b/libdino/src/service/replies.vala
index 58d44b37..cc9f43cc 100644
--- a/libdino/src/service/replies.vala
+++ b/libdino/src/service/replies.vala
@@ -38,17 +38,6 @@ public class Dino.Replies : StreamInteractionModule, Object {
return null;
}
- public void set_message_is_reply_to(Message message, ContentItem reply_to) {
- message.quoted_item_id = reply_to.id;
-
- db.reply.upsert()
- .value(db.reply.message_id, message.id, true)
- .value(db.reply.quoted_content_item_id, reply_to.id)
- .value_null(db.reply.quoted_message_stanza_id)
- .value_null(db.reply.quoted_message_from)
- .perform();
- }
-
private void on_incoming_message(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) {
// Check if a previous message was in reply to this one
var reply_qry = db.reply.select();
@@ -67,7 +56,7 @@ public class Dino.Replies : StreamInteractionModule, Object {
ContentItem? message_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_foreign(conversation, 1, message.id);
Message? reply_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(reply_row[db.message.id], conversation);
if (message_item != null && reply_message != null) {
- set_message_is_reply_to(reply_message, message_item);
+ reply_message.set_quoted_item(message_item.id);
}
}
@@ -78,7 +67,7 @@ public class Dino.Replies : StreamInteractionModule, Object {
ContentItem? quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_for_message_id(conversation, reply_to.to_message_id);
if (quoted_content_item == null) return;
- set_message_is_reply_to(message, quoted_content_item);
+ message.set_quoted_item(quoted_content_item.id);
}
private class ReceivedMessageListener : MessageListener {
diff --git a/libdino/src/util/send_message.vala b/libdino/src/util/send_message.vala
new file mode 100644
index 00000000..234a1644
--- /dev/null
+++ b/libdino/src/util/send_message.vala
@@ -0,0 +1,56 @@
+using Gee;
+
+using Dino.Entities;
+using Xmpp;
+
+namespace Dino {
+
+ public void send_message(Conversation conversation, string text, int reply_to_id, Message? correction_to, Gee.List<Xep.MessageMarkup.Span> markups) {
+ StreamInteractor stream_interactor = Application.get_default().stream_interactor;
+
+ Message out_message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(text, conversation);
+
+ if (correction_to != null) {
+ string correction_to_stanza_id = correction_to.edit_to ?? correction_to.stanza_id;
+ out_message.edit_to = correction_to_stanza_id;
+ stream_interactor.get_module(MessageCorrection.IDENTITY).set_correction(conversation, out_message, correction_to);
+ }
+
+ if (reply_to_id != 0) {
+ ContentItem reply_to = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, reply_to_id);
+
+ out_message.set_quoted_item(reply_to.id);
+
+ // Store body with fallback
+ string fallback = FallbackBody.get_quoted_fallback_body(reply_to);
+ out_message.body = fallback + out_message.body;
+
+ // Store fallback location
+ var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback.char_count());
+ var fallback_list = new ArrayList<Xep.FallbackIndication.Fallback>();
+ fallback_list.add(new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location }));
+ out_message.set_fallbacks(fallback_list);
+
+ // Adjust markups to new prefix
+ foreach (var span in markups) {
+ span.start_char += fallback.length;
+ span.end_char += fallback.length;
+ }
+ }
+
+ if (!markups.is_empty) {
+ out_message.persist_markups(markups, out_message.id);
+ }
+
+
+ if (correction_to != null) {
+ stream_interactor.get_module(MessageCorrection.IDENTITY).on_received_correction(conversation, out_message.id);
+ stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(out_message, conversation);
+ return;
+ }
+
+ stream_interactor.get_module(ContentItemStore.IDENTITY).insert_message(out_message, conversation);
+ stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(out_message, conversation);
+ stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent(out_message, conversation);
+ }
+} \ No newline at end of file