mirror of
https://github.com/chatmail/core.git
synced 2026-04-27 10:26:29 +03:00
feat: Group and broadcast channel descriptions (#7829)
fix https://github.com/chatmail/core/issues/7766 Implementation notes: - Descriptions are only sent with member additions, when the description is changed, and when promoting a previously-unpromoted group, in order not to waste bandwith. - Descriptions are not loaded everytime a chat object is loaded, because they are only needed for the profile. Instead, they are in their own table, and can be loaded with their own JsonRPC call. --------- Co-authored-by: iequidoo <117991069+iequidoo@users.noreply.github.com>
This commit is contained in:
128
src/chat.rs
128
src/chat.rs
@@ -1763,10 +1763,11 @@ impl Chat {
|
||||
} else if matches!(self.typ, Chattype::Group | Chattype::OutBroadcast)
|
||||
&& self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
|
||||
{
|
||||
msg.param.set_int(Param::AttachGroupImage, 1);
|
||||
msg.param.set_int(Param::AttachChatAvatarAndDescription, 1);
|
||||
self.param
|
||||
.remove(Param::Unpromoted)
|
||||
.set_i64(Param::GroupNameTimestamp, msg.timestamp_sort);
|
||||
.set_i64(Param::GroupNameTimestamp, msg.timestamp_sort)
|
||||
.set_i64(Param::GroupDescriptionTimestamp, msg.timestamp_sort);
|
||||
self.update_param(context).await?;
|
||||
// TODO: Remove this compat code needed because Core <= v1.143:
|
||||
// - doesn't accept synchronization of QR code tokens for unpromoted groups, so we also
|
||||
@@ -2807,9 +2808,18 @@ async fn render_mime_message_and_pre_message(
|
||||
///
|
||||
/// The caller has to interrupt SMTP loop or otherwise process new rows.
|
||||
pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
|
||||
if msg.param.get_cmd() == SystemMessage::GroupNameChanged {
|
||||
let cmd = msg.param.get_cmd();
|
||||
if cmd == SystemMessage::GroupNameChanged || cmd == SystemMessage::GroupDescriptionChanged {
|
||||
msg.chat_id
|
||||
.update_timestamp(context, Param::GroupNameTimestamp, msg.timestamp_sort)
|
||||
.update_timestamp(
|
||||
context,
|
||||
if cmd == SystemMessage::GroupNameChanged {
|
||||
Param::GroupNameTimestamp
|
||||
} else {
|
||||
Param::GroupDescriptionTimestamp
|
||||
},
|
||||
msg.timestamp_sort,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -3882,9 +3892,11 @@ pub(crate) async fn add_contact_to_chat_ex(
|
||||
|
||||
let sync_qr_code_tokens;
|
||||
if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
let smeared_time = smeared_time(context);
|
||||
chat.param
|
||||
.remove(Param::Unpromoted)
|
||||
.set_i64(Param::GroupNameTimestamp, smeared_time(context));
|
||||
.set_i64(Param::GroupNameTimestamp, smeared_time)
|
||||
.set_i64(Param::GroupDescriptionTimestamp, smeared_time);
|
||||
chat.update_param(context).await?;
|
||||
sync_qr_code_tokens = true;
|
||||
} else {
|
||||
@@ -4187,7 +4199,107 @@ async fn send_member_removal_msg(
|
||||
send_msg(context, chat.id, &mut msg).await
|
||||
}
|
||||
|
||||
/// Sets group or mailing list chat name.
|
||||
/// Set group or broadcast channel description.
|
||||
///
|
||||
/// If the group is already _promoted_ (any message was sent to the group),
|
||||
/// or if this is a brodacast channel,
|
||||
/// all members are informed by a special status message that is sent automatically by this function.
|
||||
///
|
||||
/// Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
///
|
||||
/// See also [`get_chat_description`]
|
||||
pub async fn set_chat_description(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
new_description: &str,
|
||||
) -> Result<()> {
|
||||
set_chat_description_ex(context, Sync, chat_id, new_description).await
|
||||
}
|
||||
|
||||
async fn set_chat_description_ex(
|
||||
context: &Context,
|
||||
mut sync: sync::Sync,
|
||||
chat_id: ChatId,
|
||||
new_description: &str,
|
||||
) -> Result<()> {
|
||||
let new_description = sanitize_bidi_characters(new_description.trim());
|
||||
|
||||
ensure!(!chat_id.is_special(), "Invalid chat ID");
|
||||
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
ensure!(
|
||||
chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast,
|
||||
"Can only set description for groups / broadcasts"
|
||||
);
|
||||
ensure!(
|
||||
!chat.grpid.is_empty(),
|
||||
"Cannot set description for ad hoc groups"
|
||||
);
|
||||
if !chat.is_self_in_chat(context).await? {
|
||||
context.emit_event(EventType::ErrorSelfNotInGroup(
|
||||
"Cannot set chat description; self not in group".into(),
|
||||
));
|
||||
bail!("Cannot set chat description; self not in group");
|
||||
}
|
||||
|
||||
let affected_rows = context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO chats_descriptions(chat_id, description) VALUES(?, ?)
|
||||
ON CONFLICT(chat_id) DO UPDATE
|
||||
SET description=excluded.description WHERE description<>excluded.description",
|
||||
(chat_id, &new_description),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if affected_rows == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if chat.is_promoted() {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = stock_str::msg_chat_description_changed(context, ContactId::SELF).await;
|
||||
msg.param.set_cmd(SystemMessage::GroupDescriptionChanged);
|
||||
|
||||
msg.id = send_msg(context, chat_id, &mut msg).await?;
|
||||
context.emit_msgs_changed(chat_id, msg.id);
|
||||
sync = Nosync;
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
|
||||
if sync.into() {
|
||||
chat.sync(context, SyncAction::SetDescription(new_description))
|
||||
.await
|
||||
.log_err(context)
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load the chat description from the database.
|
||||
///
|
||||
/// UIs show this in the profile page of the chat,
|
||||
/// it is settable by [`set_chat_description`]
|
||||
pub async fn get_chat_description(context: &Context, chat_id: ChatId) -> Result<String> {
|
||||
let description = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT description FROM chats_descriptions WHERE chat_id=?",
|
||||
(chat_id,),
|
||||
)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
Ok(description)
|
||||
}
|
||||
|
||||
/// Sets group, mailing list, or broadcast channel chat name.
|
||||
///
|
||||
/// If the group is already _promoted_ (any message was sent to the group),
|
||||
/// or if this is a brodacast channel,
|
||||
/// all members are informed by a special status message that is sent automatically by this function.
|
||||
///
|
||||
/// Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
|
||||
rename_ex(context, Sync, chat_id, new_name).await
|
||||
}
|
||||
@@ -5015,6 +5127,7 @@ pub(crate) enum SyncAction {
|
||||
///
|
||||
/// The list is a list of pairs of fingerprint and address.
|
||||
SetPgpContacts(Vec<(String, String)>),
|
||||
SetDescription(String),
|
||||
Delete,
|
||||
}
|
||||
|
||||
@@ -5113,6 +5226,9 @@ impl Context {
|
||||
Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
|
||||
}
|
||||
SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,
|
||||
SyncAction::SetDescription(to) => {
|
||||
set_chat_description_ex(self, Nosync, chat_id, to).await
|
||||
}
|
||||
SyncAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await,
|
||||
SyncAction::SetPgpContacts(fingerprint_addrs) => {
|
||||
set_contacts_by_fingerprints(self, chat_id, fingerprint_addrs).await
|
||||
|
||||
@@ -3156,6 +3156,119 @@ async fn test_broadcasts_name_and_avatar() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chat_description_basic() {
|
||||
test_chat_description("", false).await.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chat_description_unpromoted_description() {
|
||||
test_chat_description("Unpromoted description in the beginning", false)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chat_description_qr() {
|
||||
test_chat_description("", true).await.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chat_description_unpromoted_description_qr() {
|
||||
test_chat_description("Unpromoted description in the beginning", true)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn test_chat_description(initial_description: &str, join_via_qr: bool) -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let alice2 = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
alice.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
alice2.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
|
||||
tcm.section("Create a group chat, and add Bob");
|
||||
let alice_chat_id = create_group(alice, "My Group").await?;
|
||||
|
||||
if !initial_description.is_empty() {
|
||||
set_chat_description(alice, alice_chat_id, initial_description).await?;
|
||||
}
|
||||
sync(alice, alice2).await;
|
||||
|
||||
let alice2_chat_id = get_chat_id_by_grpid(
|
||||
alice2,
|
||||
&Chat::load_from_db(alice, alice_chat_id).await?.grpid,
|
||||
)
|
||||
.await?
|
||||
.unwrap()
|
||||
.0;
|
||||
assert_eq!(
|
||||
get_chat_description(alice2, alice2_chat_id).await?,
|
||||
initial_description
|
||||
);
|
||||
|
||||
let bob_chat_id = if join_via_qr {
|
||||
let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await.unwrap();
|
||||
tcm.exec_securejoin_qr(bob, alice, &qr).await
|
||||
} else {
|
||||
let alice_bob_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
add_contact_to_chat(alice, alice_chat_id, alice_bob_id).await?;
|
||||
let sent = alice.send_text(alice_chat_id, "promoting the group").await;
|
||||
bob.recv_msg(&sent).await.chat_id
|
||||
};
|
||||
assert_eq!(
|
||||
get_chat_description(bob, bob_chat_id).await?,
|
||||
initial_description
|
||||
);
|
||||
|
||||
for description in ["This is a cool group", "", "ä ẟ 😂"] {
|
||||
tcm.section(&format!(
|
||||
"Alice sets the chat description to '{description}'"
|
||||
));
|
||||
set_chat_description(alice, alice_chat_id, description).await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
assert_eq!(
|
||||
sent.load_from_db().await.text,
|
||||
"You changed the chat description."
|
||||
);
|
||||
|
||||
tcm.section("Bob receives the description change");
|
||||
let rcvd = bob.recv_msg(&sent).await;
|
||||
assert_eq!(rcvd.get_info_type(), SystemMessage::GroupDescriptionChanged);
|
||||
assert_eq!(rcvd.text, "Chat description changed by alice@example.org.");
|
||||
|
||||
assert_eq!(get_chat_description(bob, rcvd.chat_id).await?, description);
|
||||
|
||||
tcm.section("Check Alice's second device");
|
||||
alice2.recv_msg(&sent).await;
|
||||
let alice2_chat_id = get_chat_id_by_grpid(
|
||||
alice2,
|
||||
&Chat::load_from_db(alice, alice_chat_id).await?.grpid,
|
||||
)
|
||||
.await?
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
assert_eq!(
|
||||
get_chat_description(alice2, alice2_chat_id).await?,
|
||||
description
|
||||
);
|
||||
}
|
||||
|
||||
tcm.section("Alice calls set_chat_description() without actually changing the description");
|
||||
set_chat_description(alice, alice_chat_id, "ä ẟ 😂").await?;
|
||||
assert!(
|
||||
alice
|
||||
.pop_sent_msg_opt(Duration::from_secs(0))
|
||||
.await
|
||||
.is_none()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that directly after broadcast-securejoin,
|
||||
/// the brodacast is shown correctly on both devices.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
@@ -60,6 +60,9 @@ pub enum HeaderDef {
|
||||
ChatGroupName,
|
||||
ChatGroupNameChanged,
|
||||
ChatGroupNameTimestamp,
|
||||
ChatGroupDescription,
|
||||
ChatGroupDescriptionChanged,
|
||||
ChatGroupDescriptionTimestamp,
|
||||
ChatVerified,
|
||||
ChatGroupAvatar,
|
||||
ChatUserAvatar,
|
||||
|
||||
@@ -1005,6 +1005,7 @@ impl Message {
|
||||
pub async fn get_info_contact_id(&self, context: &Context) -> Result<Option<ContactId>> {
|
||||
match self.param.get_cmd() {
|
||||
SystemMessage::GroupNameChanged
|
||||
| SystemMessage::GroupDescriptionChanged
|
||||
| SystemMessage::GroupImageChanged
|
||||
| SystemMessage::EphemeralTimerChanged => {
|
||||
if self.from_id != ContactId::INFO {
|
||||
|
||||
@@ -663,7 +663,7 @@ impl MimeFactory {
|
||||
|
||||
if msg
|
||||
.param
|
||||
.get_bool(Param::AttachGroupImage)
|
||||
.get_bool(Param::AttachChatAvatarAndDescription)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return chat.param.get(Param::ProfileImage).map(Into::into);
|
||||
@@ -1669,6 +1669,12 @@ impl MimeFactory {
|
||||
mail_builder::headers::text::Text::new(old_name).into(),
|
||||
));
|
||||
}
|
||||
SystemMessage::GroupDescriptionChanged => {
|
||||
headers.push((
|
||||
"Chat-Group-Description-Changed",
|
||||
mail_builder::headers::text::Text::new("").into(),
|
||||
));
|
||||
}
|
||||
SystemMessage::GroupImageChanged => {
|
||||
headers.push((
|
||||
"Chat-Content",
|
||||
@@ -1683,6 +1689,26 @@ impl MimeFactory {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if command == SystemMessage::GroupDescriptionChanged
|
||||
|| command == SystemMessage::MemberAddedToGroup
|
||||
|| msg
|
||||
.param
|
||||
.get_bool(Param::AttachChatAvatarAndDescription)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
let description = chat::get_chat_description(context, chat.id).await?;
|
||||
headers.push((
|
||||
"Chat-Group-Description",
|
||||
mail_builder::headers::text::Text::new(description.clone()).into(),
|
||||
));
|
||||
if let Some(ts) = chat.param.get_i64(Param::GroupDescriptionTimestamp) {
|
||||
headers.push((
|
||||
"Chat-Group-Description-Timestamp",
|
||||
mail_builder::headers::text::Text::new(ts.to_string()).into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match command {
|
||||
|
||||
@@ -186,10 +186,10 @@ pub enum SystemMessage {
|
||||
#[default]
|
||||
Unknown = 0,
|
||||
|
||||
/// Group name changed.
|
||||
/// Group or broadcast channel name changed.
|
||||
GroupNameChanged = 2,
|
||||
|
||||
/// Group avatar changed.
|
||||
/// Group or broadcast channel avatar changed.
|
||||
GroupImageChanged = 3,
|
||||
|
||||
/// Member was added to the group.
|
||||
@@ -254,6 +254,9 @@ pub enum SystemMessage {
|
||||
|
||||
/// Message indicating that a call was ended.
|
||||
CallEnded = 67,
|
||||
|
||||
/// Group or broadcast channel description changed.
|
||||
GroupDescriptionChanged = 70,
|
||||
}
|
||||
|
||||
const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
|
||||
@@ -773,6 +776,11 @@ impl MimeMessage {
|
||||
self.is_system_message = SystemMessage::MemberAddedToGroup;
|
||||
} else if self.get_header(HeaderDef::ChatGroupNameChanged).is_some() {
|
||||
self.is_system_message = SystemMessage::GroupNameChanged;
|
||||
} else if self
|
||||
.get_header(HeaderDef::ChatGroupDescriptionChanged)
|
||||
.is_some()
|
||||
{
|
||||
self.is_system_message = SystemMessage::GroupDescriptionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ pub enum Param {
|
||||
Arg4 = b'H',
|
||||
|
||||
/// For Messages
|
||||
AttachGroupImage = b'A',
|
||||
AttachChatAvatarAndDescription = b'A',
|
||||
|
||||
/// For Messages
|
||||
WebrtcRoom = b'V',
|
||||
@@ -219,6 +219,9 @@ pub enum Param {
|
||||
/// For Chats: timestamp of group name update.
|
||||
GroupNameTimestamp = b'g',
|
||||
|
||||
/// For Chats: timestamp of chat description update.
|
||||
GroupDescriptionTimestamp = b'6',
|
||||
|
||||
/// For Chats: timestamp of member list update.
|
||||
MemberListTimestamp = b'k',
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ use std::sync::LazyLock;
|
||||
use anyhow::{Context as _, Result, ensure};
|
||||
use data_encoding::BASE32_NOPAD;
|
||||
use deltachat_contact_tools::{
|
||||
ContactAddress, addr_cmp, addr_normalize, may_be_valid_addr, sanitize_single_line,
|
||||
ContactAddress, addr_cmp, addr_normalize, may_be_valid_addr, sanitize_bidi_characters,
|
||||
sanitize_single_line,
|
||||
};
|
||||
use iroh_gossip::proto::TopicId;
|
||||
use mailparse::SingleInfo;
|
||||
@@ -3101,7 +3102,7 @@ async fn apply_group_changes(
|
||||
}
|
||||
|
||||
if is_from_in_chat {
|
||||
apply_chat_name_and_avatar_changes(
|
||||
apply_chat_name_avatar_and_description_changes(
|
||||
context,
|
||||
mime_parser,
|
||||
from_id,
|
||||
@@ -3280,7 +3281,7 @@ async fn apply_group_changes(
|
||||
///
|
||||
/// - `send_event_chat_modified` is set to `true` if ChatModified event should be sent
|
||||
/// - `better_msg` is filled with an info message about name change, if necessary
|
||||
async fn apply_chat_name_and_avatar_changes(
|
||||
async fn apply_chat_name_avatar_and_description_changes(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
from_id: ContactId,
|
||||
@@ -3339,6 +3340,51 @@ async fn apply_chat_name_and_avatar_changes(
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Apply chat description changes ==========
|
||||
|
||||
if let Some(new_description) = mime_parser
|
||||
.get_header(HeaderDef::ChatGroupDescription)
|
||||
.map(|d| d.trim())
|
||||
{
|
||||
let new_description = sanitize_bidi_characters(new_description.trim());
|
||||
let old_description = chat::get_chat_description(context, chat.id).await?;
|
||||
|
||||
let old_timestamp = chat
|
||||
.param
|
||||
.get_i64(Param::GroupDescriptionTimestamp)
|
||||
.unwrap_or(0);
|
||||
let timestamp_in_header = mime_parser
|
||||
.get_header(HeaderDef::ChatGroupDescriptionTimestamp)
|
||||
.and_then(|s| s.parse::<i64>().ok());
|
||||
|
||||
let new_timestamp = timestamp_in_header.unwrap_or(mime_parser.timestamp_sent);
|
||||
// To provide consistency, compare descriptions if timestamps are equal.
|
||||
if (old_timestamp, &old_description) < (new_timestamp, &new_description)
|
||||
&& chat
|
||||
.id
|
||||
.update_timestamp(context, Param::GroupDescriptionTimestamp, new_timestamp)
|
||||
.await?
|
||||
&& new_description != old_description
|
||||
{
|
||||
info!(context, "Updating description for chat {}.", chat.id);
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT OR REPLACE INTO chats_descriptions(chat_id, description) VALUES(?, ?)",
|
||||
(chat.id, &new_description),
|
||||
)
|
||||
.await?;
|
||||
*send_event_chat_modified = true;
|
||||
}
|
||||
if mime_parser
|
||||
.get_header(HeaderDef::ChatGroupDescriptionChanged)
|
||||
.is_some()
|
||||
{
|
||||
better_msg
|
||||
.get_or_insert(stock_str::msg_chat_description_changed(context, from_id).await);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Apply chat avatar changes ==========
|
||||
|
||||
if let (Some(value), None) = (mime_parser.get_header(HeaderDef::ChatContent), &better_msg)
|
||||
@@ -3667,7 +3713,7 @@ async fn apply_out_broadcast_changes(
|
||||
let mut better_msg = None;
|
||||
|
||||
if from_id == ContactId::SELF {
|
||||
apply_chat_name_and_avatar_changes(
|
||||
apply_chat_name_avatar_and_description_changes(
|
||||
context,
|
||||
mime_parser,
|
||||
from_id,
|
||||
@@ -3757,7 +3803,7 @@ async fn apply_in_broadcast_changes(
|
||||
let mut send_event_chat_modified = false;
|
||||
let mut better_msg = None;
|
||||
|
||||
apply_chat_name_and_avatar_changes(
|
||||
apply_chat_name_avatar_and_description_changes(
|
||||
context,
|
||||
mime_parser,
|
||||
from_id,
|
||||
|
||||
@@ -1558,6 +1558,15 @@ ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
|
||||
.await?;
|
||||
}
|
||||
|
||||
inc_and_check(&mut migration_version, 147)?;
|
||||
if dbversion < migration_version {
|
||||
sql.execute_migration(
|
||||
"CREATE TABLE chats_descriptions (chat_id INTEGER PRIMARY KEY AUTOINCREMENT, description TEXT NOT NULL DEFAULT '') STRICT;",
|
||||
migration_version,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
.await?
|
||||
|
||||
@@ -423,6 +423,12 @@ https://delta.chat/donate"))]
|
||||
|
||||
#[strum(props(fallback = "Incoming video call"))]
|
||||
IncomingVideoCall = 235,
|
||||
|
||||
#[strum(props(fallback = "You changed the chat description."))]
|
||||
MsgYouChangedDescription = 240,
|
||||
|
||||
#[strum(props(fallback = "Chat description changed by %1$s."))]
|
||||
MsgChatDescriptionChangedBy = 241,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
@@ -601,6 +607,19 @@ pub(crate) async fn msg_grp_img_changed(context: &Context, by_contact: ContactId
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn msg_chat_description_changed(
|
||||
context: &Context,
|
||||
by_contact: ContactId,
|
||||
) -> String {
|
||||
if by_contact == ContactId::SELF {
|
||||
translated(context, StockMessage::MsgYouChangedDescription).await
|
||||
} else {
|
||||
translated(context, StockMessage::MsgChatDescriptionChangedBy)
|
||||
.await
|
||||
.replace1(&by_contact.get_stock_name(context).await)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stock string: `You added member %1$s.` or `Member %1$s added by %2$s.`.
|
||||
///
|
||||
/// The `added_member_addr` parameter should be an email address and is looked up in the
|
||||
|
||||
Reference in New Issue
Block a user