From e8c8f44952f2b37e0f5cea7ba10aef70f758a43f Mon Sep 17 00:00:00 2001 From: Chen-Pang He Date: Sun, 5 May 2024 06:02:15 +0800 Subject: [PATCH] Update dependencies --- Cargo.toml | 19 ++++---- src/core.rs | 16 ++++--- src/fun/mod.rs | 63 ++++++++++++++----------- src/fun/poem.rs | 111 ++++++++++++++++++++------------------------ src/information.rs | 27 +++++------ src/main.rs | 32 +++++++------ src/tools/base64.rs | 69 ++++++++++----------------- src/tools/mod.rs | 28 +++++------ src/topgg.rs | 13 +++--- src/weeb.rs | 60 ++++++++++++++++-------- 10 files changed, 221 insertions(+), 217 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cbf7cb3..6e2e846 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "natsuki" -version = "3.0.2" +version = "3.0.3" authors = [ "Chen-Pang He (https://jdh8.org)", "u/YourWrstNightmare (https://www.reddit.com/user/YourWrstNightmare)", @@ -15,22 +15,21 @@ keywords = ["discord", "ddlc"] [dependencies] anyhow = "1.0.75" -base64 = "0.21.3" +base64 = "0.22.1" bitflags = "2.4.0" chrono = "0.4.30" csscolorparser = "0.6.2" futures = "0.3.28" -image = "0.24.7" +image = "0.25.0" md5 = "0.7.0" -poise = "0.5.6" +poise = { version = "0.6.1", features = ["collector"] } rand = "0.8.5" regex = "1.9.5" -reqwest = "0.11.20" +reqwest = { version = "0.12.4", features = ["json"] } serde_json = "1.0.105" -shuttle-poise = "0.35.2" -shuttle-runtime = "0.35.2" -shuttle-secrets = "0.35.2" -strum = { version = "0.25", features = ["derive"] } +shuttle-runtime = "0.44.0" +shuttle-serenity = "0.44.0" +strum = { version = "0.26", features = ["derive"] } tokio = { version = "1.32.0", features = ["full"] } tree_magic_mini = "3.0.3" -webp = "0.2.5" +webp = "0.3.0" diff --git a/src/core.rs b/src/core.rs index b46884f..1f97bd0 100644 --- a/src/core.rs +++ b/src/core.rs @@ -29,14 +29,16 @@ pub async fn ping(ctx: Context<'_>) -> anyhow::Result<()> { let reply = ctx.say("Pong!").await?; let duration = tick.elapsed(); - reply.edit(ctx, |m| m.content( - if duration < std::time::Duration::from_secs(1) { - format!("Pong! {} ms", duration.as_millis()) - } else { - format!("Pong! {:.3} s", duration.as_secs_f32()) - } - )).await?; + let message = if duration < std::time::Duration::from_secs(1) { + format!("Pong! {} ms", duration.as_millis()) + } else { + format!("Pong! {:.3} s", duration.as_secs_f32()) + }; + reply.edit(ctx, poise::CreateReply { + content: Some(message), + ..Default::default() + }).await?; Ok(()) } diff --git a/src/fun/mod.rs b/src/fun/mod.rs index a7bddb5..cbd7441 100644 --- a/src/fun/mod.rs +++ b/src/fun/mod.rs @@ -85,14 +85,16 @@ pub async fn cupcake(ctx: Context<'_>, let cake = image::open("assets/290px-Hostess-Cupcake-Whole.jpg")?.into_rgba8(); let cake: image::RgbImage = blend_image(cake, &face, 80, 80).convert(); let cake = webp::Encoder::from_rgb(&cake, cake.width(), cake.height()); + let cake = cake.encode(90.0).to_vec(); - ctx.send(|m| m - .content(format!("{} has been turned into a cupcake. IT LOOKS SO CUUUUTE!", target.mention())) - .attachment(serenity::model::channel::AttachmentType::Bytes { - data: cake.encode(90.0).to_vec().into(), - filename: "cupcake.webp".into(), - }) - ).await?; + ctx.send(poise::CreateReply { + content: Some(format!("{} has been turned into a cupcake. IT LOOKS SO CUUUUTE!", target.mention())), + attachments: vec![serenity::CreateAttachment::bytes( + cake, + "cupcake.webp", + )], + ..Default::default() + }).await?; Ok(()) } @@ -105,24 +107,24 @@ pub async fn cupcake(ctx: Context<'_>, pub async fn cute(ctx: Context<'_>) -> anyhow::Result<()> { let mut content = "Don't say this embarrassing thing, dummy!".to_owned(); let reply = ctx.say(&content).await?; - let typing = ctx.serenity_context().http.start_typing(ctx.channel_id().0); + let typing = ctx.serenity_context().http.start_typing(ctx.channel_id()); content.push_str("\nY-You t-too...."); sleep(Duration::from_secs(3)).await; - reply.edit(ctx, |m| m.content(&content)).await?; + reply.edit(ctx, poise::CreateReply { content: Some(content.clone()), ..Default::default() }).await?; content.push_str("\nI'M NOT CUUUUUUUUUUUTE!"); sleep(Duration::from_secs(2)).await; - reply.edit(ctx, |m| m.content(&content)).await?; + reply.edit(ctx, poise::CreateReply { content: Some(content.clone()), ..Default::default() }).await?; content.push_str("\nDon't think you can make me say this embarrassing thing just because we're not at school!"); sleep(Duration::from_secs(2)).await; - reply.edit(ctx, |m| m.content(&content)).await?; + reply.edit(ctx, poise::CreateReply { content: Some(content.clone()), ..Default::default() }).await?; - let _ = typing.map(serenity::Typing::stop); + typing.stop(); content.push_str("\nI-I have to go to the bathroom."); sleep(Duration::from_secs(4)).await; - reply.edit(ctx, |m| m.content(&content)).await?; + reply.edit(ctx, poise::CreateReply { content: Some(content.clone()), ..Default::default() }).await?; Ok(()) } @@ -221,16 +223,19 @@ async fn fuck(ctx: Context<'_>, user: Option<&serenity::User>) -> anyhow::Result }.convert(); let image = webp::Encoder::from_rgb(&image, image.width(), image.height()); + let image = image.encode(90.0).to_vec(); - ctx.send(|m| m - .content(format!("{} fucked {}!", + ctx.send(poise::CreateReply { + content: Some(format!("{} fucked {}!", ctx.author().mention(), - user.map_or_else(|| "Natsuki".to_owned(), |u| u.mention().to_string()))) - .attachment(serenity::model::channel::AttachmentType::Bytes { - data: image.encode(90.0).to_vec().into(), - filename: "fuck.webp".into(), - }) - ).await?; + user.map_or_else(|| "Natsuki".to_owned(), |u| u.mention().to_string())) + ), + attachments: vec![serenity::CreateAttachment::bytes( + image, + "fuck.webp", + )], + ..Default::default() + }).await?; Ok(()) } @@ -247,14 +252,16 @@ pub async fn smash(ctx: Context<'_>, if ctx.channel_id().to_channel(&ctx).await?.is_nsfw() { return fuck(ctx, user.as_ref()).await; } - let author = ctx.author().mention(); - ctx.send(|m| m.embed(|e| e - .description(user.map_or_else( - || format!("{author} smashed!"), - |u| format!("{author} smashed {}!", u.mention()))) - .image("https://raw.githubusercontent.com/jdh8/natsuki/master/assets/smash.png")) - ).await?; + ctx.send(poise::CreateReply { + embeds: vec![serenity::CreateEmbed::new() + .description(user.map_or_else( + || format!("{author} smashed!"), + |u| format!("{author} smashed {}!", u.mention()))) + .image("https://raw.githubusercontent.com/jdh8/natsuki/master/assets/smash.png") + ], + ..Default::default() + }).await?; Ok(()) } diff --git a/src/fun/poem.rs b/src/fun/poem.rs index aeec630..2a32328 100644 --- a/src/fun/poem.rs +++ b/src/fun/poem.rs @@ -260,27 +260,33 @@ enum Doki { Monika, } +fn make_buttons( + buttons: &[(EmojiId, Doki)], + style: impl Fn(Doki) -> serenity::ButtonStyle, + disabled: bool, +) -> serenity::CreateActionRow { + serenity::CreateActionRow::Buttons( + buttons.iter().copied().map(|(emoji, doki)| + serenity::CreateButton::new(doki.to_string()) + .style(style(doki)) + .emoji(emoji) + .label(doki.to_string()) + .disabled(disabled) + ).collect() + ) +} + async fn game(ctx: Context<'_>, word: &str, answer: Doki, buttons: &[(EmojiId, Doki)]) -> anyhow::Result<()> { - let mut question = ctx.send(|m| m - .content("Whose word is **".to_owned() + word + "**? Please answer in 15 seconds.") - //XXX Try to deduplicate these - .components(|c| c.create_action_row(|a| { - for (emoji, doki) in buttons { - a.create_button(|b| b - .style(Secondary) - .emoji(*emoji) - .label(doki) - .custom_id(doki) - ); - } - a - })) - ).await?.into_message().await?; + let content = Some("Whose word is **".to_owned() + word + "**? Please answer in 15 seconds."); + let question = ctx.send(poise::CreateReply { + content, + components: Some(vec![make_buttons(buttons, |_| Secondary, false)]), + ..Default::default() + }).await?; - let collected = serenity::CollectComponentInteraction::new(ctx) + let collected = serenity::ComponentInteractionCollector::new(ctx) .author_id(ctx.author().id) - .message_id(question.id) - .collect_limit(1) + .message_id(question.message().await?.id) .timeout(Duration::from_secs(15)) .await; @@ -289,46 +295,29 @@ async fn game(ctx: Context<'_>, word: &str, answer: Doki, buttons: &[(EmojiId, D let right = "Congratulations! That's correct.".to_owned(); let wrong = format!("Sorry, it's **{answer}**."); let selected = interaction.data.custom_id.parse::()?; - - let response = interaction.create_interaction_response(ctx, |m| m - .kind(serenity::InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|r| r - .content(if selected == answer { right } else { wrong })) - ); - - let edit = question.edit(ctx, |m| m - //XXX Try to deduplicate these - .components(|c| c.create_action_row(|a| { - for (emoji, doki) in buttons { - a.create_button(|b| b - .emoji(*emoji) - .label(doki) - .custom_id(doki) - .style(if doki == &answer { Success } - else if doki == &selected { Danger } - else { Secondary }) - ); - } - a - }))); - + + let response = interaction.create_response(ctx, serenity::CreateInteractionResponse::Message( + serenity::CreateInteractionResponseMessage::new() + .content(if selected == answer { right } else { wrong }) + )); + let style = |doki: Doki| { + if doki == answer { Success } + else if doki == selected { Danger } + else { Secondary } + }; + let edit = question.edit(ctx, poise::CreateReply { + //content, + components: Some(vec![make_buttons(buttons, style, false)]), + ..Default::default() + }); try_join!(response, edit)?; }, None => { - question.edit(ctx, |m| m - //XXX Try to deduplicate these - .components(|c| c.create_action_row(|a| { - for (emoji, doki) in buttons { - a.create_button(|b| b - .emoji(*emoji) - .label(doki) - .custom_id(doki) - .disabled(true) - .style(Secondary) - ); - } - a - }))).await?; + question.edit(ctx, poise::CreateReply { + //content, + components: Some(vec![make_buttons(buttons, |_| Secondary, true)]), + ..Default::default() + }).await?; } } Ok(()) @@ -341,9 +330,9 @@ async fn poem1(ctx: Context<'_>) -> anyhow::Result<()> { else { Doki::Natsuki }; game(ctx, word, doki, &[ - (EmojiId(424_991_418_386_350_081), Doki::Sayori), - (EmojiId(424_991_419_329_937_428), Doki::Natsuki), - (EmojiId(424_987_242_986_078_218), Doki::Yuri), + (EmojiId::new(424_991_418_386_350_081), Doki::Sayori), + (EmojiId::new(424_991_419_329_937_428), Doki::Natsuki), + (EmojiId::new(424_987_242_986_078_218), Doki::Yuri), ]).await } @@ -352,13 +341,13 @@ async fn poem2(ctx: Context<'_>) -> anyhow::Result<()> { let doki = if preference.contains(Preference::Yuri) { Doki::Yuri } else { Doki::Natsuki }; game(ctx, word, doki, &[ - (EmojiId(424_991_419_329_937_428), Doki::Natsuki), - (EmojiId(424_987_242_986_078_218), Doki::Yuri), + (EmojiId::new(424_991_419_329_937_428), Doki::Natsuki), + (EmojiId::new(424_987_242_986_078_218), Doki::Yuri), ]).await } async fn poem3(ctx: Context<'_>) -> anyhow::Result<()> { - game(ctx, "Monika", Doki::Monika, &[(EmojiId(501_274_687_842_680_832), Doki::Monika)]).await + game(ctx, "Monika", Doki::Monika, &[(EmojiId::new(501_274_687_842_680_832), Doki::Monika)]).await } /// Play a poem game diff --git a/src/information.rs b/src/information.rs index 60b75e9..302a146 100644 --- a/src/information.rs +++ b/src/information.rs @@ -87,13 +87,13 @@ pub async fn snowflake(ctx: Context<'_>, #[poise::command(context_menu_command = "Snowflake (user)")] pub async fn snowflake_user(ctx: Context<'_>, user: serenity::User) -> anyhow::Result<()> { - ctx.say(format_snowflake(user.id.0.into())).await?; + ctx.say(format_snowflake(user.id.get().into())).await?; Ok(()) } #[poise::command(context_menu_command = "Snowflake (message)")] pub async fn snowflake_message(ctx: Context<'_>, message: serenity::Message) -> anyhow::Result<()> { - ctx.say(format_snowflake(message.id.0.into())).await?; + ctx.say(format_snowflake(message.id.get().into())).await?; Ok(()) } @@ -107,20 +107,21 @@ pub async fn role(ctx: Context<'_>, #[description = "Role to inspect"] role: serenity::Role, ) -> anyhow::Result<()> { - ctx.send(|m| m - .embed(|e| e + ctx.send(poise::CreateReply { + embeds: vec![serenity::CreateEmbed::new() .title("Role ".to_owned() + &role.name) .color(role.colour) .field("Name", role.name, false) - .field("ID", role.id, false) + .field("ID", role.id.to_string(), false) .field("Color", "#".to_owned() + &role.colour.hex(), false) - .field("Hoisted", role.hoist, false) - .field("Managed", role.managed, false) - .field("Mentionable", role.mentionable, false) - .field("Permissions", role.permissions, false) - .field("Position", role.position, false) - .field("Created at", format_rfc3339(Snowflake(role.id.0).time()), false) - ) - ).await?; + .field("Hoisted", role.hoist.to_string(), false) + .field("Managed", role.managed.to_string(), false) + .field("Mentionable", role.mentionable.to_string(), false) + .field("Permissions", role.permissions.to_string(), false) + .field("Position", role.position.to_string(), false) + .field("Created at", format_rfc3339(Snowflake(role.id.get()).time()), false) + ], + ..Default::default() + }).await?; Ok(()) } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 7354599..3ab3b35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,7 @@ mod tools; mod weeb; mod topgg; use poise::serenity_prelude as serenity; - -#[macro_export] -macro_rules! bot_id { - () => { 410_315_411_695_992_833 }; -} +use anyhow::Context as _; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Data; @@ -59,13 +55,12 @@ fn get_commands() -> Vec> { #[shuttle_runtime::main] async fn main( - #[shuttle_secrets::Secrets] secrets: shuttle_secrets::SecretStore, -) -> shuttle_poise::ShuttlePoise { + #[shuttle_runtime::Secrets] secrets: shuttle_runtime::SecretStore, +) -> shuttle_serenity::ShuttleSerenity { let poster = topgg::Poster::new(secrets.get("TOP_GG_TOKEN")); + let token = secrets.get("TOKEN").context("Discord token not found")?; - let builder = poise::Framework::builder() - .token(secrets.get("TOKEN").expect("Discord token not found")) - .intents(serenity::GatewayIntents::non_privileged()) + let framework = poise::Framework::builder() .options(poise::FrameworkOptions { commands: secrets.get("CLEAR").map_or_else(get_commands, |_| Vec::new()), ..Default::default() @@ -75,7 +70,7 @@ async fn main( let commands = &framework.options().commands; match secrets.get("GUILD") { Some(id) => { - let guild = serenity::GuildId(id.parse::()?); + let guild = serenity::GuildId::new(id.parse::()?); poise::builtins::register_in_guild(ctx, commands, guild).await?; }, None => poise::builtins::register_globally(ctx, commands).await?, @@ -83,7 +78,18 @@ async fn main( Ok(Data) }) }) - .client_settings(|client| client.event_handler(poster)); + //.client_settings(|client| client.event_handler(poster)) + .build(); - Ok(builder.build().await.map_err(shuttle_runtime::CustomError::new)?.into()) + Ok(serenity::ClientBuilder::new(token, serenity::GatewayIntents::non_privileged()) + .framework(framework) + .event_handler(poster) + .await + .map_err(shuttle_runtime::CustomError::new)? + .into()) +} + +#[macro_export] +macro_rules! bot_id { + () => { 410_315_411_695_992_833 }; } \ No newline at end of file diff --git a/src/tools/base64.rs b/src/tools/base64.rs index fa26c6e..26f146d 100644 --- a/src/tools/base64.rs +++ b/src/tools/base64.rs @@ -1,6 +1,6 @@ use crate::Context; use base64::{Engine as _, engine}; -use futures::StreamExt as _; +use futures::TryStreamExt as _; use futures::stream::FuturesOrdered; use poise::serenity_prelude as serenity; @@ -40,24 +40,15 @@ pub async fn base64(_: Context<'_>) -> anyhow::Result<()> { unimplemented!("Unimplemented prefix command") } -#[derive(Debug, Clone)] -struct AttachmentData { - data: Vec, - filename: String, -} - -async fn encode_attachment(attachment: serenity::Attachment) -> anyhow::Result { +async fn encode_attachment(attachment: serenity::Attachment) -> anyhow::Result { let code = ENGINE.encode(attachment.download().await?); let code = code.as_bytes().iter().enumerate() .flat_map(|(i, c)| { if i != 0 && i % 76 == 0 { Some(b'\n') } else { None } .into_iter().chain(Some(*c)) }); - - Ok(AttachmentData { - data: code.collect(), - filename: attachment.filename + ".b64.txt", - }) + let code: Vec<_> = code.collect(); + Ok(serenity::CreateAttachment::bytes(code, attachment.filename + ".b64.txt")) } fn encode_embed(mut embed: serenity::Embed) -> serenity::CreateEmbed { @@ -84,22 +75,18 @@ fn encode_embed(mut embed: serenity::Embed) -> serenity::CreateEmbed { #[poise::command(context_menu_command = "Base64 encode")] pub async fn base64_encode(ctx: Context<'_>, message: serenity::Message) -> anyhow::Result<()> { - let _typing = ctx.serenity_context().http.start_typing(ctx.channel_id().0); + let _typing = ctx.serenity_context().http.start_typing(ctx.channel_id()); let attachments = message.attachments.into_iter().map(encode_attachment); - let attachments: Vec<_> = attachments.collect::>().collect().await; - - ctx.send(|m| { - for c in attachments.into_iter().flatten() { - m.attachment(serenity::AttachmentType::Bytes { - data: c.data.into(), - filename: c.filename, - }); - } - m.embeds = message.embeds.into_iter() - .filter(|e| matches!(e.kind.as_deref(), Some("rich") | None)) + let attachments: FuturesOrdered<_> = attachments.collect(); + + ctx.send(poise::CreateReply { + attachments: attachments.try_collect().await?, + embeds: message.embeds.into_iter() + .filter(|e| !matches!(e.kind.as_deref(), Some("rich") | None)) .map(encode_embed) - .collect(); - m.content(ENGINE.encode(message.content)) + .collect(), + content: Some(ENGINE.encode(message.content)), + ..Default::default() }).await?; Ok(()) } @@ -157,14 +144,10 @@ fn guess_extension(bytes: &[u8]) -> &'static str { }) } -async fn decode_attachment(attachment: serenity::Attachment) -> anyhow::Result { +async fn decode_attachment(attachment: serenity::Attachment) -> anyhow::Result { let buffer = forgiving_decode(attachment.download().await?)?; let extension = guess_extension(&buffer); - - Ok(AttachmentData { - data: buffer, - filename: attachment.filename + "." + extension, - }) + Ok(serenity::CreateAttachment::bytes(buffer, attachment.filename + "." + extension)) } fn decode_embed(mut embed: serenity::Embed) -> anyhow::Result { @@ -191,20 +174,16 @@ fn decode_embed(mut embed: serenity::Embed) -> anyhow::Result, message: serenity::Message) -> anyhow::Result<()> { - let _typing = ctx.serenity_context().http.start_typing(ctx.channel_id().0); + let _typing = ctx.serenity_context().http.start_typing(ctx.channel_id()); let text = String::from_utf8(forgiving_decode(message.content)?)?; let attachments = message.attachments.into_iter().map(decode_attachment); - let attachments: Vec<_> = attachments.collect::>().collect().await; - - ctx.send(|m| { - for a in attachments.into_iter().flatten() { - m.attachment(serenity::AttachmentType::Bytes { - data: a.data.into(), - filename: a.filename, - }); - } - m.embeds = message.embeds.into_iter().flat_map(decode_embed).collect(); - m.content(text) + let attachments: FuturesOrdered<_> = attachments.collect(); + + ctx.send(poise::CreateReply { + attachments: attachments.try_collect().await?, + embeds: message.embeds.into_iter().flat_map(decode_embed).collect(), + content: Some(text), + ..Default::default() }).await?; Ok(()) } \ No newline at end of file diff --git a/src/tools/mod.rs b/src/tools/mod.rs index 99b140b..ed05e62 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -4,6 +4,7 @@ use csscolorparser::Color; use rand::seq::IteratorRandom as _; use regex::{Captures, Regex}; use poise::serenity_prelude as serenity; +use anyhow::Context as _; fn to_hsl_string(color: &Color) -> String { let (h, s, l, a) = color.to_hsla(); @@ -22,22 +23,24 @@ pub async fn color(ctx: Context<'_>, #[description = "Color to display"] color: String, ) -> anyhow::Result<()> { - let _typing = ctx.serenity_context().http.start_typing(ctx.channel_id().0); + let _typing = ctx.serenity_context().http.start_typing(ctx.channel_id()); let color = csscolorparser::parse(&color)?; let pixel = image::Rgba(color.to_rgba8()); let image = image::ImageBuffer::from_pixel(128, 128, pixel); let image = webp::Encoder::from_rgba(&image, image.width(), image.height()); + let image = image.encode_lossless().to_vec(); - ctx.send(|m| m - .content("**Hex:** ".to_owned() + &color.to_hex_string() + ctx.send(poise::CreateReply { + content: Some("**Hex:** ".to_owned() + &color.to_hex_string() + "\n**RGB:** " + &color.to_rgb_string() + "\n**HSL:** " + &to_hsl_string(&color) - ) - .attachment(serenity::model::channel::AttachmentType::Bytes { - data: image.encode_lossless().to_vec().into(), - filename: "color.webp".into(), - }) - ).await?; + ), + attachments: vec![serenity::CreateAttachment::bytes( + image, + "color.webp", + )], + ..Default::default() + }).await?; Ok(()) } @@ -115,9 +118,8 @@ pub async fn keycaps(ctx: Context<'_>, #[poise::command(category = "Tools", slash_command, guild_only)] pub async fn someone(ctx: Context<'_>) -> anyhow::Result<()> { let channel = ctx.channel_id().to_channel(ctx).await?.guild(); - let channel = channel.expect("/someone only works in guilds"); - let member = channel.members(ctx).await?.into_iter().choose(&mut rand::thread_rng()); - let user = member.ok_or_else(|| anyhow::anyhow!("No members in this channel"))?.user; - ctx.say(if user.discriminator == 0 { user.name } else { user.tag() }).await?; + let channel = channel.context("/someone only works in guilds")?; + let member = channel.members(ctx)?.into_iter().choose(&mut rand::thread_rng()); + ctx.say(member.context("No members in this channel")?.user.tag()).await?; Ok(()) } \ No newline at end of file diff --git a/src/topgg.rs b/src/topgg.rs index 4e207ca..188f523 100644 --- a/src/topgg.rs +++ b/src/topgg.rs @@ -4,8 +4,8 @@ use poise::serenity_prelude as serenity; #[derive(Debug, Default, Clone, Copy)] struct Stats { - guilds: u64, - shards: u64, + guilds: usize, + shards: u32, } impl Stats { @@ -49,12 +49,11 @@ impl Poster { impl serenity::EventHandler for Poster { async fn ready(&self, _: serenity::Context, ready: serenity::Ready) { let Some(token) = self.token.as_deref() else { return }; - let guilds = ready.guilds.len() as u64; + let guilds = ready.guilds.len(); - match ready.shard { - None => post(token, Stats { guilds, shards: 0 }).await, - Some([_, s@0..=1]) => post(token, Stats { guilds, shards: s }).await, - Some([_, shards]) => { + match ready.shard.map_or(0, |s| s.total) { + s@0..=1 => post(token, Stats { guilds, shards: s }).await, + shards => { let mut stats = self.stats.lock().await; stats.guilds += guilds; stats.shards += 1; diff --git a/src/weeb.rs b/src/weeb.rs index 0efe18a..31d5492 100644 --- a/src/weeb.rs +++ b/src/weeb.rs @@ -1,4 +1,5 @@ use crate::Context; +use poise::serenity_prelude as serenity; use rand::seq::SliceRandom as _; /// Feed someone @@ -16,10 +17,13 @@ pub async fn feed(ctx: Context<'_>, let json = reqwest::get(endpoint).await?.json::().await?; let url = json["url"].as_str().ok_or_else(|| anyhow::anyhow!("Invalid image URL"))?; - ctx.send(|m| m.embed(|e| e - .description(ctx.author().to_string() + " fed " + text + "!") - .image(url) - )).await?; + ctx.send(poise::CreateReply { + embeds: vec![serenity::CreateEmbed::new() + .description(ctx.author().to_string() + " fed " + text + "!") + .image(url) + ], + ..Default::default() + }).await?; Ok(()) } @@ -37,11 +41,16 @@ pub async fn hug(ctx: Context<'_>, "https://cdn.discordapp.com/attachments/403697175948820481/413015715273113601/Nxdr0qO_1.jpg", "https://cdn.discordapp.com/attachments/403697175948820481/444226349121404960/hug.jpg", ]; + let hug = *HUGS.choose(&mut rand::thread_rng()).expect("Choosing from an empty image list!"); let text = text.as_deref().unwrap_or("Yuri"); - ctx.send(|m| m.embed(|e| e - .description(ctx.author().to_string() + " hugged " + text + "!") - .image(HUGS.choose(&mut rand::thread_rng()).expect("Choosing from an empty image list!")) - )).await?; + + ctx.send(poise::CreateReply { + embeds: vec![serenity::CreateEmbed::new() + .description(ctx.author().to_string() + " hugged " + text + "!") + .image(hug) + ], + ..Default::default() + }).await?; Ok(()) } @@ -65,11 +74,16 @@ pub async fn kiss(ctx: Context<'_>, "https://cdn.discordapp.com/attachments/403697175948820481/444355145124544513/11b4bc2.png", "https://cdn.discordapp.com/attachments/409037934470234113/449736165290016782/8qlcohr5c1011.png", ]; + let kiss = *KISSES.choose(&mut rand::thread_rng()).expect("Choosing from an empty image list!"); let text = text.as_deref().unwrap_or("Natsuki"); - ctx.send(|m| m.embed(|e| e - .description(ctx.author().to_string() + " kissed " + text + "!") - .image(KISSES.choose(&mut rand::thread_rng()).expect("Choosing from an empty image list!")) - )).await?; + + ctx.send(poise::CreateReply { + embeds: vec![serenity::CreateEmbed::new() + .description(ctx.author().to_string() + " kissed " + text + "!") + .image(kiss) + ], + ..Default::default() + }).await?; Ok(()) } @@ -95,10 +109,13 @@ pub async fn lick(ctx: Context<'_>, text: Option, ) -> anyhow::Result<()> { let text = text.as_deref().unwrap_or("the air"); - ctx.send(|m| m.embed(|e| e - .description(ctx.author().to_string() + " licked " + text + "!") - .image("https://cdn.discordapp.com/attachments/421196261132075009/421920949277818891/LickTemplate.gif") - )).await?; + ctx.send(poise::CreateReply { + embeds: vec![serenity::CreateEmbed::new() + .description(ctx.author().to_string() + " licked " + text + "!") + .image("https://cdn.discordapp.com/attachments/421196261132075009/421920949277818891/LickTemplate.gif") + ], + ..Default::default() + }).await?; Ok(()) } @@ -113,9 +130,12 @@ pub async fn neko(ctx: Context<'_>) -> anyhow::Result<()> { let json = reqwest::get(endpoint).await?.json::().await?; let url = json["url"].as_str().ok_or_else(|| anyhow::anyhow!("Invalid image URL"))?; - ctx.send(|m| m.embed(|e| e - .description("Here comes your random neko.") - .image(url) - )).await?; + ctx.send(poise::CreateReply { + embeds: vec![serenity::CreateEmbed::new() + .description("Here comes your random neko.") + .image(url) + ], + ..Default::default() + }).await?; Ok(()) } \ No newline at end of file