mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-10-27 04:52:06 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
use super::*;
|
||||
use parking_lot::Mutex;
|
||||
use teloxide::types::InlineQueryResult;
|
||||
|
||||
/// One scripted outcome, consumed front-to-back; the last entry repeats
|
||||
/// for further calls of the same method kind.
|
||||
@@ -233,11 +234,29 @@ pub(crate) struct MockSender {
|
||||
/// What each `send_animation` handed Telegram: a URL or a file id as that
|
||||
/// string, an upload as `attach://<id>`.
|
||||
animation_files: Mutex<Vec<String>>,
|
||||
/// What every `answer_inline_query` answered with, one entry per result:
|
||||
/// `cached_photo:<file id>`, `photo:<url>`, and so on. An answer with no
|
||||
/// results is recorded as an empty inner vec.
|
||||
inline_answers: Mutex<Vec<Vec<String>>>,
|
||||
/// Builds the error every `*Err` outcome returns (RequestError is not
|
||||
/// cloneable, so the factory recreates it per call).
|
||||
error: Box<dyn Fn() -> RequestError + Send + Sync>,
|
||||
}
|
||||
|
||||
/// A one-string description of an inline result: the kind plus the file id it
|
||||
/// is served from, or the URL it points Telegram at.
|
||||
fn inline_result_tag(result: &InlineQueryResult) -> String {
|
||||
match result {
|
||||
InlineQueryResult::CachedPhoto(r) => format!("cached_photo:{}", r.photo_file_id.0),
|
||||
InlineQueryResult::CachedVideo(r) => format!("cached_video:{}", r.video_file_id.0),
|
||||
InlineQueryResult::CachedMpeg4Gif(r) => format!("cached_gif:{}", r.mpeg4_file_id.0),
|
||||
InlineQueryResult::Photo(r) => format!("photo:{}", r.photo_url),
|
||||
InlineQueryResult::Video(r) => format!("video:{}", r.video_url),
|
||||
InlineQueryResult::Mpeg4Gif(r) => format!("gif:{}", r.mpeg4_url),
|
||||
other => format!("{other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The smallest `Message` the send paths accept, for the outcomes that must
|
||||
/// report one (`send_animation` reads its id, and its media for the cache).
|
||||
pub(crate) fn mock_message(id: i64) -> Message {
|
||||
@@ -266,6 +285,7 @@ impl MockSender {
|
||||
answers: Mutex::new(Vec::new()),
|
||||
edited_texts: Mutex::new(Vec::new()),
|
||||
animation_files: Mutex::new(Vec::new()),
|
||||
inline_answers: Mutex::new(Vec::new()),
|
||||
error: Box::new(error),
|
||||
}
|
||||
}
|
||||
@@ -301,6 +321,11 @@ impl MockSender {
|
||||
self.animation_files.lock().clone()
|
||||
}
|
||||
|
||||
/// What every `answer_inline_query` answered with, in call order.
|
||||
pub(crate) fn inline_answers(&self) -> Vec<Vec<String>> {
|
||||
self.inline_answers.lock().clone()
|
||||
}
|
||||
|
||||
fn next(&self, kind: &'static str) -> Outcome {
|
||||
self.calls.lock().push(kind);
|
||||
let script = self.script.lock();
|
||||
@@ -403,6 +428,26 @@ impl MediaSender for MockSender {
|
||||
})
|
||||
}
|
||||
|
||||
fn answer_inline_query(
|
||||
&self,
|
||||
_id: InlineQueryId,
|
||||
results: Vec<InlineQueryResult>,
|
||||
cache_time: u32,
|
||||
) -> BoxFuture<'_, Result<(), RequestError>> {
|
||||
// Records what the answer was made of, so a test can tell a cached
|
||||
// (file-id) result from a URL one. Always succeeds: the debounce's
|
||||
// release path is covered by `DebounceStates` directly.
|
||||
assert_eq!(
|
||||
cache_time, 300,
|
||||
"the inline cache window is what the tests pin"
|
||||
);
|
||||
self.calls.lock().push("answer_inline_query");
|
||||
self.inline_answers
|
||||
.lock()
|
||||
.push(results.iter().map(inline_result_tag).collect());
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
|
||||
fn answer_callback_query(
|
||||
&self,
|
||||
_id: CallbackQueryId,
|
||||
|
||||
Reference in New Issue
Block a user