Lines of
src/telegram.rs
from check-in c996f5c871
that are changed by the sequence of edits moving toward
check-in aaa78fed23:
1: use crate::utils::{
2: Attachment,
3: validate,
4: };
5:
6: use std::{
7: collections::HashMap,
8: fmt::Debug,
9: };
10:
11: use stacked_errors::{
12: Result,
13: StackableErr,
14: };
15: use tgbot::{
16: api::Client,
17: types::{
18: ChatPeerId,
19: InputFile,
20: InputFileReader,
21: InputMediaDocument,
22: MediaGroup,
23: MediaGroupItem,
24: Message,
25: ParseMode::Html,
26: SendMediaGroup,
27: SendMessage,
28: SendDocument,
29: },
30: };
31:
32: #[derive(Debug)]
33: pub struct TelegramTransport {
34: tg: Client,
35: recipients: HashMap<String, ChatPeerId>,
36: pub default: ChatPeerId,
37: }
38:
39: impl TelegramTransport {
c996f5c871 2026-01-12 40: /// Creates new TelegramTransport object.
41: pub fn new (api_key: String, recipients: HashMap<String, i64>, settings: &config::Config) -> Result<TelegramTransport> {
42: let default = settings.get_int("default")
43: .context("[smtp2tg.toml] missing \"default\" recipient.\n")?;
44: let api_gateway = settings.get_string("api_gateway")
45: .context("[smtp2tg.toml] missing \"api_gateway\" destination.\n")?;
46:
47: let tg = Client::new(api_key)
48: .context("Failed to create API.\n")?
49: .with_host(api_gateway);
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:
c996f5c871 2026-01-12 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, format!("<pre>{}</pre>", validate(msg).stack()?)).await
64: }
65:
c996f5c871 2026-01-12 66: /// Get recipient by address
67: pub fn get (&self, name: &str) -> Result<&ChatPeerId> {
1723b63d69 2026-08-01 68: self.recipients.get(name)
69: .with_context(|| format!("Recipient \"{name}\" not found in configuration"))
70: }
71:
c996f5c871 2026-01-12 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(Html)
78: ).await.stack()
79: }
80:
c996f5c871 2026-01-12 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(Html);
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(Html)
112: ).await.stack()?;
113: }
114: Ok(())
115: }
116: }