diff --git a/.idea/patched-porobot.iml b/.idea/patched-porobot.iml
index 20bba95..121e142 100644
--- a/.idea/patched-porobot.iml
+++ b/.idea/patched-porobot.iml
@@ -7,6 +7,7 @@
+
diff --git a/Cargo.lock b/Cargo.lock
index da36158..4d4017f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1036,8 +1036,10 @@ version = "0.3.0"
dependencies = [
"glob",
"itertools 0.10.3",
+ "lazy_static",
"log",
"pretty_env_logger",
+ "regex",
"reqwest",
"serde",
"serde_json",
diff --git a/Cargo.toml b/Cargo.toml
index 0233602..77d9fa4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,9 @@ categories = ["games", "parser-implementations"]
[dependencies]
# base
log = { version = "0.4.17" }
-itertools = { version = "0.10.3" } # Not using this yet
+itertools = { version = "0.10.3" }
+regex = { version = "1.6.0" }
+lazy_static = { version = "1.4.0" }
# exec
pretty_env_logger = { version = "0.4.0", optional = true }
glob = { version = "0.3.0", optional = true }
diff --git a/src/bin/patched_porobot_telegram.rs b/src/bin/patched_porobot_telegram.rs
new file mode 100644
index 0000000..08ea74d
--- /dev/null
+++ b/src/bin/patched_porobot_telegram.rs
@@ -0,0 +1,127 @@
+#[cfg(not(feature = "telegram"))]
+fn main() {
+ println!("The `telegram` feature was not included on compilation, therefore this binary is not available.")
+}
+
+#[cfg(feature = "telegram")]
+#[tokio::main]
+async fn main() {
+ use std::path::PathBuf;
+ use log::*;
+ use patched_porobot::data::setbundle::card::{Card, CardIndex};
+ use patched_porobot::data::corebundle::CoreBundle;
+ use patched_porobot::data::setbundle::SetBundle;
+ use patched_porobot::data::corebundle::globals::LocalizedGlobalsIndexes;
+ use patched_porobot::search::cardsearch::CardSearchEngine;
+ use patched_porobot::telegram::inline::card_to_inlinequeryresult;
+ use teloxide::payloads::AnswerInlineQuery;
+ use teloxide::requests::JsonRequest;
+ use teloxide::prelude::*;
+ use itertools::Itertools;
+
+ pretty_env_logger::init();
+ debug!("Logger initialized successfully!");
+
+ debug!("Loading bundles...");
+ let core = CoreBundle::load(&*PathBuf::from("./card-data/core-en_us")).expect("to be able to load `core-en_us` bundle");
+ let set1 = SetBundle::load(&*PathBuf::from("./card-data/set1-en_us")).expect("to be able to load `set1-en_us` bundle");
+ let set2 = SetBundle::load(&*PathBuf::from("./card-data/set2-en_us")).expect("to be able to load `set2-en_us` bundle");
+ let set3 = SetBundle::load(&*PathBuf::from("./card-data/set3-en_us")).expect("to be able to load `set3-en_us` bundle");
+ let set4 = SetBundle::load(&*PathBuf::from("./card-data/set4-en_us")).expect("to be able to load `set4-en_us` bundle");
+ let set5 = SetBundle::load(&*PathBuf::from("./card-data/set5-en_us")).expect("to be able to load `set5-en_us` bundle");
+ let set6 = SetBundle::load(&*PathBuf::from("./card-data/set6-en_us")).expect("to be able to load `set6-en_us` bundle");
+ debug!("Loaded all bundles!");
+
+ debug!("Indexing globals...");
+ let globals = LocalizedGlobalsIndexes::from(core.globals);
+ debug!("Indexed globals!");
+
+ debug!("Indexing cards...");
+ let cards: Vec = [
+ set1.cards,
+ set2.cards,
+ set3.cards,
+ set4.cards,
+ set5.cards,
+ set6.cards
+ ].concat();
+
+ let mut index = CardIndex::new();
+ for card in cards {
+ index.insert(card.code.clone(), card);
+ }
+ let cards = index;
+ debug!("Indexed cards!");
+
+ debug!("Creating search engine...");
+ let engine = CardSearchEngine::new(globals, cards);
+ debug!("Created search engine!");
+
+ debug!("Creating Telegram bot with parameters from the environment...");
+ let bot = Bot::from_env();
+ let me = bot.get_me().send().await.expect("Telegram bot parameters to be valid");
+ debug!("Created Telegram bot!");
+
+ debug!("Creating inline query handler...");
+ let handler = Update::filter_inline_query().chain(dptree::endpoint(move |query: InlineQuery, bot: Bot| {
+ info!("Handling inline query: `{}`", &query.query);
+
+ debug!("Querying the search engine...");
+ let performed_query = engine.query(&query.query, 50);
+
+ let payload = match performed_query {
+ Ok(results) => {
+ if results.len() > 0 {
+ AnswerInlineQuery {
+ inline_query_id: query.id.clone(),
+ results: results
+ .iter()
+ .map(|card| card_to_inlinequeryresult(&engine.globals, card))
+ .collect_vec(),
+ cache_time: Some(86400),
+ is_personal: Some(false),
+ next_offset: None,
+ switch_pm_text: None,
+ switch_pm_parameter: None,
+ }
+ }
+ else {
+ AnswerInlineQuery {
+ inline_query_id: query.id.clone(),
+ results: vec![],
+ cache_time: None,
+ is_personal: Some(false),
+ next_offset: None,
+ switch_pm_text: Some("No results found".to_string()),
+ switch_pm_parameter: Some("err-no-results".to_string()),
+ }
+ }
+ }
+ Err(_) => {
+ AnswerInlineQuery {
+ inline_query_id: query.id.clone(),
+ results: vec![],
+ cache_time: None,
+ is_personal: Some(false),
+ next_offset: None,
+ switch_pm_text: Some("Invalid query syntax".to_string()),
+ switch_pm_parameter: Some("err-invalid-query".to_string()),
+ }
+ }
+ };
+
+ async move {
+ let telegram_reply = JsonRequest::new(bot.clone(), payload).send().await;
+
+ if let Err(e) = telegram_reply {
+ error!("{:?}", &e);
+ }
+
+ respond(())
+ }
+ }));
+ debug!("Create inline query handler!");
+
+ info!("@{} is ready!", &me.username.as_ref().expect("bot to have an username"));
+ Dispatcher::builder(bot, handler).enable_ctrlc_handler().build().dispatch().await;
+}
diff --git a/src/bin/telegrambot.rs b/src/bin/telegrambot.rs
deleted file mode 100644
index b9ccf66..0000000
--- a/src/bin/telegrambot.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-#[cfg(not(feature = "telegram"))]
-fn main() {
- println!("The `telegram` feature was not included on compilation, therefore this binary is not available.")
-}
-
-#[cfg(feature = "telegram")]
-fn main() {
- println!("Hello telegram world!")
-}
diff --git a/src/data/setbundle/art.rs b/src/data/setbundle/art.rs
index e307ae7..2aa8d22 100644
--- a/src/data/setbundle/art.rs
+++ b/src/data/setbundle/art.rs
@@ -1,6 +1,9 @@
//! Module defining [CardArt].
+use lazy_static::lazy_static;
+use regex::Regex;
+
/// The illustration of a [Card](super::card::Card), also referred to as an *art asset*.
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct CardArt {
@@ -40,9 +43,13 @@ impl CardArt {
/// ```
///
pub fn card_jpg(&self) -> String {
- self.card_png
- .replace("https://dd.b.pvp.net/latest/set1", "https://poro.steffo.eu/set1-en_us")
- .replace(".png", ".jpg")
+ lazy_static! {
+ static ref GET_JPG: Regex = Regex::new(
+ r#"https?://dd[.]b[.]pvp[.]net/[^/]+/(?P[^/]+)/(?P[^/]+)/img/cards/(?P.+)[.]png$"#
+ ).unwrap();
+ }
+
+ GET_JPG.replace_all(&self.card_png, "https://poro.steffo.eu/$bundle-$locale/$locale/img/cards/$code.jpg").to_string()
}
/// URL to the `.jpg` image of the `en_us` locale of the full card art, via `poro.steffo.eu`.
@@ -56,9 +63,13 @@ impl CardArt {
/// ```
///
pub fn full_jpg(&self) -> String {
- self.full_png
- .replace("https://dd.b.pvp.net/latest/set1", "https://poro.steffo.eu/set1-en_us")
- .replace(".png", ".jpg")
+ lazy_static! {
+ static ref GET_JPG: Regex = Regex::new(
+ r#"https?://dd[.]b[.]pvp[.]net/[^/]+/(?P[^/]+)/(?P[^/]+)/img/cards/(?P.+)[.]png$"#
+ ).unwrap();
+ }
+
+ GET_JPG.replace_all(&self.full_png, "https://poro.steffo.eu/$bundle-$locale/$locale/img/cards/$code.jpg").to_string()
}
}
diff --git a/src/search/cardsearch.rs b/src/search/cardsearch.rs
index 54984eb..66c1c43 100644
--- a/src/search/cardsearch.rs
+++ b/src/search/cardsearch.rs
@@ -47,12 +47,17 @@ impl CardSearchEngine {
/// Create the [tantivy::schema::TextOptions] for card codes.
///
/// Card codes should:
- /// - never be tokenized;
+ /// - TODO: be tokenized without alterations;
+ /// - ignore positioning;
/// - be retrievable (what [tantivy] calls "stored").
fn options_code() -> TextOptions {
use tantivy::schema::*;
TextOptions::default()
+ .set_indexing_options(TextFieldIndexing::default()
+ .set_tokenizer("card")
+ .set_index_option(IndexRecordOption::Basic)
+ )
.set_stored()
.set_fast()
}
@@ -253,7 +258,14 @@ impl CardSearchEngine {
fn parser(index: &Index, fields: CardSchemaFields) -> QueryParser {
QueryParser::for_index(
&index,
- Vec::from(fields)
+ vec![
+ fields.code,
+ fields.name,
+ fields.description,
+ fields.flavor,
+ fields.subtypes,
+ fields.supertype,
+ ]
)
}
@@ -346,28 +358,3 @@ struct CardSchemaFields {
/// [Card::supertype].
pub supertype: Field,
}
-
-impl From for Vec {
- fn from(fields: CardSchemaFields) -> Self {
- vec![
- fields.code,
- fields.name,
- fields.r#type,
- fields.set,
- fields.rarity,
- fields.collectible,
- fields.regions,
- fields.attack,
- fields.cost,
- fields.health,
- fields.spellspeed,
- fields.keywords,
- fields.description,
- fields.levelup,
- fields.flavor,
- fields.artist,
- fields.subtypes,
- fields.supertype,
- ]
- }
-}
diff --git a/src/telegram/display.rs b/src/telegram/display.rs
index 9fb1c07..b40c85e 100644
--- a/src/telegram/display.rs
+++ b/src/telegram/display.rs
@@ -22,24 +22,23 @@ use crate::data::setbundle::supertype::CardSupertype;
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
pub fn display_card(globals: &LocalizedGlobalsIndexes, card: &Card) -> String {
let title = format!(
- r#"{}"#,
- &card.main_art().expect("Card to have at least one illustration").card_png,
+ "{}\n",
escape(&card.name),
);
let stats = match &card.r#type {
CardType::Spell => format!(
- "{} mana",
+ "{} mana\n",
escape(&card.cost.to_string()),
),
CardType::Unit => format!(
- "{} mana {}|{}",
+ "{} mana {}|{}\n",
escape(&card.cost.to_string()),
escape(&card.attack.to_string()),
escape(&card.health.to_string()),
),
CardType::Landmark => format!(
- "{} mana",
+ "{} mana\n",
&card.cost
),
_ => "".to_string(),
@@ -49,32 +48,25 @@ pub fn display_card(globals: &LocalizedGlobalsIndexes, card: &Card) -> String {
let regions = display_regions(&card.regions, &globals.regions);
let r#type = display_types(&card.r#type, &card.supertype, &card.subtypes);
- let breadcrumbs = format!("{} › {} › {}", &set, ®ions, &r#type);
+ let breadcrumbs = format!("{} › {} › {}\n", &set, ®ions, &r#type);
let keywords = display_keywords(&card.keywords, &globals.keywords);
- let description = escape(&card.localized_description_text);
+ let description = format!("{}\n", escape(&card.localized_description_text));
let flavor = format!(
- "{}",
+ "{}\n",
escape(&card.localized_flavor_text)
);
let artist = format!(
- r#"Illustration by {}"#,
+ r#"Illustration by {}"#,
&card.main_art().expect("Card to have at least one illustration").full_png,
escape(&card.artist_name)
);
format!(
- "{title} {stats}\n{breadcrumbs}\n\n{keywords}\n{description}\n\n-----\n{flavor}\n\n{artist}",
- title=title,
- stats=stats,
- breadcrumbs=breadcrumbs,
- keywords=keywords,
- description=description,
- flavor=flavor,
- artist=artist,
+ "{title}{breadcrumbs}\n{keywords}{stats}{description}\n-----\n{flavor}{artist}",
)
}
@@ -145,12 +137,16 @@ fn display_types(r#type: &CardType, supertype: &CardSupertype, subtypes: &[CardS
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_keywords(keywords: &[CardKeyword], hm: &LocalizedCardKeywordIndex) -> String {
- keywords
- .iter()
- .map(|keyword| keyword
- .localized(hm)
- .map(|o| format!("[{}]", escape(&o.name)))
- .unwrap_or_else(|| "Unknown".to_string())
- )
- .join(" ")
+ format!(
+ "{}\n",
+ keywords
+ .iter()
+ .map(|keyword| keyword
+ .localized(hm)
+ .map(|o| format!("[{}]", escape(&o.name)))
+ .unwrap_or_else(|| "Unknown".to_string())
+ )
+ .join(" ")
+ )
+
}
diff --git a/src/telegram/inline.rs b/src/telegram/inline.rs
index 168a810..7b72f8f 100644
--- a/src/telegram/inline.rs
+++ b/src/telegram/inline.rs
@@ -2,7 +2,7 @@
//!
//! [inline mode]: https://core.telegram.org/bots/api#inline-mode
-use teloxide::types::{InlineQueryResult, InlineQueryResultPhoto, InputMessageContent, InputMessageContentText, ParseMode};
+use teloxide::types::{InlineQueryResult, InlineQueryResultPhoto, ParseMode};
use crate::data::corebundle::globals::LocalizedGlobalsIndexes;
use crate::data::setbundle::card::Card;
use crate::telegram::display::display_card;
@@ -13,6 +13,8 @@ pub fn card_to_inlinequeryresult(globals: &LocalizedGlobalsIndexes, card: &Card)
InlineQueryResult::Photo(InlineQueryResultPhoto {
id: card.code.to_owned(),
title: Some(card.name.to_owned()),
+ caption: Some(display_card(&globals, &card)),
+ parse_mode: Some(ParseMode::Html),
photo_url: card
.main_art()
.expect("Card to have at least one illustration")
@@ -26,17 +28,9 @@ pub fn card_to_inlinequeryresult(globals: &LocalizedGlobalsIndexes, card: &Card)
photo_width: Some(680),
photo_height: Some(1024),
- input_message_content: Some(InputMessageContent::Text(InputMessageContentText {
- message_text: display_card(&globals, &card),
- parse_mode: Some(ParseMode::Html),
- entities: None,
- disable_web_page_preview: Some(true)
- })),
-
description: None,
- caption: None,
- parse_mode: None,
caption_entities: None,
reply_markup: None,
+ input_message_content: None,
})
}