mirror of
https://github.com/Steffo99/patched-porobot.git
synced 2024-12-23 01:54:22 +00:00
Start more or less clean
This commit is contained in:
parent
982b70cd71
commit
5836d653a7
11 changed files with 2 additions and 408 deletions
|
@ -1,45 +0,0 @@
|
||||||
//! This module contains functions to load **Set Bundles** from [Data Dragon](https://developer.riotgames.com/docs/lol).
|
|
||||||
|
|
||||||
use log::*;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use crate::data::schema::Card;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum LoadingError {
|
|
||||||
IO(std::io::Error),
|
|
||||||
Parsing(serde_json::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Load a single Set Bundle and create a [Vec] with the cards contained in it.
|
|
||||||
fn load_setbundle(path: std::path::PathBuf) -> Result<Vec<Card>, LoadingError> {
|
|
||||||
debug!("Loading Set Bundle {:?}", &path);
|
|
||||||
let file = std::fs::File::open(path)
|
|
||||||
.map_err(LoadingError::IO)?;
|
|
||||||
let data = serde_json::de::from_reader::<std::fs::File, Vec<Card>>(file)
|
|
||||||
.map_err(LoadingError::Parsing)?;
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// 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: std::path::PathBuf) -> Vec<Card> {
|
|
||||||
match load_setbundle(path) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("{:?}", e);
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// 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> {
|
|
||||||
glob::glob(pattern)
|
|
||||||
.expect("a valid glob")
|
|
||||||
.filter_map(glob::GlobResult::ok)
|
|
||||||
.map(load_setbundle_infallible)
|
|
||||||
.concat()
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use crate::data::schema::Card;
|
|
||||||
|
|
||||||
|
|
||||||
/// Build a [HashMap] mapping card codes strings to [Card] structs.
|
|
||||||
pub fn build_card_code_hashmap(cards: &Vec<Card>) -> HashMap<String, Card> {
|
|
||||||
let mut map = HashMap::<String, Card>::new();
|
|
||||||
|
|
||||||
for card in cards {
|
|
||||||
map.insert(card.card_code.to_owned(), card.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
map
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub mod schema;
|
|
||||||
pub mod load;
|
|
||||||
pub mod map;
|
|
66
src/main.rs
66
src/main.rs
|
@ -1,71 +1,9 @@
|
||||||
#[macro_use] extern crate tantivy;
|
mod schema;
|
||||||
|
|
||||||
use teloxide::prelude::*;
|
|
||||||
use log::*;
|
|
||||||
|
|
||||||
mod data;
|
|
||||||
mod search;
|
|
||||||
mod telegram;
|
|
||||||
|
|
||||||
|
|
||||||
/// Run the bot.
|
/// Run the bot.
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
debug!("There's nothing here yet.")
|
||||||
debug!("Loading Set Bundles...");
|
|
||||||
let cards = data::load::load_setbundles_infallible("./data/*/en_us/data/set*-en_us.json");
|
|
||||||
debug!("Loaded {} cards!", &cards.len());
|
|
||||||
|
|
||||||
debug!("Creating Card index...");
|
|
||||||
let card_index = search::index::build_card_index();
|
|
||||||
debug!("Card index created successfully!");
|
|
||||||
|
|
||||||
debug!("Creating Card hashmap...");
|
|
||||||
let card_map = data::map::build_card_code_hashmap(&cards);
|
|
||||||
debug!("Card hashmap contains {} cards!", &card_map.len());
|
|
||||||
|
|
||||||
debug!("Writing Cards to the Card index...");
|
|
||||||
search::index::write_cards_to_index(&card_index, &cards);
|
|
||||||
debug!("Wrote Cards to the Card index!");
|
|
||||||
|
|
||||||
debug!("Creating Card query parser...");
|
|
||||||
let card_query_parser = search::query::build_query_parser(&card_index);
|
|
||||||
debug!("Created Card query parser!");
|
|
||||||
|
|
||||||
debug!("Creating Card reader...");
|
|
||||||
let card_reader = search::index::build_reader(&card_index);
|
|
||||||
debug!("Created Card reader!");
|
|
||||||
|
|
||||||
debug!("Creating Telegram Bot with parameters from the environment...");
|
|
||||||
let bot = Bot::from_env().auto_send();
|
|
||||||
let bot_me = bot.get_me().await.expect("Telegram Bot parameters to be valid");
|
|
||||||
debug!("Created Telegram Bot @{}!", &bot_me.username.as_ref().unwrap());
|
|
||||||
|
|
||||||
let handler = Update::filter_inline_query().branch(dptree::endpoint(move |query: InlineQuery, bot: AutoSend<Bot>| {
|
|
||||||
let card_schema = card_index.schema();
|
|
||||||
let card_query_parser = &card_query_parser;
|
|
||||||
let card_reader = &card_reader;
|
|
||||||
let card_map = &card_map;
|
|
||||||
|
|
||||||
let results = search::query::search_card(
|
|
||||||
&card_schema,
|
|
||||||
&card_query_parser,
|
|
||||||
&card_reader,
|
|
||||||
&card_map,
|
|
||||||
&query.query
|
|
||||||
);
|
|
||||||
|
|
||||||
async move {
|
|
||||||
let reply = results.iter()
|
|
||||||
.map(telegram::result::create_inlinequeryresult);
|
|
||||||
|
|
||||||
if let Err(e) = bot.answer_inline_query(&query.id, reply).send().await {
|
|
||||||
error!("{:?}", e);
|
|
||||||
};
|
|
||||||
respond(())
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
Dispatcher::builder(bot, handler).enable_ctrlc_handler().build().dispatch().await;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
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")
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub mod schema;
|
|
||||||
pub mod index;
|
|
||||||
pub mod query;
|
|
|
@ -1,49 +0,0 @@
|
||||||
use log::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use tantivy::Index;
|
|
||||||
use tantivy::IndexReader;
|
|
||||||
use tantivy::LeasedItem;
|
|
||||||
use tantivy::Searcher;
|
|
||||||
use tantivy::collector::TopDocs;
|
|
||||||
use tantivy::query::QueryParser;
|
|
||||||
use tantivy::schema::Schema;
|
|
||||||
use crate::data::schema::Card;
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
|
|
||||||
pub fn build_query_parser(index: &Index) -> QueryParser {
|
|
||||||
let schema = index.schema();
|
|
||||||
|
|
||||||
let name = schema.get_field("name").unwrap();
|
|
||||||
let description = schema.get_field("description").unwrap();
|
|
||||||
let code = schema.get_field("code").unwrap();
|
|
||||||
|
|
||||||
QueryParser::for_index(&index, vec![name, description, code])
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn build_searcher(reader: &IndexReader) -> LeasedItem<Searcher> {
|
|
||||||
reader.searcher()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn search_card(schema: &Schema, parser: &QueryParser, reader: &IndexReader, map: &HashMap<String, Card>, q: &str) -> Vec<Card> {
|
|
||||||
debug!("Searching for `{}`...", &q);
|
|
||||||
|
|
||||||
let code = schema.get_field("code").unwrap();
|
|
||||||
|
|
||||||
debug!("Building Card searcher...");
|
|
||||||
let searcher = build_searcher(reader);
|
|
||||||
let query = parser.parse_query(q)
|
|
||||||
.expect("to be able to parse the query");
|
|
||||||
let search = searcher.search(&*query, &TopDocs::with_limit(50))
|
|
||||||
.expect("to be able to search for a card");
|
|
||||||
debug!("Retrieved {} results!", &search.len());
|
|
||||||
|
|
||||||
search.iter().filter_map(|(_score, address)| searcher.doc(address.to_owned()).ok())
|
|
||||||
.filter_map(|doc| doc.get_first(code).cloned())
|
|
||||||
.filter_map(|field| field.as_text().map(String::from))
|
|
||||||
.filter_map(|code| map.get(&*code))
|
|
||||||
.cloned()
|
|
||||||
.collect_vec()
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
use itertools::Itertools;
|
|
||||||
use crate::data::schema::{Card, CardType};
|
|
||||||
|
|
||||||
|
|
||||||
pub fn format_card(card: &Card) -> String {
|
|
||||||
match card.card_type {
|
|
||||||
CardType::Spell => format!(
|
|
||||||
"{}{}{}{}\n{}\n{}",
|
|
||||||
format_name(&card.name),
|
|
||||||
format_types(&card.supertype, &card.subtypes),
|
|
||||||
format_keywords(&card.keywords),
|
|
||||||
format_mana(card.cost),
|
|
||||||
format_description(&card.description_raw, &card.levelup_description_raw),
|
|
||||||
format_flavor(&card.assets.get(0).expect("card to have at least one asset").full_absolute_path, &card.flavor_text),
|
|
||||||
),
|
|
||||||
CardType::Unit => format!(
|
|
||||||
"{}{}{}{}{}\n{}\n{}",
|
|
||||||
format_name(&card.name),
|
|
||||||
format_types(&card.supertype, &card.subtypes),
|
|
||||||
format_keywords(&card.keywords),
|
|
||||||
format_mana(card.cost),
|
|
||||||
format_stats(card.attack, card.health),
|
|
||||||
format_description(&card.description_raw, &card.levelup_description_raw),
|
|
||||||
format_flavor(&card.assets.get(0).expect("card to have at least one asset").full_absolute_path, &card.flavor_text),
|
|
||||||
),
|
|
||||||
CardType::Ability => format!(
|
|
||||||
"{}{}{}\n{}\n{}",
|
|
||||||
format_name(&card.name),
|
|
||||||
format_types(&card.supertype, &card.subtypes),
|
|
||||||
format_keywords(&card.keywords),
|
|
||||||
format_description(&card.description_raw, &card.levelup_description_raw),
|
|
||||||
format_flavor(&card.assets.get(0).expect("card to have at least one asset").full_absolute_path, &card.flavor_text),
|
|
||||||
),
|
|
||||||
CardType::Landmark => format!(
|
|
||||||
"{}{}{}{}\n{}\n{}",
|
|
||||||
format_name(&card.name),
|
|
||||||
format_types(&card.supertype, &card.subtypes),
|
|
||||||
format_keywords(&card.keywords),
|
|
||||||
format_mana(card.cost),
|
|
||||||
format_description(&card.description_raw, &card.levelup_description_raw),
|
|
||||||
format_flavor(&card.assets.get(0).expect("card to have at least one asset").full_absolute_path, &card.flavor_text),
|
|
||||||
),
|
|
||||||
CardType::Trap => format!(
|
|
||||||
"{}{}{}\n{}\n{}",
|
|
||||||
format_name(&card.name),
|
|
||||||
format_types(&card.supertype, &card.subtypes),
|
|
||||||
format_keywords(&card.keywords),
|
|
||||||
format_description(&card.description_raw, &card.levelup_description_raw),
|
|
||||||
format_flavor(&card.assets.get(0).expect("card to have at least one asset").full_absolute_path, &card.flavor_text),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn escape(s: &str) -> String {
|
|
||||||
s
|
|
||||||
.replace("&", "&")
|
|
||||||
.replace("<", "<")
|
|
||||||
.replace(">", ">")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn format_name(name: &String) -> String {
|
|
||||||
format!("<b><u>{}</u></b>\n", escape(&name))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn format_types(supertype: &String, subtypes: &Vec<String>) -> String {
|
|
||||||
let mut types = vec![];
|
|
||||||
if supertype != "" {
|
|
||||||
types.push(supertype.to_owned())
|
|
||||||
}
|
|
||||||
let mut source = subtypes.to_owned();
|
|
||||||
types.append(&mut source);
|
|
||||||
|
|
||||||
let mut types = types.iter()
|
|
||||||
.map(|t| escape(&t))
|
|
||||||
.map(|k| k.to_lowercase());
|
|
||||||
|
|
||||||
let typestring = types.join(", ");
|
|
||||||
if typestring == "" {
|
|
||||||
return String::from("")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return format!("<i>{}</i>\n", &typestring)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn format_mana(cost: i8) -> String {
|
|
||||||
format!("{} mana\n", cost)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn format_stats(attack: i8, health: i8) -> String {
|
|
||||||
format!("{}|{}\n", attack, health)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn format_keywords(keywords: &Vec<String>) -> String {
|
|
||||||
if keywords.len() == 0 {
|
|
||||||
return String::from("")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
format!(
|
|
||||||
"{}\n",
|
|
||||||
keywords.iter()
|
|
||||||
.map(|k| escape(&k))
|
|
||||||
.map(|k| k.to_lowercase())
|
|
||||||
.map(|k| format!("[{}]", &k))
|
|
||||||
.join(" ")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn format_description(desc: &String, levelup: &String) -> String {
|
|
||||||
if levelup == "" {
|
|
||||||
if desc == "" {
|
|
||||||
String::from("")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
format!("{}\n", escape(&desc))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
format!("{}\n\n<u>Level up</u>: {}\n", escape(&desc), escape(&levelup))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn format_flavor(full_art: &String, flavor: &String) -> String {
|
|
||||||
format!(r#"<a href="{}"><i>{}</i></a>"#, &full_art, escape(&flavor))
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod format;
|
|
||||||
pub mod result;
|
|
|
@ -1,25 +0,0 @@
|
||||||
use crate::data::schema::Card;
|
|
||||||
use teloxide::types::*;
|
|
||||||
use reqwest::Url;
|
|
||||||
use crate::telegram::format::format_card;
|
|
||||||
|
|
||||||
pub fn create_inlinequeryresult(card: &Card) -> InlineQueryResult {
|
|
||||||
InlineQueryResult::Photo(InlineQueryResultPhoto {
|
|
||||||
id: card.card_code.to_owned(),
|
|
||||||
title: Some(card.name.to_owned()),
|
|
||||||
description: Some(card.description_raw.to_owned()),
|
|
||||||
caption: Some(format_card(&card)),
|
|
||||||
photo_url: Url::parse(
|
|
||||||
&card.assets.get(0).expect("card to have assets").game_absolute_path).expect("card to have a valid asset URL"
|
|
||||||
),
|
|
||||||
thumb_url: Url::parse(
|
|
||||||
&card.assets.get(0).expect("card to have assets").game_absolute_path).expect("card to have a valid asset URL"
|
|
||||||
),
|
|
||||||
photo_width: Some(680),
|
|
||||||
photo_height: Some(1024),
|
|
||||||
parse_mode: Some(ParseMode::Html),
|
|
||||||
caption_entities: None,
|
|
||||||
reply_markup: None,
|
|
||||||
input_message_content: None,
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in a new issue