Annotation For src/tg_bot.rs
Logged in as anonymous

Lines of src/tg_bot.rs from check-in 9adc69d514 that are changed by the sequence of edits moving toward check-in 374eadef45:

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