Lines of
src/telegram.rs
from check-in a044f68fa7
that are changed by the sequence of edits moving toward
check-in 14ef340959:
1: use crate::utils::{
2: Attachment,
3: RE_SPECIAL,
4: };
5:
6: use std::{
7: borrow::Cow,
8: collections::HashMap,
9: fmt::Debug,
10: };
11:
12: use stacked_errors::{
13: Result,
14: StackableErr,
15: };
16: use tgbot::{
17: api::Client,
18: types::{
19: ChatPeerId,
20: InputFile,
21: InputFileReader,
22: InputMediaDocument,
23: MediaGroup,
24: MediaGroupItem,
25: Message,
26: ParseMode::MarkdownV2,
27: SendMediaGroup,
28: SendMessage,
29: SendDocument,
30: },
31: };
32:
33: /// Encodes special HTML entities to prevent them interfering with Telegram HTML
34: pub fn encode (text: &str) -> Cow<'_, str> {
35: RE_SPECIAL.replace_all(text, "\\$1")
36: }
37:
38: #[derive(Debug)]
39: pub struct TelegramTransport {
40: tg: Client,
41: recipients: HashMap<String, ChatPeerId>,
42: pub default: ChatPeerId,
43: }
44:
45: impl TelegramTransport {
46:
a044f68fa7 2025-08-23 47: pub fn new (api_key: String, recipients: HashMap<String, i64>, default: i64) -> Result<TelegramTransport> {
48: let tg = Client::new(api_key)
a044f68fa7 2025-08-23 49: .context("Failed to create API.\n")?;
50: let recipients = recipients.into_iter()
51: .map(|(a, b)| (a, ChatPeerId::from(b))).collect();
52: let default = ChatPeerId::from(default);
53:
54: Ok(TelegramTransport {
55: tg,
56: recipients,
57: default,
58: })
59: }
60:
61: /// Send message to default user, used for debug/log/info purposes
62: pub async fn debug (&self, msg: &str) -> Result<Message> {
63: self.send(&self.default, encode(msg)).await
64: }
65:
66: /// Get recipient by address
67: pub fn get (&self, name: &str) -> Result<&ChatPeerId> {
68: self.recipients.get(name)
69: .with_context(|| format!("Recipient \"{name}\" not found in configuration"))
70: }
71:
72: /// Send message to specified user
73: pub async fn send <S> (&self, to: &ChatPeerId, msg: S) -> Result<Message>
74: where S: Into<String> + Debug{
75: self.tg.execute(
76: SendMessage::new(*to, msg)
77: .with_parse_mode(MarkdownV2)
78: ).await.stack()
79: }
80:
81: /// Send media to specified user
82: pub async fn sendgroup (&self, to: &ChatPeerId, media: Vec<Attachment>, msg: &str) -> Result<()> {
83: if media.len() > 1 {
84: let mut attach = vec![];
85: let mut pos = media.len();
86: for file in media {
87: let mut caption = InputMediaDocument::default();
88: if pos == 1 {
89: caption = caption.with_caption(msg)
90: .with_caption_parse_mode(MarkdownV2);
91: }
92: pos -= 1;
93: attach.push(
94: MediaGroupItem::for_document(
95: InputFile::from(
96: InputFileReader::from(file.data)
97: .with_file_name(file.name)
98: ),
99: caption
100: )
101: );
102: }
103: self.tg.execute(SendMediaGroup::new(*to, MediaGroup::new(attach).stack()?)).await.stack()?;
104: } else {
105: self.tg.execute(
106: SendDocument::new(
107: *to,
108: InputFileReader::from(media[0].data.clone())
109: .with_file_name(media[0].name.clone())
110: ).with_caption(msg)
111: .with_caption_parse_mode(MarkdownV2)
112: ).await.stack()?;
113: }
114: Ok(())
115: }
116: }