diff --git a/Cargo.lock b/Cargo.lock index 991cfc9..9d022d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -305,10 +305,12 @@ dependencies = [ "clap", "dotenv", "futures", + "rand", "regex", "serenity", "sqlx", "tokio", + "unicode-segmentation", ] [[package]] @@ -1573,6 +1575,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + [[package]] name = "unicode-width" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index 0a255bf..c35e068 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,6 @@ regex = "1" sqlx = { version = "0.3", default-features = false, features = [ "runtime-tokio", "macros", "sqlite"] } futures = "0.3" tokio = { version = "0.2", features = ["full"] } -dotenv = "0.15" \ No newline at end of file +dotenv = "0.15" +rand = "0.7" +unicode-segmentation = "1.6.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9c31328..9c78b02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,10 +9,14 @@ use serenity::model::channel::Message; use serenity::prelude::{Context, EventHandler, TypeMapKey}; use sqlx::sqlite::SqlitePool; use std::env; +use unicode_segmentation::UnicodeSegmentation; + +const COMMAND_PREFIX: &str = "~"; struct Handler { ah_regex: Regex, best_doctor_regex: Regex, + fufufu_regex: Regex, } impl Default for Handler { @@ -20,6 +24,7 @@ impl Default for Handler { Self { ah_regex: Regex::new(r"A+H{5,}").unwrap(), best_doctor_regex: Regex::new(r"[iI].*(?:best|genius) doc").unwrap(), + fufufu_regex: Regex::new(r"(?:[fF][uU]){3,}").unwrap(), } } } @@ -40,13 +45,46 @@ impl EventHandler for Handler { .say(ctx, "no, u is smol brain doctor...") .await .unwrap(); + } else if self.fufufu_regex.is_match(content) { + message.channel_id.say(ctx, get_desu()).await.unwrap(); } } } +const DESU_STRINGS: &[&str] = &[ + "です。", + "desu~", + "desu.", + r#" +``` +ででででででででででで      すす +     ででで     すすすすすすすすす +    でで  でで      すす +   でで   でで     すすす +  でで           す す +  でで           すすす +   でで           すす +    でで          すす +     でで        すす +```"#, +]; + +fn get_desu() -> &'static str { + use rand::seq::SliceRandom; + use rand::thread_rng; + + DESU_STRINGS.choose(&mut thread_rng()).unwrap() + // // https://imgur.com/a/yOb5n + // messageList.add(channel -> channel.sendMessage(new MessageBuilder() + // .setContent("https://www.youtube.com/watch?v=60mLvBWOMb4").build())); + // messageList.add(channel -> channel.sendFile(Desu.class.getResourceAsStream("/desu/desu.jpg"), "desu.jpg")); + // messageList.add(channel -> channel.sendMessage("desu~")); +} + struct DbConnPool { pool: sqlx::pool::Pool, } + impl DbConnPool { async fn get_heck(&self) -> i32 { sqlx::query!("UPDATE Heck SET count = count + 1") @@ -61,6 +99,7 @@ impl DbConnPool { .count } } + impl TypeMapKey for DbConnPool { type Value = Self; } @@ -70,7 +109,7 @@ async fn main() -> Result<(), Box> { dotenv::dotenv().ok(); let framework = StandardFramework::new() - .configure(|c| c.prefix("~")) + .configure(|c| c.prefix(COMMAND_PREFIX)) .group(&GENERAL_GROUP); // Login with a bot token from the environment @@ -124,7 +163,7 @@ async fn init_pool(pool: &sqlx::pool::Pool) { } #[group] -#[commands(heck)] +#[commands(heck, clap, cube)] struct General; #[command] async fn heck(ctx: &mut Context, msg: &Message) -> CommandResult { @@ -138,3 +177,87 @@ async fn heck(ctx: &mut Context, msg: &Message) -> CommandResult { Ok(()) } + +#[command] +async fn clap(ctx: &mut Context, msg: &Message) -> CommandResult { + let content = msg.content_safe(ctx.cache.clone()).await; + let tokens: Vec<_> = content.split_whitespace().collect(); + let resp = match tokens.len() { + 1 => ":clap:".to_string(), + 2 => "You can't clap a single word." + .split_whitespace() + .collect::>() + .join(" :clap: "), + _ => tokens[1..].join(" :clap: "), + }; + + msg.channel_id.say(ctx, resp).await?; + Ok(()) +} + +#[command] +async fn cube(ctx: &mut Context, msg: &Message) -> CommandResult { + let mut original_msg = msg.content_safe(ctx.cache.clone()).await; + let tokens = original_msg.split_ascii_whitespace().collect::>(); + + let resp = match tokens.len() { + 0 => unreachable!("wat"), + 1 => "What do you wanna cube?".to_string(), + _ => { + let to_cube: &str = &original_msg.split_off(COMMAND_PREFIX.len() + "cube".len() + 1); + let to_cube = UnicodeSegmentation::graphemes(to_cube, true).collect::>(); + let cube_len = to_cube.len(); + let should_reverse = to_cube.last() != to_cube.first(); + + let offset = cube_len / 2; + let mut field = + vec![vec![" ".to_string(); (cube_len + offset) * 2 - 1]; cube_len + offset]; + + draw_diagonal(&mut field, cube_len, offset); + draw_box(&mut field, &to_cube, should_reverse, offset * 2, 0); + draw_box(&mut field, &to_cube, should_reverse, 0, offset); + + let text = field + .iter() + .map(|r| r.join("").trim_end().to_string()) + .collect::>() + .join("\n"); + + ["```\n".to_string(), text, "```".to_string()].concat() + } + }; + + msg.channel_id.say(ctx, resp).await?; + + Ok(()) +} + +fn draw_diagonal(field: &mut Vec>, cube_len: usize, offset: usize) { + let diag_char = "/".to_string(); + for x in 0..offset { + field[offset - x][x * 2] = diag_char.clone(); + field[cube_len - x + offset - 1][x * 2] = diag_char.clone(); + field[offset - x][(x + cube_len - 1) * 2] = diag_char.clone(); + field[cube_len - x + offset - 1][(x + cube_len - 1) * 2] = diag_char.clone(); + } +} + +fn draw_box(field: &mut Vec>, chars: &[&str], should_rev: bool, x: usize, y: usize) { + let word_len = chars.len(); + + // Magic numbers are good as long as they're 1 or 2 right? + for i in 0..word_len { + field[y + i][x] = chars[i].to_string(); + field[y][x + i * 2] = chars[i].to_string(); + field[y + (word_len - 1)][x + (word_len - 1 - i) * 2] = if should_rev { + chars[i].to_string() + } else { + chars[word_len - i - 1].to_string() + }; + field[y + (word_len - 1) - i][x + (word_len - 1) * 2] = if should_rev { + chars[i].to_string() + } else { + chars[word_len - i - 1].to_string() + }; + } +}