diff --git a/Cargo.lock b/Cargo.lock index d5ddd27..8393cc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -102,6 +111,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + [[package]] name = "bumpalo" version = "3.11.1" @@ -145,6 +163,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "chrono-tz" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clap" version = "2.34.0" @@ -234,6 +274,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -302,6 +351,12 @@ dependencies = [ "serde", ] +[[package]] +name = "deunicode" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" + [[package]] name = "digest" version = "0.10.5" @@ -381,11 +436,13 @@ name = "fren" version = "0.1.0" dependencies = [ "config", + "ndm", "rand", "reqwest", "serde", "serenity", "structopt", + "tera", "tokio", "toml", ] @@ -476,6 +533,30 @@ dependencies = [ "wasi", ] +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "h2" version = "0.3.15" @@ -556,6 +637,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + [[package]] name = "hyper" version = "0.14.23" @@ -640,6 +727,24 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -810,6 +915,19 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndm" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e810798e353350b02c1d5ef5ac7bde5ace82c731a930452abd7c2b99f7b72" +dependencies = [ + "lazy_static", + "rand", + "regex", + "serde", + "serde_json", +] + [[package]] name = "nom" version = "7.1.1" @@ -942,6 +1060,15 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "pathdiff" version = "0.2.1" @@ -998,6 +1125,45 @@ dependencies = [ "sha1", ] +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", + "uncased", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1103,6 +1269,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1219,6 +1402,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.20" @@ -1384,6 +1576,12 @@ dependencies = [ "digest", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.7" @@ -1393,6 +1591,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -1476,6 +1683,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "tera" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df578c295f9ec044ff1c829daf31bb7581d5b3c2a7a3d87419afe1f2531438c" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1514,6 +1743,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.3.17" @@ -1713,6 +1951,65 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "version_check", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.6.0" @@ -1803,6 +2100,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index c71a3d6..03010a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ reqwest = "0.11.12" serde = "1.0.147" toml = "0.5.9" rand = "0.8.5" +tera = "1.17.1" +ndm = "0.9.9" [dependencies.serenity] version = "0.11.5" diff --git a/src/config.rs b/src/config.rs index 7a46f45..0edfb4e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,7 @@ use serenity::model::prelude::UserId; use serenity::prelude::TypeMapKey; use std::path::{Path, PathBuf}; use structopt::StructOpt; +use tera::Tera; #[derive(Debug, StructOpt)] #[structopt(name = "fren", about = "Friend Bot")] @@ -45,9 +46,10 @@ impl BotConfig { } } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct BotState { pub accepted_nsfw: Option, + pub fortune_templates: Tera, } #[derive(Debug)] @@ -57,6 +59,27 @@ pub struct GlobalData { pub bot_state: BotState, } +impl GlobalData { + pub fn new(args: Args, cfg: BotConfig) -> Self { + let mut fortune_templates = Tera::default(); + + for (idx, fortune) in cfg.fortunes.iter().enumerate() { + fortune_templates + .add_raw_template(&idx.to_string(), fortune) + .unwrap(); + } + + Self { + args, + cfg, + bot_state: BotState { + accepted_nsfw: None, + fortune_templates, + }, + } + } +} + impl TypeMapKey for GlobalData { type Value = GlobalData; } diff --git a/src/discord/album.rs b/src/discord/album.rs index a63e1a3..e92d5b1 100644 --- a/src/discord/album.rs +++ b/src/discord/album.rs @@ -1,8 +1,6 @@ use crate::config::AlbumConfig; use crate::error::Error; -use crate::imgur::Image; use crate::{command, group, imgur, GlobalData}; -use rand::prelude::SliceRandom; use serenity::client::Context; use serenity::framework::standard::{Args, CommandResult}; use serenity::model::channel::Message; @@ -132,7 +130,7 @@ pub async fn parse_album( .find(|album| album.name.to_lowercase() == album_name); if let Some(album) = album { - match get_image(album, global_data, tags).await { + match imgur::get_image(album, global_data, tags).await { Ok(image) => { if let Some(image) = image { msg.reply(&ctx.http, &image.link).await?; @@ -149,35 +147,3 @@ pub async fn parse_album( Ok(()) } - -async fn get_image( - album_config: &AlbumConfig, - global_data: &GlobalData, - tags: Vec<&str>, -) -> Result, Error> { - let album = - imgur::get_album_images(&global_data.cfg.imgur_client_id, &album_config.album_id).await?; - let mut rng = rand::thread_rng(); - - let album = if tags.is_empty() { - album - } else { - album - .iter() - .filter(|img| { - for tag in &tags { - if let Some(desc) = &img.description { - if desc.to_lowercase().contains(&tag.to_lowercase()) { - return true; - } - } - } - - false - }) - .cloned() - .collect() - }; - - Ok(album.choose(&mut rng).cloned()) -} diff --git a/src/discord/joke.rs b/src/discord/joke.rs index 434d92a..34ba432 100644 --- a/src/discord/joke.rs +++ b/src/discord/joke.rs @@ -1,11 +1,14 @@ +use crate::error::Error; +use crate::imgur::get_image; use crate::{command, group, GlobalData}; -use rand::prelude::SliceRandom; +use rand::prelude::IteratorRandom; use rand::thread_rng; use reqwest::Client; use serde::{Deserialize, Serialize}; use serenity::client::Context; use serenity::framework::standard::{Args, CommandResult}; use serenity::model::channel::Message; +use std::collections::HashMap; #[derive(Clone, Serialize, Deserialize)] struct DadJoke { @@ -38,23 +41,63 @@ async fn dad_joke(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { Ok(()) } +#[derive(Serialize, Deserialize)] +struct FortuneCtx { + user: serenity::model::guild::Member, + random_image: HashMap, +} + +impl FortuneCtx { + pub async fn new( + user: serenity::model::guild::Member, + global_data: &GlobalData, + ) -> Result { + let mut random_image: HashMap = HashMap::new(); + + for album in &global_data.cfg.albums { + let image = get_image(album, global_data, Vec::new()).await?; + + if let Some(image) = image { + random_image.insert(album.name.clone(), image.link); + } + } + + Ok(Self { user, random_image }) + } +} + #[command] #[only_in(guilds)] #[aliases("8ball")] -#[description("Ask your dad")] +#[description("Use as your own risk")] async fn fortune(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let data = ctx.data.read().await; let global_data = data.get::().unwrap(); - let fortune = { + let fortune_template = { let mut rng = thread_rng(); - - global_data.cfg.fortunes.choose(&mut rng) + global_data + .bot_state + .fortune_templates + .get_template_names() + .choose(&mut rng) }; - let reply = match fortune { - None => "Sorry kid, all out of fortunes.".to_string(), - Some(msg) => msg.clone(), + let guild_member = msg + .guild(&ctx.cache) + .unwrap() + .member(&ctx.http, msg.author.id) + .await?; + + let fortune_ctx = FortuneCtx::new(guild_member, global_data).await?; + + let reply = if let Some(fortune_template) = fortune_template { + global_data.bot_state.fortune_templates.render( + fortune_template, + &tera::Context::from_serialize(&fortune_ctx)?, + )? + } else { + "Sorry kid, all out of fortunes.".to_string() }; msg.reply(&ctx.http, reply).await?; diff --git a/src/error.rs b/src/error.rs index b03b54d..44385e9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,14 +1,19 @@ use crate::imgur::ImgurError; +use serde::ser::StdError; use std::fmt::{Display, Formatter}; #[derive(Debug)] #[allow(clippy::enum_variant_names)] +#[allow(dead_code)] pub enum Error { ConfigError(config::ConfigError), ImgurError(ImgurError), SerenityError(serenity::Error), + TeraError(tera::Error), } +impl StdError for Error {} + impl From for Error { fn from(e: config::ConfigError) -> Self { Self::ConfigError(e) @@ -33,6 +38,7 @@ impl Display for Error { Error::ConfigError(e) => write!(f, "Config error: {}", e), Error::ImgurError(e) => write!(f, "Imgur error: {}", e), Error::SerenityError(e) => write!(f, "Discord error: {}", e), + Error::TeraError(e) => write!(f, "Tera error: {}", e), } } } diff --git a/src/imgur/mod.rs b/src/imgur/mod.rs index ce0dfd2..7ce8b54 100644 --- a/src/imgur/mod.rs +++ b/src/imgur/mod.rs @@ -1,3 +1,7 @@ +use crate::config::AlbumConfig; +use crate::error::Error; +use crate::GlobalData; +use rand::prelude::SliceRandom; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; @@ -71,3 +75,34 @@ pub async fn get_album_images(client_id: &str, album_hash: &str) -> Result, +) -> Result, Error> { + let album = get_album_images(&global_data.cfg.imgur_client_id, &album_config.album_id).await?; + let mut rng = rand::thread_rng(); + + let album = if tags.is_empty() { + album + } else { + album + .iter() + .filter(|img| { + for tag in &tags { + if let Some(desc) = &img.description { + if desc.to_lowercase().contains(&tag.to_lowercase()) { + return true; + } + } + } + + false + }) + .cloned() + .collect() + }; + + Ok(album.choose(&mut rng).cloned()) +} diff --git a/src/main.rs b/src/main.rs index 72518e9..b5c3a4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod discord; mod error; mod imgur; -use crate::config::{Args, BotConfig, BotState, GlobalData}; +use crate::config::{Args, BotConfig, GlobalData}; use crate::discord::unrecognised_command_hook; use serenity::framework::standard::macros::{command, group, help, hook}; use serenity::framework::standard::StandardFramework; @@ -22,11 +22,7 @@ async fn main() { } }; - let global_data = GlobalData { - args, - cfg, - bot_state: BotState::default(), - }; + let global_data = GlobalData::new(args, cfg); let framework = StandardFramework::new() .configure(|c| c.with_whitespace(true).prefix("!").ignore_bots(true))