fix: surface chat state read failures

This commit is contained in:
2026-09-24 19:37:29 +08:00
parent 412359e84c
commit 8428468712
8 changed files with 110 additions and 88 deletions
+8 -4
View File
@@ -59,7 +59,8 @@ async fn handle_callback(
};
// Lazy expiry: a stale record (past the TTL, not yet swept) is dropped.
if edit.created_at + ttl_secs <= unix_now() {
ctx.chat_store
let _ = ctx
.chat_store
.update(chat_id, |data| {
data.edit_message.remove(&prompt_message_id);
})
@@ -80,7 +81,8 @@ async fn handle_callback(
// "do not forward this" answer, and it drops the record so the forward
// can never happen later.
log::info!("edit-before-forward prompt {prompt_message_id} skipped");
ctx.chat_store
let _ = ctx
.chat_store
.update(chat_id, |data| {
data.edit_message.remove(&prompt_message_id);
})
@@ -150,7 +152,8 @@ async fn handle_callback(
.sender
.delete_message(ChatId(chat_id), MessageId(prompt_message_id as i32))
.await;
ctx.chat_store
let _ = ctx
.chat_store
.update(chat_id, |data| {
data.edit_message.remove(&prompt_message_id);
})
@@ -190,7 +193,8 @@ async fn handle_callback(
.await
{
super::EditOutcome::Applied => {
ctx.chat_store
let _ = ctx
.chat_store
.update(chat_id, |data| {
if let Some(entry) = data.edit_message.get_mut(&prompt_message_id) {
entry.template = name.to_string();
+66 -52
View File
@@ -74,6 +74,8 @@ fn parse_arg_remainder(s: String) -> Result<(String,), ParseError> {
/// `x_media::site::caption_from_fields` substitutes.
const FORMAT_PLACEHOLDERS: [&str; 6] = ["url", "author", "author_url", "title", "content", "tags"];
const CHAT_STATE_READ_ERROR: &str = "Couldn't read chat settings; try again.";
/// `/start`'s welcome: what the bot is for, where links work, where to look
/// next. The old "Hello!" left a first-time user with nothing.
const START_TEXT: &str = "\
@@ -294,14 +296,19 @@ pub(crate) async fn execute_command(
}
Command::SetForwardChannel(channel) => {
let result = match set_forward_channel_handler(bot, message, channel).await {
Ok(channel_id) => {
ctx.chat_store
.update(message.chat.id.0, |data| {
data.forward_channel_id = Some(channel_id);
})
.await;
"Add successfully.".to_string()
}
Ok(channel_id) => match ctx
.chat_store
.update(message.chat.id.0, |data| {
data.forward_channel_id = Some(channel_id);
})
.await
{
Ok((_, true)) => "Add successfully.".to_string(),
Ok((_, false)) => {
"Forward channel set only in memory; retry later.".to_string()
}
Err(()) => CHAT_STATE_READ_ERROR.to_string(),
},
Err(SetForwardChannelError::EmptyParameter) => {
"Receive empty parameter.\nYou should enter a channel id or username"
.to_string()
@@ -323,7 +330,7 @@ pub(crate) async fn execute_command(
}
Command::RemoveForwardChannel => {
let chat_id = message.chat.id.0;
let (text, _) = ctx
let text = match ctx
.chat_store
.update(chat_id, |data| {
if data.forward_channel_id.is_some() {
@@ -333,12 +340,16 @@ pub(crate) async fn execute_command(
"No channel to remove.".to_string()
}
})
.await;
.await
{
Ok((text, _)) => text,
Err(()) => CHAT_STATE_READ_ERROR.to_string(),
};
reply(ctx.sender, message.chat.id.0, message.id, text).await?;
}
Command::EditBeforeForward => {
let chat_id = message.chat.id.0;
let (text, _) = ctx
let text = match ctx
.chat_store
.update(chat_id, |data| {
if data.forward_channel_id.is_none() {
@@ -352,7 +363,11 @@ pub(crate) async fn execute_command(
"Enable edit before forward.".to_string()
}
})
.await;
.await
{
Ok((text, _)) => text,
Err(()) => CHAT_STATE_READ_ERROR.to_string(),
};
reply(ctx.sender, message.chat.id.0, message.id, text).await?;
}
Command::SetTemplate(name) => {
@@ -366,15 +381,22 @@ pub(crate) async fn execute_command(
} else if name.is_empty() {
"Please provide a name for the template.".to_string()
} else {
ctx.chat_store
match ctx
.chat_store
.update(chat_id, |data| {
data.template.insert(
name,
html_escape::encode_text(reply_text).into_owned(),
);
})
.await;
"Template set.".to_string()
.await
{
Ok((_, true)) => "Template set.".to_string(),
Ok((_, false)) => {
"Template set only in memory; retry later.".to_string()
}
Err(()) => CHAT_STATE_READ_ERROR.to_string(),
}
}
}
};
@@ -393,21 +415,21 @@ pub(crate) async fn execute_command(
.await?;
return Ok(());
}
let (removed, _) = ctx
let text = match ctx
.chat_store
.update(chat_id, |data| data.template.remove(&name).is_some())
.await;
let text = if removed {
format!("Template '{name}' removed.")
} else {
// Name the live templates: a typo would otherwise look like a
// successful delete.
let names = sorted_template_names(&ctx.chat_store.get(chat_id).await);
if names.is_empty() {
format!("No template named '{name}'. None are saved yet.")
} else {
format!("No template named '{name}'. Saved: {}", names.join(", "))
.await
{
Ok((true, _)) => format!("Template '{name}' removed."),
Ok((false, _)) => {
let names = sorted_template_names(&ctx.chat_store.get(chat_id).await);
if names.is_empty() {
format!("No template named '{name}'. None are saved yet.")
} else {
format!("No template named '{name}'. Saved: {}", names.join(", "))
}
}
Err(()) => CHAT_STATE_READ_ERROR.to_string(),
};
reply(ctx.sender, chat_id, message.id, text).await?;
}
@@ -461,23 +483,18 @@ pub(crate) async fn execute_command(
// set a format once could never get back to the default (the
// built-in format string is not something a user can retype).
if format == "-" {
let (_, saved) = ctx
let text = match ctx
.chat_store
.update(chat_id, |data| {
data.message_format.remove(site);
})
.await;
reply(
ctx.sender,
message.chat.id.0,
message.id,
if saved {
"Format reset to the built-in one.".to_string()
} else {
"Reset in memory only: the database write failed, so it will be lost on restart.".to_string()
},
)
.await?;
.await
{
Ok((_, true)) => "Format reset to the built-in one.".to_string(),
Ok((_, false)) => "Reset in memory only; retry later.".to_string(),
Err(()) => CHAT_STATE_READ_ERROR.to_string(),
};
reply(ctx.sender, message.chat.id.0, message.id, text).await?;
return Ok(());
}
// A typo like {titel} would otherwise be rendered literally into
@@ -500,23 +517,20 @@ pub(crate) async fn execute_command(
.await?;
return Ok(());
}
let (_, saved) = ctx
let text = match ctx
.chat_store
.update(chat_id, |data| {
data.message_format.insert(site.to_string(), format);
})
.await;
reply(
ctx.sender,
message.chat.id.0,
message.id,
if saved {
.await
{
Ok((_, true)) => {
"Format set. Use /debug <link> to preview the caption.".to_string()
} else {
"Set in memory only: the database write failed, so it will be lost on restart. Use /debug <link> to preview the caption.".to_string()
},
)
.await?;
}
Ok((_, false)) => "Format set only in memory; retry later.".to_string(),
Err(()) => CHAT_STATE_READ_ERROR.to_string(),
};
reply(ctx.sender, message.chat.id.0, message.id, text).await?;
}
Command::ClearCache(arg) => {
let Some(sender_id) = require_admin(ctx, message).await? else {
+2 -1
View File
@@ -145,7 +145,8 @@ async fn edit_message_handler(
// (not yet swept) is dropped and the reply falls through to the normal
// message flow instead of rewriting a caption from a dead prompt.
if edit.created_at + ctx.config.edit_message_ttl.as_secs() as i64 <= crate::db::unix_now() {
ctx.chat_store
let _ = ctx
.chat_store
.update(chat_id, |data| {
data.edit_message.remove(&reply_to_message_id);
})
+2 -1
View File
@@ -1009,7 +1009,8 @@ mod tests {
data.forward_channel_id = Some(2);
data.edit_before_forward = true;
})
.await;
.await
.unwrap();
}
#[tokio::test]