1
Fork 0
mirror of https://github.com/Steffo99/patched-porobot.git synced 2024-12-23 10:04:21 +00:00

Continue building the skeleton of the bot

This commit is contained in:
Steffo 2022-07-31 20:32:58 +02:00
parent e17c2b446f
commit 6810e1acc7
Signed by: steffo
GPG key ID: 6965406171929D01
6 changed files with 147 additions and 52 deletions

View file

@ -1,10 +1,7 @@
//! This module contains functions to load **Set Bundles** from [Data Dragon](https://developer.riotgames.com/docs/lol). //! This module contains functions to load **Set Bundles** from [Data Dragon](https://developer.riotgames.com/docs/lol).
use std::path::*;
use glob::{glob, GlobResult};
use itertools::Itertools; use itertools::Itertools;
use crate::data::schema::Card; use crate::data::schema::Card;
use log::*;
#[derive(Debug)] #[derive(Debug)]
@ -15,7 +12,7 @@ enum LoadingError {
/// Load a single Set Bundle and create a [Vec] with the cards contained in it. /// Load a single Set Bundle and create a [Vec] with the cards contained in it.
fn load_setbundle(path: PathBuf) -> Result<Vec<Card>, LoadingError> { fn load_setbundle(path: std::path::PathBuf) -> Result<Vec<Card>, LoadingError> {
let file = std::fs::File::open(path) let file = std::fs::File::open(path)
.map_err(LoadingError::IO)?; .map_err(LoadingError::IO)?;
let data = serde_json::de::from_reader::<std::fs::File, Vec<Card>>(file) let data = serde_json::de::from_reader::<std::fs::File, Vec<Card>>(file)
@ -25,11 +22,11 @@ fn load_setbundle(path: PathBuf) -> Result<Vec<Card>, LoadingError> {
/// Load a single Set Bundle (similarly to [load_setbundle]), but instead of returning a [Result], return an empty [Vec] in case of failure and log a [warn]ing. /// Load a single Set Bundle (similarly to [load_setbundle]), but instead of returning a [Result], return an empty [Vec] in case of failure and log a [warn]ing.
fn load_setbundle_infallible(path: PathBuf) -> Vec<Card> { fn load_setbundle_infallible(path: std::path::PathBuf) -> Vec<Card> {
match load_setbundle(path) { match load_setbundle(path) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
warn!("{:?}", e); log::warn!("{:?}", e);
vec![] vec![]
} }
} }
@ -38,9 +35,9 @@ fn load_setbundle_infallible(path: PathBuf) -> Vec<Card> {
/// Load all Set Bundles matched by the passed glob, using [load_setbundle_infallible] and then concatenating the resulting [Vec]s. /// Load all Set Bundles matched by the passed glob, using [load_setbundle_infallible] and then concatenating the resulting [Vec]s.
pub fn load_setbundles_infallible(pattern: &str) -> Vec<Card> { pub fn load_setbundles_infallible(pattern: &str) -> Vec<Card> {
glob(pattern) glob::glob(pattern)
.expect("a valid glob") .expect("a valid glob")
.filter_map(GlobResult::ok) .filter_map(glob::GlobResult::ok)
.map(load_setbundle_infallible) .map(load_setbundle_infallible)
.concat() .concat()
} }

View file

@ -1,94 +1,94 @@
/// A single Legends of Runeterra card as represented in the data files from [Data Dragon](https://developer.riotgames.com/docs/lor). /// A single Legends of Runeterra card as represented in the data files from [Data Dragon](https://developer.riotgames.com/docs/lor).
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all="camelCase")] #[serde(rename_all="camelCase")]
pub struct Card { pub struct Card {
/// Localized names of the cards associated with this one. /// Localized names of the cards associated with this one.
/// For some reason, might not match what is contained in `associated_card_refs`. /// For some reason, might not match what is contained in `associated_card_refs`.
associated_cards: Vec<String>, pub associated_cards: Vec<String>,
/// `card_code`s of the cards associated with this one. /// `card_code`s of the cards associated with this one.
associated_card_refs: Vec<String>, pub associated_card_refs: Vec<String>,
/// Art assets of this card. /// Art assets of this card.
assets: Vec<Asset>, pub assets: Vec<Asset>,
/// Localized names of the regions this card belongs to. /// Localized names of the regions this card belongs to.
regions: Vec<String>, pub regions: Vec<String>,
/// IDs of the regions this card belongs to. /// IDs of the regions this card belongs to.
region_refs: Vec<String>, pub region_refs: Vec<String>,
/// Base attack of the card. /// Base attack of the card.
attack: i8, pub attack: i8,
/// Base cost of the card. /// Base cost of the card.
cost: i8, pub cost: i8,
/// Base health of the card. /// Base health of the card.
health: i8, pub health: i8,
/// Localized description of the card, in XML. /// Localized description of the card, in XML.
description: String, pub description: String,
/// Localized description of the card, in plain text. /// Localized description of the card, in plain text.
description_raw: String, pub description_raw: String,
/// Localized level up text of the card, in XML. /// Localized level up text of the card, in XML.
levelup_description: String, pub levelup_description: String,
/// Localized level up text of the card, in plain text. /// Localized level up text of the card, in plain text.
levelup_description_raw: String, pub levelup_description_raw: String,
/// Flavor text of the card, displayed when its image is inspected. /// Flavor text of the card, displayed when its image is inspected.
flavor_text: String, pub flavor_text: String,
/// Name of the artist who drew the card. /// Name of the artist who drew the card.
artist_name: String, pub artist_name: String,
/// Localized name of the card. /// Localized name of the card.
name: String, pub name: String,
/// Unique seven-character identifier of the card. /// Unique seven-character identifier of the card.
card_code: String, pub card_code: String,
/// List of keywords of this card, with their localized names. /// List of keywords of this card, with their localized names.
keywords: Vec<String>, pub keywords: Vec<String>,
/// List of keywords of this card, with their internal names. /// List of keywords of this card, with their internal names.
keyword_refs: Vec<String>, pub keyword_refs: Vec<String>,
/// Localized spell speed. /// Localized spell speed.
spell_speed: String, pub spell_speed: String,
/// [SpellSpeed] of the card. /// [SpellSpeed] of the card.
spell_speed_ref: SpellSpeed, pub spell_speed_ref: SpellSpeed,
/// Localized rarity of the card. /// Localized rarity of the card.
rarity: String, pub rarity: String,
/// [CardRarity] of the card. /// [CardRarity] of the card.
rarity_ref: CardRarity, pub rarity_ref: CardRarity,
/// The subtypes the card has, such as `PORO`. /// The subtypes the card has, such as `PORO`.
subtypes: Vec<String>, pub subtypes: Vec<String>,
/// The [CardSupertype] the card belongs to, such as `Champion`. /// The [CardSupertype] the card belongs to, such as `Champion`.
supertype: CardSupertype, pub supertype: CardSupertype,
/// If `true`, the card can be found in chests, crafted, or used in decks. /// If `true`, the card can be found in chests, crafted, or used in decks.
/// If `false`, the card is not available for direct use, as it is probably created by another card. /// If `false`, the card is not available for direct use, as it is probably created by another card.
collectible: bool, pub collectible: bool,
/// The [CardSet] the card belongs to. /// The [CardSet] the card belongs to.
set: String, pub set: String,
#[serde(rename(serialize = "type", deserialize = "type"))] #[serde(rename(serialize = "type", deserialize = "type"))]
card_type: CardType, pub card_type: CardType,
} }
/// An art asset associated with a given card. /// An art asset associated with a given card.
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all="camelCase")] #[serde(rename_all="camelCase")]
pub struct Asset { pub struct Asset {
/// URL to the card art as it is displayed in-game. /// URL to the card art as it is displayed in-game.
game_absolute_path: String, pub game_absolute_path: String,
/// URL to the full-size card art as it is displayed when the card is inspected. /// URL to the full-size card art as it is displayed when the card is inspected.
full_absolute_path: String, pub full_absolute_path: String,
} }
/// Possible card types. /// Possible card types.
#[non_exhaustive] #[non_exhaustive]
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum CardType { pub enum CardType {
/// A spell. /// A spell.
Spell, Spell,
@ -99,12 +99,14 @@ pub enum CardType {
Ability, Ability,
/// A landmark. /// A landmark.
Landmark, Landmark,
/// A trap or boon.
Trap,
} }
/// Possible card supertypes. /// Possible card supertypes.
#[non_exhaustive] #[non_exhaustive]
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum CardSupertype { pub enum CardSupertype {
#[serde(alias = "")] #[serde(alias = "")]
None, None,
@ -115,7 +117,7 @@ pub enum CardSupertype {
/// Possible card rarities. /// Possible card rarities.
#[non_exhaustive] #[non_exhaustive]
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum CardRarity { pub enum CardRarity {
#[serde(alias = "NONE")] #[serde(alias = "NONE")]
None, None,
@ -132,7 +134,7 @@ pub enum CardRarity {
/// Possible spell speeds. /// Possible spell speeds.
#[non_exhaustive] #[non_exhaustive]
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum SpellSpeed { pub enum SpellSpeed {
/// Non-spell cards have this speed. /// Non-spell cards have this speed.
#[serde(alias = "")] #[serde(alias = "")]
@ -148,7 +150,7 @@ pub enum SpellSpeed {
/// Release sets [Card]s may belong to. /// Release sets [Card]s may belong to.
#[non_exhaustive] #[non_exhaustive]
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum CardSet { pub enum CardSet {
#[serde(rename = "Set1")] #[serde(rename = "Set1")]
Foundations, Foundations,

View file

@ -1,27 +1,52 @@
#[macro_use] extern crate tantivy;
#[macro_use] extern crate log;
use teloxide::prelude::*; use teloxide::prelude::*;
use teloxide::types::*; use teloxide::types::*;
use log::*; use log::*;
use tantivy::{IndexReader, ReloadPolicy};
mod data; mod data;
mod search;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
pretty_env_logger::init(); pretty_env_logger::init();
let data = data::load::load_files(); debug!("Loading Set Bundles...");
debug!("{:?}", data); let cards = data::load::load_setbundles_infallible("./data/*/en_us/data/*-en_us.json");
debug!("Creating Tantivy index...");
let card_index = search::index::build_card_index();
debug!("Writing cards to the index...");
search::index::write_cards_to_index(&card_index, &cards);
debug!("Creating Tantivy reader...");
let card_reader = search::index::build_reader(&card_index);
let inline_query_closure = |query: InlineQuery, bot: AutoSend<Bot>| async move {
bot.answer_inline_query(&query.id, handle_query(&query, &card_reader)).await;
respond(())
};
info!("patched-porobot is starting..."); info!("patched-porobot is starting...");
let bot = Bot::from_env().auto_send(); let bot = Bot::from_env().auto_send();
let handler = Update::filter_inline_query().branch(dptree::endpoint(|query: InlineQuery, bot: AutoSend<Bot>| async move { let handler = Update::filter_inline_query().branch(dptree::endpoint(inline_query_closure));
let result = InlineQueryResult::Article(InlineQueryResultArticle::new("test", "Test", InputMessageContent::Text(InputMessageContentText::new("Qui è dove metterei la mia carta, se solo ne avessi una!"))));
let response = bot.answer_inline_query(&query.id, vec![result]).await;
respond(())
}));
Dispatcher::builder(bot, handler).enable_ctrlc_handler().build().dispatch().await; Dispatcher::builder(bot, handler).enable_ctrlc_handler().build().dispatch().await;
} }
/// Handle a [InlineQuery] incoming from Telegram.
fn handle_query(query: &InlineQuery, reader: &IndexReader) -> Vec<InlineQueryResult> {
debug!("Creating Tantivy searcher...");
let card_searcher = reader.searcher();
let result = InlineQueryResult::Article(InlineQueryResultArticle::new("test", "Test", InputMessageContent::Text(InputMessageContentText::new("Qui è dove metterei la mia carta, se solo ne avessi una!"))));
vec![result]
}

41
src/search/index.rs Normal file
View file

@ -0,0 +1,41 @@
use tantivy::*;
use crate::data::schema::Card;
use crate::search::schema;
/// Build a [tantivy] [Index] storing [Card]s as documents.
pub fn build_card_index() -> Index {
Index::create_in_ram(schema::build_card_schema())
}
/// Build a [tantivy] [IndexWriter] from the given [Index] with some preset parameters.
///
/// Currently allocates 4 MB.
pub fn build_writer(index: &Index) -> IndexWriter {
index.writer(4_000_000)
.expect("to be able to allocate a tantivy writer")
}
/// Write a [Vec] of [Card]s to a [tantivy] [Index], using a writer built with [build_writer].
pub fn write_cards_to_index(index: &Index, cards: &Vec<Card>) -> () {
let schema = index.schema();
let mut writer = build_writer(index);
for card in cards {
let document = schema::card_to_document(&schema, card.to_owned());
writer.add_document(document)
.expect("to be able to add a document to the index");
}
writer.commit()
.expect("to be able to commit the schema changes");
}
/// Build a [tantivy] [IndexReader] from the given [Index] with some preset parameters.
pub fn build_reader(index: &Index) -> IndexReader {
index.reader_builder().reload_policy(ReloadPolicy::OnCommit).try_into()
.expect("to be able to allocate a tantivy reader")
}

2
src/search/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod schema;
pub mod index;

28
src/search/schema.rs Normal file
View file

@ -0,0 +1,28 @@
use tantivy::schema::*;
use crate::data::schema::Card;
/// Build a [tantivy] [Schema] storing [Card]s as documents.
///
/// TODO: Allow search on all fields.
pub fn build_card_schema() -> Schema {
let mut schema_builder = Schema::builder();
schema_builder.add_text_field("name", TEXT);
schema_builder.add_text_field("description", TEXT);
schema_builder.add_text_field("code", STRING | STORED);
schema_builder.build()
}
/// Convert a [Card] to a Tantivy [Document], using the specified [Schema] (which should come from [build_card_schema]).
pub fn card_to_document(schema: &Schema, card: Card) -> Document {
let name = schema.get_field("name").unwrap();
let description = schema.get_field("description").unwrap();
let code = schema.get_field("code").unwrap();
doc!(
name => card.name,
description => card.description_raw,
code => card.card_code,
)
}