Annotation For src/tg_bot.rs
Logged in as anonymous

Origin for each line in src/tg_bot.rs from check-in 9adc69d514:

9adc69d514 2026-03-25    1: use crate::{
9adc69d514 2026-03-25    2: 	Arc,
9adc69d514 2026-03-25    3: 	Mutex,
9adc69d514 2026-03-25    4: };
9adc69d514 2026-03-25    5: 
9adc69d514 2026-03-25    6: use std::{
9adc69d514 2026-03-25    7: 	borrow::Cow,
9adc69d514 2026-03-25    8: 	collections::HashMap,
9adc69d514 2026-03-25    9: 	fmt,
9adc69d514 2026-03-25   10: };
9adc69d514 2026-03-25   11: 
13265e7697 2026-01-10   12: use serde::{
13265e7697 2026-01-10   13: 	Deserialize,
13265e7697 2026-01-10   14: 	Serialize,
13265e7697 2026-01-10   15: };
9c4f09193a 2026-01-09   16: use stacked_errors::{
9adc69d514 2026-03-25   17: 	bail,
9c4f09193a 2026-01-09   18: 	Result,
9c4f09193a 2026-01-09   19: 	StackableErr,
9c4f09193a 2026-01-09   20: };
9c4f09193a 2026-01-09   21: use tgbot::{
9c4f09193a 2026-01-09   22: 	api::Client,
9c4f09193a 2026-01-09   23: 	types::{
9c4f09193a 2026-01-09   24: 		Bot,
9c4f09193a 2026-01-09   25: 		ChatPeerId,
9c4f09193a 2026-01-09   26: 		GetBot,
13265e7697 2026-01-10   27: 		InlineKeyboardButton,
13265e7697 2026-01-10   28: 		InlineKeyboardMarkup,
9c4f09193a 2026-01-09   29: 		Message,
9c4f09193a 2026-01-09   30: 		ParseMode,
9c4f09193a 2026-01-09   31: 		SendMessage,
9c4f09193a 2026-01-09   32: 	},
9c4f09193a 2026-01-09   33: };
13265e7697 2026-01-10   34: 
9adc69d514 2026-03-25   35: const CB_VERSION: u8 = 0;
9adc69d514 2026-03-25   36: 
13265e7697 2026-01-10   37: #[derive(Serialize, Deserialize, Debug)]
9adc69d514 2026-03-25   38: pub enum Callback {
9adc69d514 2026-03-25   39: 	// List all feeds (version, name to show, page number)
9adc69d514 2026-03-25   40: 	List(u8, String, u8),
9adc69d514 2026-03-25   41: }
9adc69d514 2026-03-25   42: 
9adc69d514 2026-03-25   43: impl Callback {
9adc69d514 2026-03-25   44: 	pub fn list (text: &str, page: u8) -> Callback {
9adc69d514 2026-03-25   45: 		Callback::List(CB_VERSION, text.to_owned(), page)
9adc69d514 2026-03-25   46: 	}
9adc69d514 2026-03-25   47: 
9adc69d514 2026-03-25   48: 	fn version (&self) -> u8 {
9adc69d514 2026-03-25   49: 		match self {
9adc69d514 2026-03-25   50: 			Callback::List(version, .. ) => *version,
9adc69d514 2026-03-25   51: 		}
9adc69d514 2026-03-25   52: 	}
9adc69d514 2026-03-25   53: }
9adc69d514 2026-03-25   54: 
9adc69d514 2026-03-25   55: impl fmt::Display for Callback {
9adc69d514 2026-03-25   56: 	fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
9adc69d514 2026-03-25   57: 		f.write_str(&toml::to_string(self).map_err(|_| fmt::Error)?)
9adc69d514 2026-03-25   58: 	}
9adc69d514 2026-03-25   59: }
9adc69d514 2026-03-25   60: 
9adc69d514 2026-03-25   61: /// Produce new Keyboard Markup from current Callback
9adc69d514 2026-03-25   62: pub async fn get_kb (cb: &Callback, feeds: Arc<Mutex<HashMap<i32, String>>>) -> Result<InlineKeyboardMarkup> {
9adc69d514 2026-03-25   63: 	if cb.version() != CB_VERSION {
9adc69d514 2026-03-25   64: 		bail!("Wrong callback version.");
9adc69d514 2026-03-25   65: 	}
9adc69d514 2026-03-25   66: 	let mark = match cb {
9adc69d514 2026-03-25   67: 		Callback::List(_, name, page) => {
9adc69d514 2026-03-25   68: 			let mut kb = vec![];
9adc69d514 2026-03-25   69: 			let feeds = feeds.lock_arc().await;
9adc69d514 2026-03-25   70: 			let long = feeds.len() > 6;
9adc69d514 2026-03-25   71: 			let (start, end) = if long {
9adc69d514 2026-03-25   72: 				(page * 5, 5 + page * 5)
9adc69d514 2026-03-25   73: 			} else {
9adc69d514 2026-03-25   74: 				(0, 6)
9adc69d514 2026-03-25   75: 			};
9adc69d514 2026-03-25   76: 			let mut i = 0;
9adc69d514 2026-03-25   77: 			for (id, name) in feeds.iter() {
9adc69d514 2026-03-25   78: 				if i < start { continue }
9adc69d514 2026-03-25   79: 				if i > end { break }
9adc69d514 2026-03-25   80: 				i += 1;
9adc69d514 2026-03-25   81: 				kb.push(vec![
9adc69d514 2026-03-25   82: 					InlineKeyboardButton::for_callback_data(
9adc69d514 2026-03-25   83: 						format!("{}. {}", id, name),
9adc69d514 2026-03-25   84: 						Callback::list("xxx", *page).to_string()), // XXX edit
9adc69d514 2026-03-25   85: 				])
9adc69d514 2026-03-25   86: 			}
9adc69d514 2026-03-25   87: 			if name.is_empty() {
9adc69d514 2026-03-25   88: 				// no name - reverting to pages, any unknown number means last page
9adc69d514 2026-03-25   89: 				kb.push(vec![
9adc69d514 2026-03-25   90: 					InlineKeyboardButton::for_callback_data("1",
9adc69d514 2026-03-25   91: 						Callback::list("xxx", 0).to_string()),
9adc69d514 2026-03-25   92: 				])
9adc69d514 2026-03-25   93: 			} else {
9adc69d514 2026-03-25   94: 				kb.push(vec![
9adc69d514 2026-03-25   95: 					InlineKeyboardButton::for_callback_data("1",
9adc69d514 2026-03-25   96: 						Callback::list("xxx", 0).to_string()),
9adc69d514 2026-03-25   97: 				])
9adc69d514 2026-03-25   98: 			}
9adc69d514 2026-03-25   99: 			if long {
9adc69d514 2026-03-25  100: 				kb.push(vec![
9adc69d514 2026-03-25  101: 					InlineKeyboardButton::for_callback_data("<<",
9adc69d514 2026-03-25  102: 						Callback::list("", if *page == 0 { *page } else { page - 1 } ).to_string()),
9adc69d514 2026-03-25  103: 					InlineKeyboardButton::for_callback_data(">>",
9adc69d514 2026-03-25  104: 						Callback::list("", page + 1).to_string()),
9adc69d514 2026-03-25  105: 				]);
9adc69d514 2026-03-25  106: 			}
9adc69d514 2026-03-25  107: 			InlineKeyboardMarkup::from(kb)
9adc69d514 2026-03-25  108: 		},
9adc69d514 2026-03-25  109: 	};
9adc69d514 2026-03-25  110: 	Ok(mark)
9adc69d514 2026-03-25  111: }
9adc69d514 2026-03-25  112: 
9adc69d514 2026-03-25  113: pub enum MyMessage <'a> {
9adc69d514 2026-03-25  114: 	Html { text: Cow<'a, str> },
9adc69d514 2026-03-25  115: 	HtmlTo { text: Cow<'a, str>, to: ChatPeerId },
9adc69d514 2026-03-25  116: 	HtmlToKb { text: Cow<'a, str>, to: ChatPeerId, kb: InlineKeyboardMarkup },
9adc69d514 2026-03-25  117: 	Text { text: Cow<'a, str> },
9adc69d514 2026-03-25  118: 	TextTo { text: Cow<'a, str>, to: ChatPeerId },
13265e7697 2026-01-10  119: }
13265e7697 2026-01-10  120: 
9adc69d514 2026-03-25  121: impl MyMessage <'_> {
9adc69d514 2026-03-25  122: 	pub fn html <'a, S> (text: S) -> MyMessage<'a>
9adc69d514 2026-03-25  123: 	where S: Into<Cow<'a, str>> {
9adc69d514 2026-03-25  124: 		let text = text.into();
9adc69d514 2026-03-25  125: 		MyMessage::Html { text }
9adc69d514 2026-03-25  126: 	}
9adc69d514 2026-03-25  127: 	
9adc69d514 2026-03-25  128: 	pub fn html_to <'a, S> (text: S, to: ChatPeerId) -> MyMessage<'a>
9adc69d514 2026-03-25  129: 	where S: Into<Cow<'a, str>> {
9adc69d514 2026-03-25  130: 		let text = text.into();
9adc69d514 2026-03-25  131: 		MyMessage::HtmlTo { text, to }
9adc69d514 2026-03-25  132: 	}
9adc69d514 2026-03-25  133: 	
9adc69d514 2026-03-25  134: 	pub fn html_to_kb <'a, S> (text: S, to: ChatPeerId, kb: InlineKeyboardMarkup) -> MyMessage<'a>
9adc69d514 2026-03-25  135: 	where S: Into<Cow<'a, str>> {
9adc69d514 2026-03-25  136: 		let text = text.into();
9adc69d514 2026-03-25  137: 		MyMessage::HtmlToKb { text, to, kb }
9adc69d514 2026-03-25  138: 	}
9adc69d514 2026-03-25  139: 	
9adc69d514 2026-03-25  140: 	pub fn text <'a, S> (text: S) -> MyMessage<'a>
9adc69d514 2026-03-25  141: 	where S: Into<Cow<'a, str>> {
9adc69d514 2026-03-25  142: 		let text = text.into();
9adc69d514 2026-03-25  143: 		MyMessage::Text { text }
9adc69d514 2026-03-25  144: 	}
9adc69d514 2026-03-25  145: 	
9adc69d514 2026-03-25  146: 	pub fn text_to <'a, S> (text: S, to: ChatPeerId) -> MyMessage<'a>
9adc69d514 2026-03-25  147: 	where S: Into<Cow<'a, str>> {
9adc69d514 2026-03-25  148: 		let text = text.into();
9adc69d514 2026-03-25  149: 		MyMessage::TextTo { text, to }
9adc69d514 2026-03-25  150: 	}
9adc69d514 2026-03-25  151: 	
9adc69d514 2026-03-25  152: 	fn req (&self, tg: &Tg) -> Result<SendMessage> {
9adc69d514 2026-03-25  153: 		Ok(match self {
9adc69d514 2026-03-25  154: 			MyMessage::Html { text } =>
9adc69d514 2026-03-25  155: 				SendMessage::new(tg.owner, text.as_ref())
9adc69d514 2026-03-25  156: 					.with_parse_mode(ParseMode::Html),
9adc69d514 2026-03-25  157: 			MyMessage::HtmlTo { text, to } =>
9adc69d514 2026-03-25  158: 				SendMessage::new(*to, text.as_ref())
9adc69d514 2026-03-25  159: 					.with_parse_mode(ParseMode::Html),
9adc69d514 2026-03-25  160: 			MyMessage::HtmlToKb { text, to, kb } =>
9adc69d514 2026-03-25  161: 				SendMessage::new(*to, text.as_ref())
9adc69d514 2026-03-25  162: 					.with_parse_mode(ParseMode::Html)
9adc69d514 2026-03-25  163: 					.with_reply_markup(kb.clone()),
9adc69d514 2026-03-25  164: 			MyMessage::Text { text } =>
9adc69d514 2026-03-25  165: 				SendMessage::new(tg.owner, text.as_ref())
9adc69d514 2026-03-25  166: 					.with_parse_mode(ParseMode::MarkdownV2),
9adc69d514 2026-03-25  167: 			MyMessage::TextTo { text, to } =>
9adc69d514 2026-03-25  168: 				SendMessage::new(*to, text.as_ref())
9adc69d514 2026-03-25  169: 					.with_parse_mode(ParseMode::MarkdownV2),
9adc69d514 2026-03-25  170: 		})
9adc69d514 2026-03-25  171: 	}
13265e7697 2026-01-10  172: }
9c4f09193a 2026-01-09  173: 
9c4f09193a 2026-01-09  174: #[derive(Clone)]
9c4f09193a 2026-01-09  175: pub struct Tg {
9c4f09193a 2026-01-09  176: 	pub me: Bot,
9c4f09193a 2026-01-09  177: 	pub owner: ChatPeerId,
9c4f09193a 2026-01-09  178: 	pub client: Client,
9c4f09193a 2026-01-09  179: }
9c4f09193a 2026-01-09  180: 
9c4f09193a 2026-01-09  181: impl Tg {
fabcca1eaf 2026-01-09  182: 	/// Construct a new `Tg` instance from configuration.
fabcca1eaf 2026-01-09  183: 	///
fabcca1eaf 2026-01-09  184: 	/// The `settings` must provide the following keys:
fabcca1eaf 2026-01-09  185: 	///  - `"api_key"` (string),
fabcca1eaf 2026-01-09  186: 	///  - `"owner"` (integer chat id),
fabcca1eaf 2026-01-09  187: 	///  - `"api_gateway"` (string).
fabcca1eaf 2026-01-09  188: 	///
fabcca1eaf 2026-01-09  189: 	/// The function initialises the client, configures the gateway and fetches the bot identity
fabcca1eaf 2026-01-09  190: 	/// before returning the constructed `Tg`.
9c4f09193a 2026-01-09  191: 	pub async fn new (settings: &config::Config) -> Result<Tg> {
9c4f09193a 2026-01-09  192: 		let api_key = settings.get_string("api_key").stack()?;
9c4f09193a 2026-01-09  193: 
9c4f09193a 2026-01-09  194: 		let owner = ChatPeerId::from(settings.get_int("owner").stack()?);
9c4f09193a 2026-01-09  195: 		let client = Client::new(&api_key).stack()?
9c4f09193a 2026-01-09  196: 			.with_host(settings.get_string("api_gateway").stack()?)
9c4f09193a 2026-01-09  197: 			.with_max_retries(0);
9c4f09193a 2026-01-09  198: 		let me = client.execute(GetBot).await.stack()?;
9c4f09193a 2026-01-09  199: 		Ok(Tg {
9c4f09193a 2026-01-09  200: 			me,
9c4f09193a 2026-01-09  201: 			owner,
9c4f09193a 2026-01-09  202: 			client,
9c4f09193a 2026-01-09  203: 		})
9c4f09193a 2026-01-09  204: 	}
9c4f09193a 2026-01-09  205: 
fabcca1eaf 2026-01-09  206: 	/// Send a text message to a chat, using an optional target and parse mode.
fabcca1eaf 2026-01-09  207: 	///
fabcca1eaf 2026-01-09  208: 	/// # Returns
fabcca1eaf 2026-01-09  209: 	/// The sent `Message` on success.
9adc69d514 2026-03-25  210: 	pub async fn send (&self, msg: MyMessage<'_>) -> Result<Message> {
9adc69d514 2026-03-25  211: 		self.client.execute(msg.req(self)?).await.stack()
fabcca1eaf 2026-01-09  212: 	}
fabcca1eaf 2026-01-09  213: 
fabcca1eaf 2026-01-09  214: 	/// Create a copy of this `Tg` with the owner replaced by the given chat ID.
fabcca1eaf 2026-01-09  215: 	///
fabcca1eaf 2026-01-09  216: 	/// # Parameters
fabcca1eaf 2026-01-09  217: 	/// - `owner`: The Telegram chat identifier to set as the new owner (expressed as an `i64`).
fabcca1eaf 2026-01-09  218: 	///
fabcca1eaf 2026-01-09  219: 	/// # Returns
fabcca1eaf 2026-01-09  220: 	/// A new `Tg` instance identical to the original except its `owner` field is set to the provided chat ID.
fabcca1eaf 2026-01-09  221: 	pub fn with_owner <O>(&self, owner: O) -> Tg
fabcca1eaf 2026-01-09  222: 	where O: Into<i64> {
fabcca1eaf 2026-01-09  223: 		Tg {
fabcca1eaf 2026-01-09  224: 			owner: ChatPeerId::from(owner.into()),
9c4f09193a 2026-01-09  225: 			..self.clone()
9c4f09193a 2026-01-09  226: 		}
9c4f09193a 2026-01-09  227: 	}
9c4f09193a 2026-01-09  228: }