@@ -1,15 +1,119 @@
11use std::path::{Path, PathBuf};
22
33use teloxide::prelude::*;
4−use teloxide::types::InputFile;
4+use teloxide::types::{InlineKeyboardMarkup, InputFile};
5+
6+const CAPTION_LIMIT: usize = 1024;
7+
8+pub async fn send_event_message(
9+ bot: &Bot,
10+ chat: ChatId,
11+ files: &[PathBuf],
12+ text: &str,
13+ keyboard: Option<InlineKeyboardMarkup>,
14+) {
15+ if files.is_empty() {
16+ send_text(bot, chat, text, keyboard).await;
17+ return;
18+ }
19+
20+ let fits_in_caption = text.chars().count() <= CAPTION_LIMIT;
21+ if files.len() == 1 && fits_in_caption {
22+ match send_one_with_caption(bot, chat, &files[0], text, keyboard.clone()).await {
23+ Ok(()) => return,
24+ Err(e) => tracing::warn!(
25+ "failed to send {} with caption: {}; falling back to separate messages",
26+ files[0].display(),
27+ e
28+ ),
29+ }
30+ }
531
6−pub async fn send_files(bot: &Bot, chat: ChatId, files: &[PathBuf]) {
732 for path in files {
833 send_one(bot, chat, path).await;
934 }
35+ send_text(bot, chat, text, keyboard).await;
36+}
37+
38+async fn send_text(bot: &Bot, chat: ChatId, text: &str, keyboard: Option<InlineKeyboardMarkup>) {
39+ let text = if text.trim().is_empty() {
40+ "(пустое сообщение)"
41+ } else {
42+ text
43+ };
44+ let result = match keyboard {
45+ Some(kb) => bot.send_message(chat, text).reply_markup(kb).await.map(|_| ()),
46+ None => bot.send_message(chat, text).await.map(|_| ()),
47+ };
48+ if let Err(e) = result {
49+ tracing::error!("failed to send text to {}: {}", chat, e);
50+ }
51+}
52+
53+async fn send_one_with_caption(
54+ bot: &Bot,
55+ chat: ChatId,
56+ path: &Path,
57+ caption: &str,
58+ keyboard: Option<InlineKeyboardMarkup>,
59+) -> Result<(), teloxide::RequestError> {
60+ let ext = path
61+ .extension()
62+ .and_then(|e| e.to_str())
63+ .unwrap_or("")
64+ .to_ascii_lowercase();
65+
66+ match ext.as_str() {
67+ "jpg" | "jpeg" | "png" | "webp" => {
68+ let req = bot
69+ .send_photo(chat, InputFile::file(path))
70+ .caption(caption);
71+ match keyboard {
72+ Some(kb) => req.reply_markup(kb).await?,
73+ None => req.await?,
74+ };
75+ }
76+ "mp4" => {
77+ let req = bot
78+ .send_video(chat, InputFile::file(path))
79+ .caption(caption);
80+ match keyboard {
81+ Some(kb) => req.reply_markup(kb).await?,
82+ None => req.await?,
83+ };
84+ }
85+ "mp3" | "m4a" => {
86+ let req = bot
87+ .send_audio(chat, InputFile::file(path))
88+ .caption(caption);
89+ match keyboard {
90+ Some(kb) => req.reply_markup(kb).await?,
91+ None => req.await?,
92+ };
93+ }
94+ "ogg" => {
95+ let req = bot
96+ .send_voice(chat, InputFile::file(path))
97+ .caption(caption);
98+ match keyboard {
99+ Some(kb) => req.reply_markup(kb).await?,
100+ None => req.await?,
101+ };
102+ }
103+ _ => {
104+ let req = bot
105+ .send_document(chat, InputFile::file(path))
106+ .caption(caption);
107+ match keyboard {
108+ Some(kb) => req.reply_markup(kb).await?,
109+ None => req.await?,
110+ };
111+ }
112+ }
113+ Ok(())
10114}
11115
12−pub async fn send_one(bot: &Bot, chat: ChatId, path: &Path) {
116+async fn send_one(bot: &Bot, chat: ChatId, path: &Path) {
13117 let ext = path
14118 .extension()
15119 .and_then(|e| e.to_str())