diff --git a/src/main.rs b/src/main.rs index 27f172d..d035b06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,8 @@ use tokio::signal::unix::{signal, SignalKind}; const BOT_URL: &str = "https://api.telegram.org/bot"; /// Command to search for in a message text const COMMAND: &str = "/xkcd"; +/// Length of the command substring +const CMD_LEN: usize = COMMAND.len(); /// Long polling timeout in seconds const POLL_TIMEOUT: &str = "300"; /// Update types to receive (filtered out on tg backend) @@ -58,11 +60,13 @@ struct TgChat { // is_forum: bool, } -/// XKCD.com API comic info schema +/// XKCD API comic info schema #[derive(Debug, Deserialize)] struct XkcdInfo { - alt: String, - img: String, + #[serde(alias = "text")] // xkcd.ru + alt: String, // xkcd.com + #[serde(alias = "image")] // xkcd.ru + img: String, // xkcd.com } #[tokio::main] @@ -122,30 +126,43 @@ async fn handler(cfg: &Config, s: &mut State) -> Result<(), reqwest::Error> { .await? .result; - for u in &mut updates { - let Some(mut msg) = u.message.take() else { - continue; - }; - - let Some(text) = msg.text.take() else { - continue; - }; - - if !text.starts_with(COMMAND) { - continue; - } - - let Some(comic_id) = text - .split_whitespace() - .skip(1) - .next() - .and_then(|id| id.parse::().ok()) - else { - continue; - }; - - tokio::spawn(send_comic(cfg.clone(), msg, comic_id)); - } + updates + .iter_mut() + .filter_map(|u| { + u.message.take().and_then(|mut msg| { + msg.text.take().and_then(|text| { + // starts with our command + if text.get(..CMD_LEN)? == COMMAND + // check space or @ after cmd name + // to ensure that our command is + // not a part of any other cmd + && [" ", "@"].contains(&text.get(CMD_LEN..CMD_LEN + 1)?) + { + let mut args = text.split_whitespace(); + args.next(); // skip command name + match args.next()? { + "ru" => { + let comic_id = args.next()?.parse::().ok()?; + Some((msg, format!("https://xkcd.ru/{}/?json=true", comic_id))) + } + "en" => { + let comic_id = args.next()?.parse::().ok()?; + Some((msg, format!("https://xkcd.com/{}/info.0.json", comic_id))) + } + comic_id => { + let comic_id = comic_id.parse::().ok()?; + Some((msg, format!("https://xkcd.com/{}/info.0.json", comic_id))) + } + } + } else { + None + } + }) + }) + }) + .for_each(|(msg, url)| { + tokio::spawn(send_comic(cfg.clone(), msg, url)); + }); if let Some(u) = updates.last() { s.offset = u.update_id + 1; @@ -154,14 +171,8 @@ async fn handler(cfg: &Config, s: &mut State) -> Result<(), reqwest::Error> { Ok(()) } -async fn send_comic(cfg: Config, msg: TgMessage, comic_id: u16) -> Result<(), reqwest::Error> { - let info = cfg - .client - .get(format!("https://xkcd.com/{}/info.0.json", comic_id)) - .send() - .await? - .json::() - .await?; +async fn send_comic(cfg: Config, msg: TgMessage, url: String) -> Result<(), reqwest::Error> { + let info = cfg.client.get(url).send().await?.json::().await?; cfg.client .post((*cfg.send_url).clone())