From 3f9376a46efeb53b494a8b5272fc15be6ca5869a Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Fri, 7 Jan 2022 13:33:31 -0500 Subject: Initial Commit --- src/commands/actions.rs | 109 +++++++++++++++++++++++++++++++ src/commands/meta.rs | 38 +++++++++++ src/commands/mod.rs | 4 ++ src/commands/osu.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++ src/commands/pony.rs | 137 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 454 insertions(+) create mode 100644 src/commands/actions.rs create mode 100644 src/commands/meta.rs create mode 100644 src/commands/mod.rs create mode 100644 src/commands/osu.rs create mode 100644 src/commands/pony.rs (limited to 'src/commands') diff --git a/src/commands/actions.rs b/src/commands/actions.rs new file mode 100644 index 0000000..96862bb --- /dev/null +++ b/src/commands/actions.rs @@ -0,0 +1,109 @@ +use crate::{Context, Error}; +use poise::serenity_prelude as serenity; + +use rand::Rng; + +static HUG_VEC: [&'static str; 48] = [ + "https://media1.tenor.com/images/2d9902a4de4ad000be9ca64c5abac9a1/tenor.gif?itemid=8766309", + "https://media1.tenor.com/images/dd056f2e708792ce8085649eaf0c3307/tenor.gif?itemid=13964315", + "https://media1.tenor.com/images/4081db5de72d7b225be12e764715cb99/tenor.gif?itemid=13964310", + "https://i.kym-cdn.com/photos/images/original/000/360/344/26e.gif", + "https://media.giphy.com/media/t0K54lpOGUmlO/giphy.gif", + "https://i.kym-cdn.com/photos/images/newsfeed/000/464/096/ccb.gif", + "https://i.kym-cdn.com/photos/images/newsfeed/000/988/544/548.gif", + "https://i.gifer.com/IPJ1.gif", + "https://i.kym-cdn.com/photos/images/original/000/503/185/f38.gif", + "https://thumbs.gfycat.com/TepidPrestigiousAmazontreeboa-max-1mb.gif", + "https://thumbs.gfycat.com/SparseContentHypacrosaurus-small.gif", + "https://i.pinimg.com/originals/6d/7d/dc/6d7ddcb24307c502da6fcc17334251db.gif", + "https://i.pinimg.com/originals/16/b3/49/16b349b54d19818a895a2bd8ecebae3e.gif", + "https://66.media.tumblr.com/8e38820abca85c70a645079929471657/tumblr_oopymyHyz71vtnwhpo1_400.gif", + "https://media1.tenor.com/images/0ea597aa8a27770ff072b004a3dfbcdd/tenor.gif?itemid=14521586", + "https://media1.giphy.com/media/tKJqxkFT1y6qY/giphy.gif&key=909e231cc9819f389143bd5645661eac236a26cffc5bf69578000f0d2f8a6403", + "https://buffy.mlpforums.com/monthly_2019_05/2513213.gif.2bd1eb00f85e8c4ec5e212bac52a2815.gif", + "https://i.kym-cdn.com/photos/images/original/000/546/558/e57.gif", + "https://data.whicdn.com/images/333305271/original.gif", + "https://derpicdn.net/img/2014/4/10/597321/full.gif", + "https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/2b92ff77-0420-4b4a-bc2e-6a81f19bb447/d5o7mtq-61d57c24-4e23-4b3b-a67b-91b68ec155ff.gif?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3sicGF0aCI6IlwvZlwvMmI5MmZmNzctMDQyMC00YjRhLWJjMmUtNmE4MWYxOWJiNDQ3XC9kNW83bXRxLTYxZDU3YzI0LTRlMjMtNGIzYi1hNjdiLTkxYjY4ZWMxNTVmZi5naWYifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6ZmlsZS5kb3dubG9hZCJdfQ._cX9QVNRzOu0YXp89DlFun4gwd-lBx3wRBBQhWz0Uog", + "https://mrwgifs.com/wp-content/uploads/2013/05/Fluttershy-Rainbow-Dash-Excited-Hug-On-My-Little-Pony.gif", + "https://i.kym-cdn.com/photos/images/original/001/284/428/e2c.gif", + "https://i.kym-cdn.com/photos/images/original/001/284/454/d15.gif", + "https://i.kym-cdn.com/photos/images/original/001/508/796/105.gif", + "https://i.kym-cdn.com/photos/images/original/000/453/448/604.gif", + "https://pa1.narvii.com/6513/153ac19fe9f83512ee28e93cec3955eb75391e3f_00.gif", + "https://img.cartoongoodies.com/wp-content/uploads/2019/11/07164015/My-Little-Pony-Bear-Hug.gif", + "https://i.imgur.com/9ko6n.gif", + "https://ii.yuki.la/7/7c/8c06bd8120840fcbd02ec9c6babccb2113bba37844ac605c483987259484a7c7.gif", + "https://derpicdn.net/img/view/2017/5/20/1441283.gif", + "https://i.kym-cdn.com/photos/images/original/001/286/795/785.gif", + "https://i1.wp.com/derpicdn.net/img/2014/2/2/541180/full.gif", + "https://thumbs.gfycat.com/GlossyCloseAfricancivet-size_restricted.gif", + "https://i.pinimg.com/originals/d5/bf/21/d5bf21c9e018d40a6e9a3a0273e52ff1.gif", + "https://i.pinimg.com/originals/14/4f/17/144f17e18f9bb5fbd033bf467affd1b4.gif", + "https://i.kym-cdn.com/photos/images/original/000/334/693/430.gif", + "https://i.kym-cdn.com/photos/images/original/000/968/146/1c5.gif", + "https://i.gifer.com/X3Cw.gif", + "https://www.tamatalk.com/IB/uploads/gallery/album_14/gallery_193175_14_257519.gif", + "https://ii.yuki.la/c/92/dd67cf2750331016171f62e86dbd8fb209700e4ddbfe21a3ac668a006dd0892c.gif", + "https://i0.kym-cdn.com/photos/images/original/000/456/998/a7a.gif", + "https://derpicdn.net/img/view/2016/9/11/1247199__safe_screencap_animated_scootaloo_eyes+closed_hug_griffon_the+fault+in+our+cutie+marks_spoiler-colon-s06e19_hovering.gif", + "https://derpicdn.net/img/2012/7/5/32216/full.gif", + "https://i.kym-cdn.com/photos/images/original/001/166/921/31e.gif", + "https://i.kym-cdn.com/photos/images/original/000/513/996/2b1.gif", + "https://i.kym-cdn.com/photos/images/original/001/284/346/fbe.gif", + "https://derpicdn.net/img/2019/7/6/2084796/medium.gif", +]; + +static BOOP_VEC: [&'static str; 15] = [ + "https://i.pinimg.com/originals/8f/67/20/8f6720fb8b277f120658fbceef9303b0.gif", + "https://66.media.tumblr.com/b916203f91fdc2d3a85aef6e1454d785/tumblr_oxljyr11c01w0by9bo1_400.gif", + "https://i.pinimg.com/originals/a6/39/fe/a639fe75e4e15ae4705fe1cff55aa0fe.gif", + "https://i.kym-cdn.com/photos/images/original/001/406/562/2d4.gif", + "https://66.media.tumblr.com/8ce960ad7d7a100ce87027809c41a728/tumblr_opwdkf56Mc1w0by9bo1_400.gif", + "https://i.pinimg.com/originals/92/8b/47/928b477daf42151c0db18edb9221172f.gif", + "https://thumbs.gfycat.com/AgileRectangularArizonaalligatorlizard-small.gif", + "https://buffy.mlpforums.com/monthly_2017_11/img-1331978-1-mlfw2090-87516_-_animated_boop_fluttershy_rainbow_dash.gif.444b4e7258a9e3e82606a312e87a1cec.gif", + "https://i.imgur.com/GInYrc7.gif", + "https://i.pinimg.com/originals/86/1d/49/861d494ec0115bb96892aad8e937a4a3.gif", + "https://i.kym-cdn.com/photos/images/original/001/243/977/85a.gif", + "https://i.pinimg.com/originals/f7/5b/d4/f75bd4ab2ba0a94a742c67a6df10486b.gif", + "https://i.pinimg.com/originals/e7/59/29/e7592972772130c45aaaea1001edb6dc.gif", + "https://i.pinimg.com/originals/aa/60/27/aa60277149600ea29500aa0318d01289.gif", + "https://derpicdn.net/img/view/2018/3/16/1682500__safe_artist-colon-grypher_derpibooru+exclusive_tempest+shadow_my+little+pony-colon-+the+movie_spoiler-colon-my+little+pony+movie_animated_boop.gif", + +]; + +/// Boops the user! D'awwww +/// +/// Usage: +/// ~boop <@User> +#[poise::command(context_menu_command = "Boop!", slash_command, prefix_command)] +pub async fn boop(ctx: Context<'_>, + #[description = "The user to be booped"] user: serenity::User, + ) -> Result<(), Error> { + let url = get_random_url_from_vec(BOOP_VEC.to_vec()); + ctx.say(format!("<@{}> boops <@{}>! Awwwww!\n{}", ctx.author().id.0, user.id.0, url)).await?; + + Ok(()) +} + +/// Hugs the user with a random gif +/// +/// Usage: +/// ~hug <@User> +#[poise::command(context_menu_command = "Hug!", slash_command, prefix_command)] +pub async fn hug(ctx: Context<'_>, + #[description = "The user to be hugged"] user: serenity::User, + ) -> Result<(), Error> { + let url = get_random_url_from_vec(HUG_VEC.to_vec()); + ctx.say(format!("<@{}> hugs <@{}>! So kind of them.\n{}", ctx.author().id.0, user.id.0, url)).await?; + + Ok(()) +} +fn get_random_url_from_vec(vec: Vec<&str>) -> &str { + let mut url = ""; + let rand = rand::thread_rng().gen_range(0..vec.len()); + url = &vec[rand]; + + url.clone() +} diff --git a/src/commands/meta.rs b/src/commands/meta.rs new file mode 100644 index 0000000..d2c5564 --- /dev/null +++ b/src/commands/meta.rs @@ -0,0 +1,38 @@ +use crate::{Context, Error}; +use poise::serenity_prelude as serenity; + +/// Pings the bot +#[poise::command(prefix_command, slash_command)] +pub async fn ping(ctx: Context<'_>) -> Result<(), Error> { + ctx.say("Pong!").await?; + + Ok(()) +} + +/// Shows information about the bot +#[poise::command(prefix_command, slash_command)] +pub async fn about(ctx: Context<'_>) -> Result<(), Error> { + let current_version = env!("CARGO_PKG_VERSION"); + + if let Err(e) = ctx.send(|m| { + m.embed(|e| { + e.title("Glitch"); + + e + }); + m + }).await { + return Err(e.into()); + } + + Ok(()) +} + +#[poise::command(prefix_command, slash_command, context_menu_command = "User Info")] +pub async fn userinfo(ctx: Context<'_>, + #[description = "The user to get info on"] + user: serenity::User, + ) -> Result<(), Error> { + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..50636a3 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,4 @@ +pub mod meta; +pub mod pony; +pub mod actions; +pub mod osu; diff --git a/src/commands/osu.rs b/src/commands/osu.rs new file mode 100644 index 0000000..2b3961f --- /dev/null +++ b/src/commands/osu.rs @@ -0,0 +1,166 @@ +use std::time::Duration; + +use crate::{Context, Error}; +use poise::serenity_prelude as serenity; +use reqwest::{header, ClientBuilder}; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize)] +struct OsuTokenResponse { + pub access_token: String, +} + +#[derive(Serialize)] +struct OsuTokenRequest { + pub client_id: u32, + pub client_secret: String, + pub grant_type: String, + pub scope: String, +} + +async fn setup_reqwest() -> Result { + let client_id = std::env::var("OSU_CLIENT_ID").unwrap(); + let client_secret = std::env::var("OSU_CLIENT_SECRET").unwrap(); + + let token_req = OsuTokenRequest { + client_id: client_id.parse::().unwrap(), + client_secret, + grant_type: "client_credentials".into(), + scope: "public".into(), + }; + + let req = reqwest::Client::new().post("https://osu.ppy.sh/oauth/token") + .json(&token_req).send().await? + .json::().await?; + + + let mut headers = header::HeaderMap::new(); + headers.insert("Authorization", header::HeaderValue::from_str(format!("Bearer {}", req.access_token).as_str()).unwrap()); + + Ok(ClientBuilder::new() + .default_headers(headers) + .build().unwrap()) +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +struct OsuUser { + pub username: String, + pub avatar_url: String, + pub country_code: String, + pub is_supporter: bool, + pub join_date: chrono::DateTime, + pub statistics: OsuUserStats, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +struct OsuUserStats { + pub global_rank: Option, + pub pp: f32, + pub hit_accuracy: Option, + pub grade_counts: OsuUserStatsGrades, + pub country_rank: Option, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +struct OsuUserStatsGrades { + pub ss: u32, + pub s: u32, + pub a: u32, +} + +/// Gets an osu profile by username +/// +/// Usage: +/// ~osup +/// Examples: +/// ~osup muirrum +#[poise::command(slash_command, prefix_command)] +pub async fn osup(ctx: Context<'_>, + #[description = "The osu! username or ID to look up"] + lookup: String, + ) -> Result<(), Error> { + let client = setup_reqwest().await?; + + let mut res = client.get(format!("https://osu.ppy.sh/api/v2/users/{}?key=username", lookup)) + .send().await?.json::().await?; + + res.country_code = res.country_code.to_lowercase(); + + ctx.send(|m| { + m.embed(|e| { + e.title(format!("osu! Profile: {}", res.clone().username)); + e.thumbnail(res.clone().avatar_url); + e.field("Ranks", format!(":map: #{}\n:flag_{}: #{}", res.clone().statistics.global_rank.unwrap_or(0), res.clone().country_code, res.clone().statistics.country_rank.unwrap_or(0u32)), true); + + e.field("Stats", format!("**PP:** {}\n**Acc:** {}%", res.clone().statistics.pp, res.clone().statistics.hit_accuracy.unwrap_or(0.0)), false); + e + }); + m + }).await?; + + Ok(()) +} + +#[derive(Deserialize, Debug, Clone)] +struct OsuBeatMap { + pub id: u32, + pub mode: String, + pub status: String, + pub version: String, + pub total_length: u32, + pub difficulty_rating: f32, + pub bpm: u32, + pub last_updated: chrono::DateTime, + pub passcount: u32, + pub playcount: u32, + pub beatmapset: OsuBeatMapSet, +} + +#[derive(Deserialize, Debug, Clone)] +struct OsuBeatMapSet { + pub id: u32, + pub nsfw: bool, + pub title: String, + pub artist: String, + pub covers: OsuBeatMapSetCovers, + pub creator: String, + pub tags: String, + pub submitted_date: chrono::DateTime, +} + +#[derive(Deserialize, Debug, Clone)] +struct OsuBeatMapSetCovers { + #[serde(rename = "list@2x")] + pub list2: String, +} + +/// Looks up an osu! beatmap by its ID +/// +/// Usage: +/// ~osubm +#[poise::command(slash_command, prefix_command)] +pub async fn osubm(ctx: Context<'_>, + #[description = "The beatmap ID"] + bm_id: u32, + ) -> Result<(), Error> { + let client = setup_reqwest().await?; + + let mut res = client.get(format!("https://osu.ppy.sh/api/v2/beatmaps/{}", bm_id)) + .send().await?.json::().await?; + + ctx.send(|m| { + m.embed(|e| { + e.title(format!("osu! Beatmap: {} by {}", res.beatmapset.title, res.beatmapset.creator)); + e.image(res.beatmapset.covers.list2); + e.description(format!("**Length:** {} **BPM:** {}\n**Difficulty:** {}:star:", res.total_length, res.bpm, res.difficulty_rating)); + e.footer(|f| { + f.text(format!("BM ID {} | BM Set ID {}\nCreated {}", res.id, res.beatmapset.id, res.beatmapset.submitted_date)); + f + }); + e + }); + m + }).await?; + + Ok(()) +} diff --git a/src/commands/pony.rs b/src/commands/pony.rs new file mode 100644 index 0000000..75b4238 --- /dev/null +++ b/src/commands/pony.rs @@ -0,0 +1,137 @@ +use crate::{Context, Error}; +use poise::serenity_prelude as serenity; + +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Deserialize)] +pub struct PonyResponse { + pub pony: Pony, +} + +#[derive(Deserialize)] +pub struct Pony { + pub id: u64, + pub derpiId: Option, + pub tags: Vec, + pub sourceURL: Option, + pub height: u64, + pub width: u64, + pub aspectRatio: f64, + pub mimeType: String, + pub originalFormat: String, + + pub representations: PonyRepresentation, +} + +#[derive(Deserialize)] +pub struct PonyRepresentation { + pub full: String, + pub tall: String, + pub large: String, + pub medium: String, + pub small: String, + pub thumb: String, + pub thumbSmall: String, + pub thumbTiny: String, +} + +/// Retrieves a random SFW pony image +#[poise::command(slash_command, prefix_command)] +pub async fn randpony(ctx: Context<'_>) -> Result<(), Error> { + let mut response_msg = ctx.say("Fetching a random pony image, please wait!").await?; + + let client = reqwest::Client::new(); + let res = reqwest::get("https://theponyapi.com/api/v1/pony/random") + .await? + .json::() + .await?; + + match response_msg.unwrap().message().await { + Ok(mut msg) => { + msg.edit(&ctx.discord(), |m| { + m.content(""); + m.embed(|e| { + e.title("Pony!"); + e.image(res.pony.representations.full.clone()); + let res_tags = get_tags_as_string(res.pony.tags); + e.field("Tags", format!("{:?}", res_tags), true); + e.field("Image URL", res.pony.representations.full.clone(), true); + if let Some(url) = res.pony.sourceURL { + e.field("Source", url, true); + } else { + e.field("Source", "None found", true); + } + e + }); + m + }).await?; + }, + Err(e) => { + ctx.say("Error editing message").await?; + }, + }; + + Ok(()) +} + +/// Get a random pony image based on a set of tags +/// +/// Usage: +/// ~tpony +/// Example: +/// ~tpony twilight sparkle,fluttershy +#[poise::command(slash_command, prefix_command)] +pub async fn tpony(ctx: Context<'_>, + #[description = "List of tags"] + #[rest] + tags: String, + ) -> Result<(), Error> { + let mut response_msg = ctx.say(format!("Fetching pony image based on tags: {:?}", tags.clone())).await?; + + let res = reqwest::get(format!("https://theponyapi.com/api/v1/pony/random?q={}", tags).as_str()) + .await? + .json::() + .await?; + + match response_msg.unwrap().message().await { + Ok(mut msg) => { + msg.edit(&ctx.discord(), |m| { + m.content(""); + m.embed(|e| { + e.title("Pony!"); + e.image(res.pony.representations.full.clone()); + let res_tags = get_tags_as_string(res.pony.tags); + e.field("Tags", format!("{:?}", res_tags), true); + e.field("Image URL", res.pony.representations.full.clone(), true); + if let Some(url) = res.pony.sourceURL { + e.field("Source", url, true); + } else { + e.field("Source", "None found", true); + } + e + }); + m + }).await?; + }, + Err(e) => { + ctx.say("Error editing message").await?; + }, + }; + + + Ok(()) +} + +fn get_tags_as_string(tags: Vec) -> String { + let mut response = String::from(tags.get(0).unwrap()); + for (i, s) in tags.iter().enumerate() { + if i > 0 { + response.push_str(format!(", {}", s).as_str()); + } else { + continue; + } + } + + response +} -- cgit v1.2.3