From 5836d653a7a7c2ca503af453bba808c95f6ff5f1 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Wed, 3 Aug 2022 15:13:44 +0000 Subject: [PATCH] Start more or less clean --- src/data/load.rs | 45 -------------- src/data/map.rs | 14 ----- src/data/mod.rs | 3 - src/main.rs | 66 +------------------- src/search/index.rs | 41 ------------- src/search/mod.rs | 3 - src/search/query.rs | 49 --------------- src/search/schema.rs | 28 --------- src/telegram/format.rs | 134 ----------------------------------------- src/telegram/mod.rs | 2 - src/telegram/result.rs | 25 -------- 11 files changed, 2 insertions(+), 408 deletions(-) delete mode 100644 src/data/load.rs delete mode 100644 src/data/map.rs delete mode 100644 src/data/mod.rs delete mode 100644 src/search/index.rs delete mode 100644 src/search/mod.rs delete mode 100644 src/search/query.rs delete mode 100644 src/search/schema.rs delete mode 100644 src/telegram/format.rs delete mode 100644 src/telegram/mod.rs delete mode 100644 src/telegram/result.rs diff --git a/src/data/load.rs b/src/data/load.rs deleted file mode 100644 index e223143..0000000 --- a/src/data/load.rs +++ /dev/null @@ -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, LoadingError> { - debug!("Loading Set Bundle {:?}", &path); - let file = std::fs::File::open(path) - .map_err(LoadingError::IO)?; - let data = serde_json::de::from_reader::>(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 { - 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 { - glob::glob(pattern) - .expect("a valid glob") - .filter_map(glob::GlobResult::ok) - .map(load_setbundle_infallible) - .concat() -} diff --git a/src/data/map.rs b/src/data/map.rs deleted file mode 100644 index 7101e8c..0000000 --- a/src/data/map.rs +++ /dev/null @@ -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) -> HashMap { - let mut map = HashMap::::new(); - - for card in cards { - map.insert(card.card_code.to_owned(), card.to_owned()); - } - - map -} diff --git a/src/data/mod.rs b/src/data/mod.rs deleted file mode 100644 index 605c8fc..0000000 --- a/src/data/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod schema; -pub mod load; -pub mod map; diff --git a/src/main.rs b/src/main.rs index dab15e0..e2b0f5c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,71 +1,9 @@ -#[macro_use] extern crate tantivy; - -use teloxide::prelude::*; -use log::*; - -mod data; -mod search; -mod telegram; +mod schema; /// Run the bot. #[tokio::main] async fn main() { pretty_env_logger::init(); - - 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| { - 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; + debug!("There's nothing here yet.") } diff --git a/src/search/index.rs b/src/search/index.rs deleted file mode 100644 index ac7bb3b..0000000 --- a/src/search/index.rs +++ /dev/null @@ -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) -> () { - 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") -} diff --git a/src/search/mod.rs b/src/search/mod.rs deleted file mode 100644 index 6ad594f..0000000 --- a/src/search/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod schema; -pub mod index; -pub mod query; diff --git a/src/search/query.rs b/src/search/query.rs deleted file mode 100644 index 3e72a86..0000000 --- a/src/search/query.rs +++ /dev/null @@ -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 { - reader.searcher() -} - - -pub fn search_card(schema: &Schema, parser: &QueryParser, reader: &IndexReader, map: &HashMap, q: &str) -> Vec { - 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() -} \ No newline at end of file diff --git a/src/search/schema.rs b/src/search/schema.rs deleted file mode 100644 index dbd6fa6..0000000 --- a/src/search/schema.rs +++ /dev/null @@ -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, - ) -} \ No newline at end of file diff --git a/src/telegram/format.rs b/src/telegram/format.rs deleted file mode 100644 index 43e09a4..0000000 --- a/src/telegram/format.rs +++ /dev/null @@ -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!("{}\n", escape(&name)) -} - - -fn format_types(supertype: &String, subtypes: &Vec) -> 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!("{}\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 { - 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\nLevel up: {}\n", escape(&desc), escape(&levelup)) - } -} - - -fn format_flavor(full_art: &String, flavor: &String) -> String { - format!(r#"{}"#, &full_art, escape(&flavor)) -} \ No newline at end of file diff --git a/src/telegram/mod.rs b/src/telegram/mod.rs deleted file mode 100644 index ba8ecac..0000000 --- a/src/telegram/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod format; -pub mod result; \ No newline at end of file diff --git a/src/telegram/result.rs b/src/telegram/result.rs deleted file mode 100644 index 65ffa04..0000000 --- a/src/telegram/result.rs +++ /dev/null @@ -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, - }) -}