1
Fork 0
mirror of https://github.com/Steffo99/patched-porobot.git synced 2024-12-23 01:54:22 +00:00

Complete most of the telegram bot

This commit is contained in:
Steffo 2022-08-08 04:10:59 +02:00
parent b2a2535b93
commit 25df599586
Signed by: steffo
GPG key ID: 6965406171929D01
9 changed files with 189 additions and 78 deletions

View file

@ -7,6 +7,7 @@
<sourceFolder url="file://$MODULE_DIR$/card-data" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/card-data" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>

2
Cargo.lock generated
View file

@ -1036,8 +1036,10 @@ version = "0.3.0"
dependencies = [ dependencies = [
"glob", "glob",
"itertools 0.10.3", "itertools 0.10.3",
"lazy_static",
"log", "log",
"pretty_env_logger", "pretty_env_logger",
"regex",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -12,7 +12,9 @@ categories = ["games", "parser-implementations"]
[dependencies] [dependencies]
# base # base
log = { version = "0.4.17" } 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 # exec
pretty_env_logger = { version = "0.4.0", optional = true } pretty_env_logger = { version = "0.4.0", optional = true }
glob = { version = "0.3.0", optional = true } glob = { version = "0.3.0", optional = true }

View file

@ -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<Card> = [
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;
}

View file

@ -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!")
}

View file

@ -1,6 +1,9 @@
//! Module defining [CardArt]. //! 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*. /// 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)] #[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct CardArt { pub struct CardArt {
@ -40,9 +43,13 @@ impl CardArt {
/// ``` /// ```
/// ///
pub fn card_jpg(&self) -> String { pub fn card_jpg(&self) -> String {
self.card_png lazy_static! {
.replace("https://dd.b.pvp.net/latest/set1", "https://poro.steffo.eu/set1-en_us") static ref GET_JPG: Regex = Regex::new(
.replace(".png", ".jpg") r#"https?://dd[.]b[.]pvp[.]net/[^/]+/(?P<bundle>[^/]+)/(?P<locale>[^/]+)/img/cards/(?P<code>.+)[.]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`. /// 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 { pub fn full_jpg(&self) -> String {
self.full_png lazy_static! {
.replace("https://dd.b.pvp.net/latest/set1", "https://poro.steffo.eu/set1-en_us") static ref GET_JPG: Regex = Regex::new(
.replace(".png", ".jpg") r#"https?://dd[.]b[.]pvp[.]net/[^/]+/(?P<bundle>[^/]+)/(?P<locale>[^/]+)/img/cards/(?P<code>.+)[.]png$"#
).unwrap();
}
GET_JPG.replace_all(&self.full_png, "https://poro.steffo.eu/$bundle-$locale/$locale/img/cards/$code.jpg").to_string()
} }
} }

View file

@ -47,12 +47,17 @@ impl CardSearchEngine {
/// Create the [tantivy::schema::TextOptions] for card codes. /// Create the [tantivy::schema::TextOptions] for card codes.
/// ///
/// Card codes should: /// Card codes should:
/// - never be tokenized; /// - TODO: be tokenized without alterations;
/// - ignore positioning;
/// - be retrievable (what [tantivy] calls "stored"). /// - be retrievable (what [tantivy] calls "stored").
fn options_code() -> TextOptions { fn options_code() -> TextOptions {
use tantivy::schema::*; use tantivy::schema::*;
TextOptions::default() TextOptions::default()
.set_indexing_options(TextFieldIndexing::default()
.set_tokenizer("card")
.set_index_option(IndexRecordOption::Basic)
)
.set_stored() .set_stored()
.set_fast() .set_fast()
} }
@ -253,7 +258,14 @@ impl CardSearchEngine {
fn parser(index: &Index, fields: CardSchemaFields) -> QueryParser { fn parser(index: &Index, fields: CardSchemaFields) -> QueryParser {
QueryParser::for_index( QueryParser::for_index(
&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]. /// [Card::supertype].
pub supertype: Field, pub supertype: Field,
} }
impl From<CardSchemaFields> for Vec<Field> {
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,
]
}
}

View file

@ -22,24 +22,23 @@ use crate::data::setbundle::supertype::CardSupertype;
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style /// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
pub fn display_card(globals: &LocalizedGlobalsIndexes, card: &Card) -> String { pub fn display_card(globals: &LocalizedGlobalsIndexes, card: &Card) -> String {
let title = format!( let title = format!(
r#"<a href="{}"><b><i>{}</b></i></a>"#, "<b><u>{}</u></b>\n",
&card.main_art().expect("Card to have at least one illustration").card_png,
escape(&card.name), escape(&card.name),
); );
let stats = match &card.r#type { let stats = match &card.r#type {
CardType::Spell => format!( CardType::Spell => format!(
"{} mana", "{} mana\n",
escape(&card.cost.to_string()), escape(&card.cost.to_string()),
), ),
CardType::Unit => format!( CardType::Unit => format!(
"{} mana {}|{}", "{} mana {}|{}\n",
escape(&card.cost.to_string()), escape(&card.cost.to_string()),
escape(&card.attack.to_string()), escape(&card.attack.to_string()),
escape(&card.health.to_string()), escape(&card.health.to_string()),
), ),
CardType::Landmark => format!( CardType::Landmark => format!(
"{} mana", "{} mana\n",
&card.cost &card.cost
), ),
_ => "".to_string(), _ => "".to_string(),
@ -49,32 +48,25 @@ pub fn display_card(globals: &LocalizedGlobalsIndexes, card: &Card) -> String {
let regions = display_regions(&card.regions, &globals.regions); let regions = display_regions(&card.regions, &globals.regions);
let r#type = display_types(&card.r#type, &card.supertype, &card.subtypes); let r#type = display_types(&card.r#type, &card.supertype, &card.subtypes);
let breadcrumbs = format!("{} {} {}", &set, &regions, &r#type); let breadcrumbs = format!("{} {} {}\n", &set, &regions, &r#type);
let keywords = display_keywords(&card.keywords, &globals.keywords); 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!( let flavor = format!(
"<i>{}</i>", "<i>{}</i>\n",
escape(&card.localized_flavor_text) escape(&card.localized_flavor_text)
); );
let artist = format!( let artist = format!(
r#"<a href="{}">Illustration by {}</a>"#, r#"<a href="{}">Illustration</a> by {}"#,
&card.main_art().expect("Card to have at least one illustration").full_png, &card.main_art().expect("Card to have at least one illustration").full_png,
escape(&card.artist_name) escape(&card.artist_name)
); );
format!( format!(
"{title} {stats}\n{breadcrumbs}\n\n{keywords}\n{description}\n\n-----\n{flavor}\n\n{artist}", "{title}{breadcrumbs}\n{keywords}{stats}{description}\n-----\n{flavor}{artist}",
title=title,
stats=stats,
breadcrumbs=breadcrumbs,
keywords=keywords,
description=description,
flavor=flavor,
artist=artist,
) )
} }
@ -145,6 +137,8 @@ fn display_types(r#type: &CardType, supertype: &CardSupertype, subtypes: &[CardS
/// ///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style /// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_keywords(keywords: &[CardKeyword], hm: &LocalizedCardKeywordIndex) -> String { fn display_keywords(keywords: &[CardKeyword], hm: &LocalizedCardKeywordIndex) -> String {
format!(
"{}\n",
keywords keywords
.iter() .iter()
.map(|keyword| keyword .map(|keyword| keyword
@ -153,4 +147,6 @@ fn display_keywords(keywords: &[CardKeyword], hm: &LocalizedCardKeywordIndex) ->
.unwrap_or_else(|| "Unknown".to_string()) .unwrap_or_else(|| "Unknown".to_string())
) )
.join(" ") .join(" ")
)
} }

View file

@ -2,7 +2,7 @@
//! //!
//! [inline mode]: https://core.telegram.org/bots/api#inline-mode //! [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::corebundle::globals::LocalizedGlobalsIndexes;
use crate::data::setbundle::card::Card; use crate::data::setbundle::card::Card;
use crate::telegram::display::display_card; use crate::telegram::display::display_card;
@ -13,6 +13,8 @@ pub fn card_to_inlinequeryresult(globals: &LocalizedGlobalsIndexes, card: &Card)
InlineQueryResult::Photo(InlineQueryResultPhoto { InlineQueryResult::Photo(InlineQueryResultPhoto {
id: card.code.to_owned(), id: card.code.to_owned(),
title: Some(card.name.to_owned()), title: Some(card.name.to_owned()),
caption: Some(display_card(&globals, &card)),
parse_mode: Some(ParseMode::Html),
photo_url: card photo_url: card
.main_art() .main_art()
.expect("Card to have at least one illustration") .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_width: Some(680),
photo_height: Some(1024), 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, description: None,
caption: None,
parse_mode: None,
caption_entities: None, caption_entities: None,
reply_markup: None, reply_markup: None,
input_message_content: None,
}) })
} }