feat(inline): answer from the link cache, and put the answer on the trait

Inline results were URL-only: Telegram fetches an inline result's URL itself
and cannot send site headers, so every pixiv item (and every locally encoded
ugoira/bsky MP4) was skipped and such a query answered empty. A post that is
already in the link cache now answers with InlineQueryResultCached* built
from its Telegram file ids — no fetch, no upload, and the hotlink-protected
case simply works. A degraded entry (file ids gone) falls back to URLs, and
there a video with no poster is skipped (Telegram would try to render the
mp4 as its own thumbnail).

The answer call moves onto MediaSender (answer_inline_query, mirroring the
other user-flow methods), which is what makes the path testable at all: the
two new tests drive the cache answer and the degraded/empty answer through
TestStores + MockSender, which recorded nothing about inline before.

The result builders are shared by both paths now (url_result/cached_result +
inline_kind), so the fetch path's behaviour is unchanged.
This commit is contained in:
2026-09-21 20:35:18 +08:00
parent 20a0cf3ea3
commit 3ebc1e4a8f
5 changed files with 390 additions and 74 deletions
+27 -2
View File
@@ -8,8 +8,8 @@ use teloxide::RequestError;
use teloxide::prelude::Requester;
use teloxide::prelude::*;
use teloxide::types::{
CallbackQueryId, ChatAction, ChatId, InlineKeyboardMarkup, InputFile, InputMedia, Message,
MessageId, ParseMode, ReplyParameters,
CallbackQueryId, ChatAction, ChatId, InlineKeyboardMarkup, InlineQueryId, InlineQueryResult,
InputFile, InputMedia, Message, MessageId, ParseMode, ReplyParameters,
};
/// Boxed, `Send` future returned by a [`MediaSender`] method (`async fn` in
@@ -60,6 +60,17 @@ pub trait MediaSender: Send + Sync {
reply_markup: Option<InlineKeyboardMarkup>,
) -> BoxFuture<'_, Result<i64, RequestError>>;
/// Answers an inline query with `results`, cached by Telegram for
/// `cache_time` seconds. An empty `results` answers *empty*, which is a
/// real answer: it stops the client spinning and lets Telegram serve a
/// repeat itself instead of the bot re-running the query.
fn answer_inline_query(
&self,
id: InlineQueryId,
results: Vec<InlineQueryResult>,
cache_time: u32,
) -> BoxFuture<'_, Result<(), RequestError>>;
/// Answers a callback query, optionally with a toast `text` shown to the
/// user who pressed the button.
fn answer_callback_query(
@@ -186,6 +197,20 @@ impl MediaSender for Bot {
})
}
fn answer_inline_query(
&self,
id: InlineQueryId,
results: Vec<InlineQueryResult>,
cache_time: u32,
) -> BoxFuture<'_, Result<(), RequestError>> {
Box::pin(async move {
<Bot as Requester>::answer_inline_query(self, id, results)
.cache_time(cache_time)
.await
.map(|_| ())
})
}
fn answer_callback_query(
&self,
id: CallbackQueryId,