From b142f1809d92ac88c77d4cd998c124c5a92f0ebb Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 23 Jul 2026 13:40:09 +0200 Subject: [PATCH 01/46] feat: allow channel subscriber reactions --- src/chat.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index e93f9832c9..eee42d0267 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2687,10 +2687,11 @@ async fn prepare_send_msg( // from the chat. CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup, CantSendReason::InBroadcast => { - matches!( - msg.param.get_cmd(), - SystemMessage::MemberRemovedFromGroup | SystemMessage::SecurejoinMessage - ) + msg.param.get_int(Param::Reaction).unwrap_or_default() != 0 + || matches!( + msg.param.get_cmd(), + SystemMessage::MemberRemovedFromGroup | SystemMessage::SecurejoinMessage + ) } CantSendReason::MissingKey => msg .param From 1230ab5230c49356a20896291ea1f1b8c7c4fa84 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 23 Jul 2026 15:13:20 +0200 Subject: [PATCH 02/46] add tables to handle broadcast reactions --- src/reaction.rs | 14 ++++++++++++++ src/sql/migrations.rs | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/reaction.rs b/src/reaction.rs index 09ec92ad9d..372fe34a8b 100644 --- a/src/reaction.rs +++ b/src/reaction.rs @@ -23,6 +23,7 @@ use serde::{Deserialize, Serialize}; use crate::chat::{Chat, ChatId, send_msg}; use crate::chatlist_events; +use crate::constants::Chattype; use crate::contact::ContactId; use crate::context::Context; use crate::events::EventType; @@ -190,6 +191,19 @@ async fn set_msg_id_reaction( } } + let chat = Chat::load_from_db(context, chat_id).await?; + if chat.typ == Chattype::OutBroadcast { + context + .sql + .execute( + "INSERT INTO reactions_need_broadcast (chat_id, msg_id) + VALUES (?1, ?2) + ON CONFLICT(msg_id, contact_id) DO NOTHING;", + (chat_id, msg_id), + ) + .await?; + } + context.emit_event(EventType::ReactionsChanged { chat_id, msg_id, diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index ecaf59091b..0226a071e9 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -2529,6 +2529,31 @@ UPDATE msgs SET state=24 WHERE state=18; -- Change OutPreparing to OutFailed. fingerprint TEXT PRIMARY KEY NOT NULL, -- Upper-case fingerprint of the recipient key. attached_timestamp INTEGER NOT NULL ) STRICT", + + migration_version, + ) + .await?; + } + + inc_and_check(&mut migration_version, 161)?; + if dbversion < migration_version { + // `reactions_accumulated` stores accumulated reactions for broadcast channel subscribers (Chattype::InBroadcast). + // `reactions_accumulated` is unused for broadcast channel owners (Chattype::OutBroadcast), + // there `reactions_need_broadcast` is used to find out new reactions to be sent to subscribers. + sql.execute_migration( + "CREATE TABLE reactions_accumulated ( + msg_id INTEGER NOT NULL DEFAULT 0, + reaction TEXT NOT NULL DEFAULT '', + count INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted + ) STRICT; + CREATE INDEX reactions_accumulated_index1 ON reactions_accumulated (msg_id); + CREATE TABLE reactions_need_broadcast ( + chat_id INTEGER NOT NULL DEFAULT 0, + msg_id INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted + ) STRICT; + CREATE INDEX reactions_need_broadcast_index1 ON reactions_need_broadcast (chat_id);", migration_version, ) .await?; From b80edffaa2e2a4e9dc28eb188ac1569cc309deb3 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 23 Jul 2026 23:59:09 +0200 Subject: [PATCH 03/46] schedule broadcasting reactions --- src/config.rs | 3 +++ src/constants.rs | 3 +++ src/context.rs | 6 ++++++ src/reaction.rs | 37 +++++++++++++++++++++++++++++++++++++ src/scheduler.rs | 25 +++++++++++++++++++++++++ 5 files changed, 74 insertions(+) diff --git a/src/config.rs b/src/config.rs index df0a7b74c1..ca3024438b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -346,6 +346,9 @@ pub enum Config { /// Timestamp of the last time housekeeping was run LastHousekeeping, + /// Timestamp of the last time accumulated broadcast channel reactions were sent + LastReactionsBroadcast, + /// Timestamp of the last `CantDecryptOutgoingMsgs` notification. LastCantDecryptOutgoingMsgs, diff --git a/src/constants.rs b/src/constants.rs index d8b973c2d3..163ce90a2d 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -199,6 +199,9 @@ pub(crate) const EDITED_PREFIX: &str = "✏️"; /// Period between `sql::housekeeping()` runs. pub(crate) const HOUSEKEEPING_PERIOD: i64 = 24 * 60 * 60; +/// Seconds between sending out accumulated reaction updates for broadcast channels from `reactions_need_broadcast` table +pub(crate) const REACTION_BROADCAST_PERIOD: i64 = 10 * 60; + pub(crate) const BROADCAST_INCOMPATIBILITY_MSG: &str = r#"The up to now "experimental channels feature" is about to become an officially supported one. By that, privacy will be improved, it will become faster, and less traffic will be consumed. As we do not guarantee feature-stability for such experiments, this means, that you will need to create the channel again. diff --git a/src/context.rs b/src/context.rs index e5782bd384..3bf0e1f4d7 100644 --- a/src/context.rs +++ b/src/context.rs @@ -956,6 +956,12 @@ impl Context { .await? .to_string(), ); + res.insert( + "last_reactions_broadcast", + self.get_config_int(Config::LastReactionsBroadcast) + .await? + .to_string(), + ); res.insert( "last_cant_decrypt_outgoing_msgs", self.get_config_int(Config::LastCantDecryptOutgoingMsgs) diff --git a/src/reaction.rs b/src/reaction.rs index 372fe34a8b..c69514ecb0 100644 --- a/src/reaction.rs +++ b/src/reaction.rs @@ -27,6 +27,7 @@ use crate::constants::Chattype; use crate::contact::ContactId; use crate::context::Context; use crate::events::EventType; +use crate::log::warn; use crate::message::{Message, MsgId, rfc724_mid_exists}; use crate::param::Param; @@ -437,6 +438,42 @@ pub async fn get_msg_reactions(context: &Context, msg_id: MsgId) -> Result