1
Fork 0
mirror of https://github.com/Steffo99/patched-porobot.git synced 2024-12-22 17:44: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$/benches" 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" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>

2
Cargo.lock generated
View file

@ -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",

View file

@ -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 }

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].
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<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`.
@ -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<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.
///
/// 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<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
pub fn display_card(globals: &LocalizedGlobalsIndexes, card: &Card) -> String {
let title = format!(
r#"<a href="{}"><b><i>{}</b></i></a>"#,
&card.main_art().expect("Card to have at least one illustration").card_png,
"<b><u>{}</u></b>\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, &regions, &r#type);
let breadcrumbs = format!("{} {} {}\n", &set, &regions, &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!(
"<i>{}</i>",
"<i>{}</i>\n",
escape(&card.localized_flavor_text)
);
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,
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!("[<b>{}</b>]", escape(&o.name)))
.unwrap_or_else(|| "Unknown".to_string())
)
.join(" ")
format!(
"{}\n",
keywords
.iter()
.map(|keyword| keyword
.localized(hm)
.map(|o| format!("[<b>{}</b>]", escape(&o.name)))
.unwrap_or_else(|| "Unknown".to_string())
)
.join(" ")
)
}

View file

@ -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,
})
}