diff --git a/src/bin/patched_porobot_telegram.rs b/src/bin/patched_porobot_telegram.rs index 7abdd6a..7157406 100644 --- a/src/bin/patched_porobot_telegram.rs +++ b/src/bin/patched_porobot_telegram.rs @@ -72,7 +72,7 @@ //! //! Since [@patchedporobot] uses [`tantivy`] internally, you might find more information on even more advanced queries in the [documentation of their `QueryParser`](tantivy::query::QueryParser)! //! -//! ### Deck queries +//! ### Deck parsing //! //! You can have [@patchedporobot] display a deck and its cards by pasting the deck code after the bot's username: //! @@ -82,6 +82,15 @@ //! //! Then, select the "Deck with N cards" option to send the deck's card list in the chat! //! +//! #### Named decks +//! +//! Optionally, you may add a name to your deck, which will be displayed above the deck code: +//! +//! ```text +//! @patchedporobot CIBQCAICAQAQGBQIBEBAMBAJBMGBUHJNGE4AEAIBAIYQEAQGEU2QCAIBAIUQ Gimbo's Depths +//! ``` +//! +//! If entered correctly, the bot will display a slightly different option containing the deck's name (_Deck "NAME" with N cards_), which you can check before the message is sent to the chat. //! //! [@patchedporobot]: https://t.me/patchedporobot diff --git a/src/data/deckcode/deck.rs b/src/data/deckcode/deck.rs index 34f56aa..1a2ac12 100644 --- a/src/data/deckcode/deck.rs +++ b/src/data/deckcode/deck.rs @@ -198,9 +198,7 @@ impl Deck { let card_count = reader.read_u32_varint().map_err(DeckDecodingError::Read)?; let set = reader.read_u32_varint().map_err(DeckDecodingError::Read)?; - let set = CardSet::from(set) - .to_code() - .ok_or(DeckDecodingError::UnknownSet)?; + let set = format!("{:02}", &set); let region = reader.read_u32_varint().map_err(DeckDecodingError::Read)?; let region = CardRegion::from(region) @@ -229,8 +227,7 @@ impl Deck { .write_u32_varint(len) .map_err(DeckEncodingError::Write)?; - let set: u32 = CardSet::from_code(set) - .try_into() + let set: u32 = set.parse() .map_err(|_| DeckEncodingError::UnknownSet)?; writer .write_u32_varint(set) @@ -484,9 +481,9 @@ pub type DeckEncodingResult = Result; #[macro_export] macro_rules! deck { [$($cd:literal: $qty:literal),* $(,)?] => { - crate::data::deckcode::deck::Deck { + $crate::data::deckcode::deck::Deck { contents: std::collections::HashMap::from([ - $((crate::data::setbundle::code::CardCode { full: $cd.to_string() }, $qty),)* + $(($crate::data::setbundle::code::CardCode { full: $cd.to_string() }, $qty),)* ]) } } diff --git a/src/data/setbundle/set.rs b/src/data/setbundle/set.rs index 637cfdb..5962e88 100644 --- a/src/data/setbundle/set.rs +++ b/src/data/setbundle/set.rs @@ -55,8 +55,10 @@ impl CardSet { hm.get(self) } - /// Get the [`CardSet`] from its short code, **assuming it is not an [`CardSet::Events`] card**. + /// Get the [`CardSet`] from its short code. /// + /// [`CardSet::Worldwalker`] and [`CardSet::TheDarkinSaga`] share the same code `06`, so a variant cannot be determined. + /// /// [`CardSet::Events`] cards have the short code of the set they were released in, so it is impossible to determine if a card belongs to that set from its short code. pub fn from_code(value: &str) -> Self { match value { @@ -65,7 +67,6 @@ impl CardSet { "03" => Self::CallOfTheMountain, "04" => Self::EmpiresOfTheAscended, "05" => Self::BeyondTheBandlewood, - "06" => Self::Worldwalker, _ => Self::Unsupported, } @@ -84,6 +85,7 @@ impl CardSet { Self::EmpiresOfTheAscended => Some("04".to_string()), Self::BeyondTheBandlewood => Some("05".to_string()), Self::Worldwalker => Some("06".to_string()), + Self::TheDarkinSaga => Some("06".to_string()), _ => None, } @@ -92,6 +94,8 @@ impl CardSet { /// Get the [`CardSet`] from its internal id. /// +/// [`CardSet::Worldwalker`] and [`CardSet::TheDarkinSaga`] share the same id, so a variant cannot be determined. +/// /// [`CardSet::Events`] cards have the id of the set they were released in, so it is impossible to determine if a card belongs to that set from its id. impl From for CardSet { fn from(value: u32) -> Self { @@ -101,7 +105,6 @@ impl From for CardSet { 3 => CardSet::CallOfTheMountain, 4 => CardSet::EmpiresOfTheAscended, 5 => CardSet::BeyondTheBandlewood, - 6 => CardSet::Worldwalker, _ => CardSet::Unsupported, } } @@ -121,6 +124,7 @@ impl TryFrom for u32 { CardSet::EmpiresOfTheAscended => Ok(4), CardSet::BeyondTheBandlewood => Ok(5), CardSet::Worldwalker => Ok(6), + CardSet::TheDarkinSaga => Ok(6), _ => Err(()), } } diff --git a/src/telegram/display.rs b/src/telegram/display.rs index d53170a..03645be 100644 --- a/src/telegram/display.rs +++ b/src/telegram/display.rs @@ -153,10 +153,10 @@ fn display_levelup(levelup: &String) -> String { } } -/// Render a [Deck] in [Telegram Bot HTML]. +/// Render a [Deck] in [Telegram Bot HTML], with an optional `name`. /// /// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style -pub fn display_deck(index: &CardIndex, deck: &Deck, code: String) -> String { +pub fn display_deck(index: &CardIndex, deck: &Deck, code: &str, name: &Option<&str>) -> String { // TODO: optimize this let cards = deck .contents @@ -182,5 +182,8 @@ pub fn display_deck(index: &CardIndex, deck: &Deck, code: String) -> String { }) .join("\n"); - format!("{}\n\n{}", &code, &cards) + match name { + Some(name) => format!("{}\n{}\n\n{}", &name, &code, &cards), + None => format!("{}\n\n{}", &code, &cards), + } } diff --git a/src/telegram/handler.rs b/src/telegram/handler.rs index c2fff99..7cc1f34 100644 --- a/src/telegram/handler.rs +++ b/src/telegram/handler.rs @@ -10,6 +10,8 @@ use teloxide::payloads::{AnswerInlineQuery, SendMessage}; use teloxide::prelude::*; use teloxide::requests::{JsonRequest, ResponseResult}; use teloxide::types::{ParseMode, Recipient}; +use lazy_static::lazy_static; +use regex::Regex; /// Handle inline queries by searching cards on the [CardSearchEngine]. pub fn inline_query_handler( @@ -33,17 +35,28 @@ pub fn inline_query_handler( }; } - if let Ok(deck) = Deck::from_code(&query.query.to_ascii_uppercase()) { - debug!("Parsed deck successfully!"); - break AnswerInlineQuery { - inline_query_id: query.id.clone(), - results: vec![deck_to_inlinequeryresult(&engine.cards, &deck)], - cache_time: None, - is_personal: Some(false), - next_offset: None, - switch_pm_text: None, - switch_pm_parameter: None, - }; + lazy_static! { + static ref DECK_RE: Regex = Regex::new(r#"^(?P[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]+)(?:\s+(?P.+?))?\s*$"#).unwrap(); + } + + if let Some(deck_captures) = DECK_RE.captures(&query.query) { + if let Some(deck_code) = deck_captures.name("code") { + if let Ok(deck) = Deck::from_code(&deck_code.as_str()) { + + debug!("Parsed deck successfully!"); + let name = deck_captures.name("name").map(|m| m.as_str()); + + break AnswerInlineQuery { + inline_query_id: query.id.clone(), + results: vec![deck_to_inlinequeryresult(&engine.cards, &deck, &name)], + cache_time: None, + is_personal: Some(false), + next_offset: None, + switch_pm_text: None, + switch_pm_parameter: None, + }; + } + } } debug!("Querying the card search engine..."); diff --git a/src/telegram/inline.rs b/src/telegram/inline.rs index 3b55fef..8eae007 100644 --- a/src/telegram/inline.rs +++ b/src/telegram/inline.rs @@ -43,17 +43,20 @@ pub fn card_to_inlinequeryresult( }) } -/// Convert a [Deck] into a [InlineQueryResult]. -pub fn deck_to_inlinequeryresult(index: &CardIndex, deck: &Deck) -> InlineQueryResult { +/// Convert a [Deck] with an optional name into a [InlineQueryResult]. +pub fn deck_to_inlinequeryresult(index: &CardIndex, deck: &Deck, name: &Option<&str>) -> InlineQueryResult { let code = deck .to_code(DeckCodeFormat::F1) .expect("serialized deck to deserialize properly"); InlineQueryResult::Article(InlineQueryResultArticle { id: format!("{:x}", md5::compute(&code)), - title: format!("Deck with {} cards", deck.contents.len()), + title: match &name { + Some(name) => format!(r#"Deck "{}" with {} cards"#, name, deck.contents.len()), + None => format!("Deck with {} cards", deck.contents.len()) + }, input_message_content: InputMessageContent::Text(InputMessageContentText { - message_text: display_deck(index, deck, code), + message_text: display_deck(index, deck, &code, &name), parse_mode: Some(ParseMode::Html), entities: None, disable_web_page_preview: Some(true),