From 349664ff53c00ada192f45f7d26cc1455396fa7b Mon Sep 17 00:00:00 2001 From: iequidoo Date: Thu, 12 Jun 2025 21:07:57 -0300 Subject: [PATCH] feat: Donation request device message (#6913) A donation request device message is added if >= 100 messages have been sent and delivered. The condition is checked every 30 days since the first message is sent. The message is added only once. --- deltachat-ffi/deltachat.h | 3 +++ src/chat.rs | 28 ++++++++++++++++++++++++++++ src/config.rs | 3 +++ src/context.rs | 6 ++++++ src/stock_str.rs | 15 +++++++++++++++ 5 files changed, 55 insertions(+) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 25b86e95eb..ee30f2b3b9 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -7667,6 +7667,9 @@ void dc_event_unref(dc_event_t* event); /// @deprecated 2025-06-05 #define DC_STR_SECUREJOIN_TAKES_LONGER 192 +/// "❤️ Seems you're enjoying Delta Chat!"… (donation request device message) +#define DC_STR_DONATION_REQUEST 193 + /// "Contact". Deprecated, currently unused. #define DC_STR_CONTACT 200 diff --git a/src/chat.rs b/src/chat.rs index 1e7c6d1927..ebc7bad5d7 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2967,6 +2967,9 @@ async fn prepare_send_msg( let row_ids = create_send_msg_jobs(context, msg) .await .context("Failed to create send jobs")?; + if !row_ids.is_empty() { + donation_request_maybe(context).await.log_err(context).ok(); + } Ok(row_ids) } @@ -3211,6 +3214,31 @@ pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Re send_msg(context, chat_id, &mut msg).await } +async fn donation_request_maybe(context: &Context) -> Result<()> { + let secs_between_checks = 30 * 24 * 60 * 60; + let now = time(); + let ts = context + .get_config_i64(Config::DonationRequestNextCheck) + .await?; + if ts > now { + return Ok(()); + } + let msg_cnt = context.sql.count( + "SELECT COUNT(*) FROM msgs WHERE state>=? AND hidden=0", + (MessageState::OutDelivered,), + ); + let ts = if ts == 0 || msg_cnt.await? < 100 { + now.saturating_add(secs_between_checks) + } else { + let mut msg = Message::new_text(stock_str::donation_request(context).await); + add_device_msg(context, None, Some(&mut msg)).await?; + i64::MAX + }; + context + .set_config_internal(Config::DonationRequestNextCheck, Some(&ts.to_string())) + .await +} + /// Chat message list request options. #[derive(Debug)] pub struct MessageListOptions { diff --git a/src/config.rs b/src/config.rs index 3c143a78b2..4cc38285ba 100644 --- a/src/config.rs +++ b/src/config.rs @@ -369,6 +369,9 @@ pub enum Config { #[strum(props(default = "0"))] DisableIdle, + /// Timestamp of the next check for donation request need. + DonationRequestNextCheck, + /// Defines the max. size (in bytes) of messages downloaded automatically. /// 0 = no limit. #[strum(props(default = "0"))] diff --git a/src/context.rs b/src/context.rs index 5c1a1e1866..b78a18d493 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1054,6 +1054,12 @@ impl Context { .await? .to_string(), ); + res.insert( + "donation_request_next_check", + self.get_config_i64(Config::DonationRequestNextCheck) + .await? + .to_string(), + ); res.insert( "first_key_contacts_msg_id", self.sql diff --git a/src/stock_str.rs b/src/stock_str.rs index 86fb86b130..4d295da62a 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -413,6 +413,16 @@ pub enum StockMessage { #[strum(props(fallback = "Establishing guaranteed end-to-end encryption, please wait…"))] SecurejoinWait = 190, + + #[strum(props(fallback = "❤️ Seems you're enjoying Delta Chat! + +Please consider donating to help that Delta Chat stays free for everyone. + +While Delta Chat is free to use and open source, development costs money. +Help keeping us to keep Delta Chat independent and make it more awesome in the future. + +https://delta.chat/donate"))] + DonationRequest = 193, } impl StockMessage { @@ -785,6 +795,11 @@ pub(crate) async fn securejoin_wait(context: &Context) -> String { translated(context, StockMessage::SecurejoinWait).await } +/// Stock string: `❤️ Seems you're enjoying Delta Chat!`… +pub(crate) async fn donation_request(context: &Context) -> String { + translated(context, StockMessage::DonationRequest).await +} + /// Stock string: `Scan to chat with %1$s`. pub(crate) async fn setup_contact_qr_description( context: &Context,