feat(telegram): add forum topic support
Implement Telegram Forum (topic) support to allow the bot to respond in the same topic where it was mentioned/called. Changes: - parse_update_message(): Extract message_thread_id and format reply_target as 'chat_id:thread_id' - send(): Parse recipient to extract chat_id and optional thread_id - All send methods now pass thread_id parameter and include message_thread_id in API requests - Added test for forum topic message parsing This ensures bot replies stay within the same forum topic thread.
This commit is contained in:
parent
3467d34596
commit
36062fb1c2
1 changed files with 174 additions and 36 deletions
|
|
@ -562,10 +562,23 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
.and_then(serde_json::Value::as_i64)
|
||||
.unwrap_or(0);
|
||||
|
||||
// Extract thread/topic ID for forum support
|
||||
let thread_id = message
|
||||
.get("message_thread_id")
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
.map(|id| id.to_string());
|
||||
|
||||
// reply_target: chat_id or chat_id:thread_id format
|
||||
let reply_target = if let Some(tid) = thread_id {
|
||||
format!("{}:{}", chat_id, tid)
|
||||
} else {
|
||||
chat_id.clone()
|
||||
};
|
||||
|
||||
Some(ChannelMessage {
|
||||
id: format!("telegram_{chat_id}_{message_id}"),
|
||||
sender: sender_identity,
|
||||
reply_target: chat_id,
|
||||
reply_target,
|
||||
content: text.to_string(),
|
||||
channel: "telegram".to_string(),
|
||||
timestamp: std::time::SystemTime::now()
|
||||
|
|
@ -575,7 +588,12 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
})
|
||||
}
|
||||
|
||||
async fn send_text_chunks(&self, message: &str, chat_id: &str) -> anyhow::Result<()> {
|
||||
async fn send_text_chunks(
|
||||
&self,
|
||||
message: &str,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
let chunks = split_message_for_telegram(message);
|
||||
|
||||
for (index, chunk) in chunks.iter().enumerate() {
|
||||
|
|
@ -591,12 +609,17 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
chunk.to_string()
|
||||
};
|
||||
|
||||
let markdown_body = serde_json::json!({
|
||||
let mut markdown_body = serde_json::json!({
|
||||
"chat_id": chat_id,
|
||||
"text": text,
|
||||
"parse_mode": "Markdown"
|
||||
});
|
||||
|
||||
// Add message_thread_id for forum topic support
|
||||
if let Some(tid) = thread_id {
|
||||
markdown_body["message_thread_id"] = serde_json::Value::String(tid.to_string());
|
||||
}
|
||||
|
||||
let markdown_resp = self
|
||||
.client
|
||||
.post(self.api_url("sendMessage"))
|
||||
|
|
@ -618,10 +641,15 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
"Telegram sendMessage with Markdown failed; retrying without parse_mode"
|
||||
);
|
||||
|
||||
let plain_body = serde_json::json!({
|
||||
let mut plain_body = serde_json::json!({
|
||||
"chat_id": chat_id,
|
||||
"text": text,
|
||||
});
|
||||
|
||||
// Add message_thread_id for forum topic support
|
||||
if let Some(tid) = thread_id {
|
||||
plain_body["message_thread_id"] = serde_json::Value::String(tid.to_string());
|
||||
}
|
||||
let plain_resp = self
|
||||
.client
|
||||
.post(self.api_url("sendMessage"))
|
||||
|
|
@ -654,6 +682,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
method: &str,
|
||||
media_field: &str,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
url: &str,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
@ -662,6 +691,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
});
|
||||
body[media_field] = serde_json::Value::String(url.to_string());
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
body["message_thread_id"] = serde_json::Value::String(tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
body["caption"] = serde_json::Value::String(cap.to_string());
|
||||
}
|
||||
|
|
@ -685,6 +718,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
async fn send_attachment(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
attachment: &TelegramAttachment,
|
||||
) -> anyhow::Result<()> {
|
||||
let target = attachment.target.trim();
|
||||
|
|
@ -692,19 +726,24 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
if is_http_url(target) {
|
||||
return match attachment.kind {
|
||||
TelegramAttachmentKind::Image => {
|
||||
self.send_photo_by_url(chat_id, target, None).await
|
||||
self.send_photo_by_url(chat_id, thread_id, target, None)
|
||||
.await
|
||||
}
|
||||
TelegramAttachmentKind::Document => {
|
||||
self.send_document_by_url(chat_id, target, None).await
|
||||
self.send_document_by_url(chat_id, thread_id, target, None)
|
||||
.await
|
||||
}
|
||||
TelegramAttachmentKind::Video => {
|
||||
self.send_video_by_url(chat_id, target, None).await
|
||||
self.send_video_by_url(chat_id, thread_id, target, None)
|
||||
.await
|
||||
}
|
||||
TelegramAttachmentKind::Audio => {
|
||||
self.send_audio_by_url(chat_id, target, None).await
|
||||
self.send_audio_by_url(chat_id, thread_id, target, None)
|
||||
.await
|
||||
}
|
||||
TelegramAttachmentKind::Voice => {
|
||||
self.send_voice_by_url(chat_id, target, None).await
|
||||
self.send_voice_by_url(chat_id, thread_id, target, None)
|
||||
.await
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -715,11 +754,13 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
match attachment.kind {
|
||||
TelegramAttachmentKind::Image => self.send_photo(chat_id, path, None).await,
|
||||
TelegramAttachmentKind::Document => self.send_document(chat_id, path, None).await,
|
||||
TelegramAttachmentKind::Video => self.send_video(chat_id, path, None).await,
|
||||
TelegramAttachmentKind::Audio => self.send_audio(chat_id, path, None).await,
|
||||
TelegramAttachmentKind::Voice => self.send_voice(chat_id, path, None).await,
|
||||
TelegramAttachmentKind::Image => self.send_photo(chat_id, thread_id, path, None).await,
|
||||
TelegramAttachmentKind::Document => {
|
||||
self.send_document(chat_id, thread_id, path, None).await
|
||||
}
|
||||
TelegramAttachmentKind::Video => self.send_video(chat_id, thread_id, path, None).await,
|
||||
TelegramAttachmentKind::Audio => self.send_audio(chat_id, thread_id, path, None).await,
|
||||
TelegramAttachmentKind::Voice => self.send_voice(chat_id, thread_id, path, None).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -727,6 +768,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_document(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
file_path: &Path,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
@ -742,6 +784,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
.text("chat_id", chat_id.to_string())
|
||||
.part("document", part);
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
form = form.text("message_thread_id", tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
form = form.text("caption", cap.to_string());
|
||||
}
|
||||
|
|
@ -766,6 +812,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_document_bytes(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
file_bytes: Vec<u8>,
|
||||
file_name: &str,
|
||||
caption: Option<&str>,
|
||||
|
|
@ -776,6 +823,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
.text("chat_id", chat_id.to_string())
|
||||
.part("document", part);
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
form = form.text("message_thread_id", tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
form = form.text("caption", cap.to_string());
|
||||
}
|
||||
|
|
@ -800,6 +851,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_photo(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
file_path: &Path,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
@ -815,6 +867,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
.text("chat_id", chat_id.to_string())
|
||||
.part("photo", part);
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
form = form.text("message_thread_id", tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
form = form.text("caption", cap.to_string());
|
||||
}
|
||||
|
|
@ -839,6 +895,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_photo_bytes(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
file_bytes: Vec<u8>,
|
||||
file_name: &str,
|
||||
caption: Option<&str>,
|
||||
|
|
@ -849,6 +906,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
.text("chat_id", chat_id.to_string())
|
||||
.part("photo", part);
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
form = form.text("message_thread_id", tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
form = form.text("caption", cap.to_string());
|
||||
}
|
||||
|
|
@ -873,6 +934,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_video(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
file_path: &Path,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
@ -888,6 +950,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
.text("chat_id", chat_id.to_string())
|
||||
.part("video", part);
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
form = form.text("message_thread_id", tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
form = form.text("caption", cap.to_string());
|
||||
}
|
||||
|
|
@ -912,6 +978,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_audio(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
file_path: &Path,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
@ -927,6 +994,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
.text("chat_id", chat_id.to_string())
|
||||
.part("audio", part);
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
form = form.text("message_thread_id", tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
form = form.text("caption", cap.to_string());
|
||||
}
|
||||
|
|
@ -951,6 +1022,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_voice(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
file_path: &Path,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
@ -966,6 +1038,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
.text("chat_id", chat_id.to_string())
|
||||
.part("voice", part);
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
form = form.text("message_thread_id", tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
form = form.text("caption", cap.to_string());
|
||||
}
|
||||
|
|
@ -990,6 +1066,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_document_by_url(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
url: &str,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
@ -998,6 +1075,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
"document": url
|
||||
});
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
body["message_thread_id"] = serde_json::Value::String(tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
body["caption"] = serde_json::Value::String(cap.to_string());
|
||||
}
|
||||
|
|
@ -1022,6 +1103,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_photo_by_url(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
url: &str,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
@ -1030,6 +1112,10 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
"photo": url
|
||||
});
|
||||
|
||||
if let Some(tid) = thread_id {
|
||||
body["message_thread_id"] = serde_json::Value::String(tid.to_string());
|
||||
}
|
||||
|
||||
if let Some(cap) = caption {
|
||||
body["caption"] = serde_json::Value::String(cap.to_string());
|
||||
}
|
||||
|
|
@ -1054,10 +1140,11 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_video_by_url(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
url: &str,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.send_media_by_url("sendVideo", "video", chat_id, url, caption)
|
||||
self.send_media_by_url("sendVideo", "video", chat_id, thread_id, url, caption)
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -1065,10 +1152,11 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_audio_by_url(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
url: &str,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.send_media_by_url("sendAudio", "audio", chat_id, url, caption)
|
||||
self.send_media_by_url("sendAudio", "audio", chat_id, thread_id, url, caption)
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -1076,10 +1164,11 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
pub async fn send_voice_by_url(
|
||||
&self,
|
||||
chat_id: &str,
|
||||
thread_id: Option<&str>,
|
||||
url: &str,
|
||||
caption: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.send_media_by_url("sendVoice", "voice", chat_id, url, caption)
|
||||
self.send_media_by_url("sendVoice", "voice", chat_id, thread_id, url, caption)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
@ -1094,28 +1183,34 @@ impl Channel for TelegramChannel {
|
|||
// Strip tool_call tags before processing to prevent Markdown parsing failures
|
||||
let content = strip_tool_call_tags(&message.content);
|
||||
|
||||
// Parse recipient: "chat_id" or "chat_id:thread_id" format
|
||||
let (chat_id, thread_id) = match message.recipient.split_once(':') {
|
||||
Some((chat, thread)) => (chat, Some(thread)),
|
||||
None => (message.recipient.as_str(), None),
|
||||
};
|
||||
|
||||
let (text_without_markers, attachments) = parse_attachment_markers(&content);
|
||||
|
||||
if !attachments.is_empty() {
|
||||
if !text_without_markers.is_empty() {
|
||||
self.send_text_chunks(&text_without_markers, &message.recipient)
|
||||
self.send_text_chunks(&text_without_markers, chat_id, thread_id)
|
||||
.await?;
|
||||
}
|
||||
|
||||
for attachment in &attachments {
|
||||
self.send_attachment(&message.recipient, attachment).await?;
|
||||
self.send_attachment(chat_id, thread_id, attachment).await?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(attachment) = parse_path_only_attachment(&content) {
|
||||
self.send_attachment(&message.recipient, &attachment)
|
||||
self.send_attachment(chat_id, thread_id, &attachment)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.send_text_chunks(&content, &message.recipient).await
|
||||
self.send_text_chunks(&content, chat_id, thread_id).await
|
||||
}
|
||||
|
||||
async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) -> anyhow::Result<()> {
|
||||
|
|
@ -1453,6 +1548,35 @@ mod tests {
|
|||
assert_eq!(msg.reply_target, "12345");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_update_message_extracts_thread_id_for_forum_topic() {
|
||||
let ch = TelegramChannel::new("token".into(), vec!["*".into()]);
|
||||
let update = serde_json::json!({
|
||||
"update_id": 3,
|
||||
"message": {
|
||||
"message_id": 42,
|
||||
"text": "hello from topic",
|
||||
"from": {
|
||||
"id": 555,
|
||||
"username": "alice"
|
||||
},
|
||||
"chat": {
|
||||
"id": -100_200_300
|
||||
},
|
||||
"message_thread_id": 789
|
||||
}
|
||||
});
|
||||
|
||||
let msg = ch
|
||||
.parse_update_message(&update)
|
||||
.expect("message with thread_id should parse");
|
||||
|
||||
assert_eq!(msg.sender, "alice");
|
||||
assert_eq!(msg.reply_target, "-100200300:789");
|
||||
assert_eq!(msg.content, "hello from topic");
|
||||
assert_eq!(msg.id, "telegram_-100200300_42");
|
||||
}
|
||||
|
||||
// ── File sending API URL tests ──────────────────────────────────
|
||||
|
||||
#[test]
|
||||
|
|
@ -1511,7 +1635,7 @@ mod tests {
|
|||
// The actual API call will fail (no real server), but we verify the method exists
|
||||
// and handles the input correctly up to the network call
|
||||
let result = ch
|
||||
.send_document_bytes("123456", file_bytes, "test.txt", Some("Test caption"))
|
||||
.send_document_bytes("123456", None, file_bytes, "test.txt", Some("Test caption"))
|
||||
.await;
|
||||
|
||||
// Should fail with network error, not a panic or type error
|
||||
|
|
@ -1531,7 +1655,7 @@ mod tests {
|
|||
let file_bytes = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
||||
|
||||
let result = ch
|
||||
.send_photo_bytes("123456", file_bytes, "test.png", None)
|
||||
.send_photo_bytes("123456", None, file_bytes, "test.png", None)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
|
|
@ -1542,7 +1666,12 @@ mod tests {
|
|||
let ch = TelegramChannel::new("fake-token".into(), vec!["*".into()]);
|
||||
|
||||
let result = ch
|
||||
.send_document_by_url("123456", "https://example.com/file.pdf", Some("PDF doc"))
|
||||
.send_document_by_url(
|
||||
"123456",
|
||||
None,
|
||||
"https://example.com/file.pdf",
|
||||
Some("PDF doc"),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
|
|
@ -1553,7 +1682,7 @@ mod tests {
|
|||
let ch = TelegramChannel::new("fake-token".into(), vec!["*".into()]);
|
||||
|
||||
let result = ch
|
||||
.send_photo_by_url("123456", "https://example.com/image.jpg", None)
|
||||
.send_photo_by_url("123456", None, "https://example.com/image.jpg", None)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
|
|
@ -1566,7 +1695,7 @@ mod tests {
|
|||
let ch = TelegramChannel::new("fake-token".into(), vec!["*".into()]);
|
||||
let path = Path::new("/nonexistent/path/to/file.txt");
|
||||
|
||||
let result = ch.send_document("123456", path, None).await;
|
||||
let result = ch.send_document("123456", None, path, None).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err().to_string();
|
||||
|
|
@ -1582,7 +1711,7 @@ mod tests {
|
|||
let ch = TelegramChannel::new("fake-token".into(), vec!["*".into()]);
|
||||
let path = Path::new("/nonexistent/path/to/photo.jpg");
|
||||
|
||||
let result = ch.send_photo("123456", path, None).await;
|
||||
let result = ch.send_photo("123456", None, path, None).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
|
@ -1592,7 +1721,7 @@ mod tests {
|
|||
let ch = TelegramChannel::new("fake-token".into(), vec!["*".into()]);
|
||||
let path = Path::new("/nonexistent/path/to/video.mp4");
|
||||
|
||||
let result = ch.send_video("123456", path, None).await;
|
||||
let result = ch.send_video("123456", None, path, None).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
|
@ -1602,7 +1731,7 @@ mod tests {
|
|||
let ch = TelegramChannel::new("fake-token".into(), vec!["*".into()]);
|
||||
let path = Path::new("/nonexistent/path/to/audio.mp3");
|
||||
|
||||
let result = ch.send_audio("123456", path, None).await;
|
||||
let result = ch.send_audio("123456", None, path, None).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
|
@ -1612,7 +1741,7 @@ mod tests {
|
|||
let ch = TelegramChannel::new("fake-token".into(), vec!["*".into()]);
|
||||
let path = Path::new("/nonexistent/path/to/voice.ogg");
|
||||
|
||||
let result = ch.send_voice("123456", path, None).await;
|
||||
let result = ch.send_voice("123456", None, path, None).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
|
@ -1702,13 +1831,19 @@ mod tests {
|
|||
|
||||
// With caption
|
||||
let result = ch
|
||||
.send_document_bytes("123456", file_bytes.clone(), "test.txt", Some("My caption"))
|
||||
.send_document_bytes(
|
||||
"123456",
|
||||
None,
|
||||
file_bytes.clone(),
|
||||
"test.txt",
|
||||
Some("My caption"),
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_err()); // Network error expected
|
||||
|
||||
// Without caption
|
||||
let result = ch
|
||||
.send_document_bytes("123456", file_bytes, "test.txt", None)
|
||||
.send_document_bytes("123456", None, file_bytes, "test.txt", None)
|
||||
.await;
|
||||
assert!(result.is_err()); // Network error expected
|
||||
}
|
||||
|
|
@ -1722,6 +1857,7 @@ mod tests {
|
|||
let result = ch
|
||||
.send_photo_bytes(
|
||||
"123456",
|
||||
None,
|
||||
file_bytes.clone(),
|
||||
"test.png",
|
||||
Some("Photo caption"),
|
||||
|
|
@ -1731,7 +1867,7 @@ mod tests {
|
|||
|
||||
// Without caption
|
||||
let result = ch
|
||||
.send_photo_bytes("123456", file_bytes, "test.png", None)
|
||||
.send_photo_bytes("123456", None, file_bytes, "test.png", None)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
|
@ -1744,7 +1880,7 @@ mod tests {
|
|||
let file_bytes: Vec<u8> = vec![];
|
||||
|
||||
let result = ch
|
||||
.send_document_bytes("123456", file_bytes, "empty.txt", None)
|
||||
.send_document_bytes("123456", None, file_bytes, "empty.txt", None)
|
||||
.await;
|
||||
|
||||
// Should not panic, will fail at API level
|
||||
|
|
@ -1756,7 +1892,9 @@ mod tests {
|
|||
let ch = TelegramChannel::new("fake-token".into(), vec!["*".into()]);
|
||||
let file_bytes = b"content".to_vec();
|
||||
|
||||
let result = ch.send_document_bytes("123456", file_bytes, "", None).await;
|
||||
let result = ch
|
||||
.send_document_bytes("123456", None, file_bytes, "", None)
|
||||
.await;
|
||||
|
||||
// Should not panic
|
||||
assert!(result.is_err());
|
||||
|
|
@ -1768,7 +1906,7 @@ mod tests {
|
|||
let file_bytes = b"content".to_vec();
|
||||
|
||||
let result = ch
|
||||
.send_document_bytes("", file_bytes, "test.txt", None)
|
||||
.send_document_bytes("", None, file_bytes, "test.txt", None)
|
||||
.await;
|
||||
|
||||
// Should not panic
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue