fix: always answer inline query

This commit is contained in:
2026-09-24 20:33:23 +08:00
parent 5e7a87c0dd
commit 85c3be5ef5
+18 -20
View File
@@ -114,16 +114,10 @@ static INLINE_DEBOUNCE_STATE: LazyLock<parking_lot::Mutex<DebounceStates>> =
LazyLock::new(|| parking_lot::Mutex::new(DebounceStates::default())); LazyLock::new(|| parking_lot::Mutex::new(DebounceStates::default()));
pub async fn inline_query_handler(bot: Bot, query: InlineQuery) -> Result<(), RequestError> { pub async fn inline_query_handler(bot: Bot, query: InlineQuery) -> Result<(), RequestError> {
if query.query.is_empty() { let ctx = AppContext::from_statics(&bot);
return respond(()); if query.query.is_empty() || x_media::site::cache_key(&query.query).is_none() {
return answer_inline_query(&ctx, query).await.map(|_| ());
} }
// Only run a fetch for something that is actually a supported post URL.
if x_media::site::cache_key(&query.query).is_none() {
return respond(());
}
// Debounce: record the query and answer only after it has been stable for
// INLINE_DEBOUNCE (the timer below). An already-answered repeat of the
// same query is left to Telegram's inline cache instead of re-fetching.
let user_id = query.from.id.0; let user_id = query.from.id.0;
if !INLINE_DEBOUNCE_STATE.lock().note(user_id, &query.query) { if !INLINE_DEBOUNCE_STATE.lock().note(user_id, &query.query) {
return respond(()); return respond(());
@@ -131,19 +125,15 @@ pub async fn inline_query_handler(bot: Bot, query: InlineQuery) -> Result<(), Re
let query_text = query.query.clone(); let query_text = query.query.clone();
tokio::spawn(async move { tokio::spawn(async move {
tokio::time::sleep(INLINE_DEBOUNCE).await; tokio::time::sleep(INLINE_DEBOUNCE).await;
// Only the user's last query of a typing burst survives: earlier
// timers see the query changed and give up without answering.
if !INLINE_DEBOUNCE_STATE.lock().claim(user_id, &query_text) { if !INLINE_DEBOUNCE_STATE.lock().claim(user_id, &query_text) {
return; return;
} }
let ctx = AppContext::from_statics(&bot); let ctx = AppContext::from_statics(&bot);
match answer_inline_query(&ctx, query).await { match answer_inline_query(&ctx, query).await {
Ok(true) => {} Ok(true) => {}
// The fetch or the answer call failed: release so a repeat of the Ok(false) | Err(_) => {
// same query may retry it. An *empty* answer is a real answer INLINE_DEBOUNCE_STATE.lock().release(user_id, &query_text);
// (`Ok(true)`), so a link whose media Telegram cannot fetch is not }
// re-fetched on every keystroke.
Ok(false) | Err(_) => INLINE_DEBOUNCE_STATE.lock().release(user_id, &query_text),
} }
}); });
respond(()) respond(())
@@ -160,7 +150,8 @@ async fn answer_inline_query(
log::debug!("inline query [key={}]", log_key(&query.query)); log::debug!("inline query [key={}]", log_key(&query.query));
log::trace!("inline query: {}", query.query); log::trace!("inline query: {}", query.query);
let Some(key) = x_media::site::cache_key(&query.query) else { let Some(key) = x_media::site::cache_key(&query.query) else {
return Ok(false); answer(ctx.sender, query.id, Vec::new()).await?;
return Ok(true);
}; };
// A post that was already sent to some chat is answered from the link // A post that was already sent to some chat is answered from the link
// cache: its Telegram file ids make the answer instant, and — unlike a URL // cache: its Telegram file ids make the answer instant, and — unlike a URL
@@ -237,10 +228,17 @@ async fn answer_inline_query(
answer(ctx.sender, query.id, results).await?; answer(ctx.sender, query.id, results).await?;
return Ok(true); return Ok(true);
} }
Ok(None) => {} Ok(None) | Err(_) => {
Err(e) => log::error!("inline fetch [key={}]: {e}", log_key(&query.query)), if let Err(e) = answer(ctx.sender, query.id, Vec::new()).await {
log::error!(
"inline empty answer failed for [key={}]: {e}",
log_key(&query.query)
);
return Err(e);
}
return Ok(true);
}
} }
Ok(false)
} }
/// The caption of an inline answer, from a cached post: the caption that was /// The caption of an inline answer, from a cached post: the caption that was