diff --git a/Cargo.lock b/Cargo.lock index d1a5ca6..941fe67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "animethemes-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85ae35b6d7efdd387a743cbf2d2bd1b8e9545d2c3e5abc85a1aa0e5ca99e5af" +dependencies = [ + "reqwest", + "serde", + "thiserror", +] + [[package]] name = "anyhow" version = "1.0.40" @@ -2058,9 +2069,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" dependencies = [ "serde_derive", ] @@ -2078,9 +2089,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2", "quote", @@ -2528,6 +2539,7 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" name = "tobi-rs" version = "0.8.1" dependencies = [ + "animethemes-rs", "aspotify", "bot-coreutils", "bot-database", diff --git a/Cargo.toml b/Cargo.toml index 1acfa6d..dcdae68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,4 +40,5 @@ trigram = "0.4.4" typemap_rev = "0.1.5" youtube-metadata = "0.1.1" xkcd-search = "0.1.1" -lavalink-rs = {version="0.7.1", features=["native", "serenity"]} \ No newline at end of file +lavalink-rs = {version="0.7.1", features=["native", "serenity"]} +animethemes-rs = "0.2.1" \ No newline at end of file diff --git a/src/commands/weeb/mod.rs b/src/commands/weeb/mod.rs index de75e24..a4b8a6e 100644 --- a/src/commands/weeb/mod.rs +++ b/src/commands/weeb/mod.rs @@ -7,6 +7,7 @@ mod miko; mod pekofy; mod rushia; mod sauce; +mod theme; use crate::utils::context_data::get_database_from_context; use crate::utils::error::BotError; @@ -22,9 +23,10 @@ use miko::MIKO_COMMAND; use pekofy::PEKOFY_COMMAND; use rushia::RUSHIA_COMMAND; use sauce::SAUCE_COMMAND; +use theme::THEME_COMMAND; #[group] -#[commands(pekofy, sauce, matsuri, korone, rushia, fubuki, miko)] +#[commands(pekofy, sauce, matsuri, korone, rushia, fubuki, miko, theme)] pub struct Weeb; /// Posts a random media entry with the given category diff --git a/src/commands/weeb/theme.rs b/src/commands/weeb/theme.rs new file mode 100644 index 0000000..84fb763 --- /dev/null +++ b/src/commands/weeb/theme.rs @@ -0,0 +1,38 @@ +use crate::messages::theme::create_theme_menu; +use animethemes_rs::client::AnimeThemesClient; +use serenity::client::Context; +use serenity::framework::standard::macros::command; +use serenity::framework::standard::{Args, CommandResult}; +use serenity::model::channel::Message; +use serenity_rich_interaction::core::MEDIUM_TIMEOUT; +use serenity_rich_interaction::ephemeral_message::EphemeralMessage; + +#[command] +#[description("Query for the opening/ending/insert song of an anime")] +#[usage("")] +#[aliases("animetheme", "anime-theme", "opening", "ending", "ost")] +#[bucket("general")] +async fn theme(ctx: &Context, msg: &Message, args: Args) -> CommandResult { + let query = args.message(); + let client = AnimeThemesClient::default(); + let themes = client + .search(query, &["entries"], &["theme", "theme.anime", "videos"]) + .await?; + if let Some(entries) = themes.entries { + if entries.is_empty() { + EphemeralMessage::create(&ctx.http, msg.channel_id, MEDIUM_TIMEOUT, |c| { + c.reference_message(msg).content("No themes found") + }) + .await?; + } else { + create_theme_menu(ctx, msg.channel_id, entries).await?; + } + } else { + EphemeralMessage::create(&ctx.http, msg.channel_id, MEDIUM_TIMEOUT, |c| { + c.reference_message(msg).content("No themes found") + }) + .await?; + } + + Ok(()) +} diff --git a/src/messages/mod.rs b/src/messages/mod.rs index bad0c06..3cc3f3c 100644 --- a/src/messages/mod.rs +++ b/src/messages/mod.rs @@ -8,6 +8,7 @@ pub mod gifs; pub mod minecraft; pub mod music; pub mod sauce; +pub mod theme; pub mod xkcd; /// Adds an ephemeral message to the database diff --git a/src/messages/theme.rs b/src/messages/theme.rs new file mode 100644 index 0000000..f321534 --- /dev/null +++ b/src/messages/theme.rs @@ -0,0 +1,69 @@ +use crate::utils::error::BotResult; +use animethemes_rs::models::{ThemeEntry, ThemeType}; +use serenity::builder::CreateMessage; +use serenity::client::Context; +use serenity::model::id::ChannelId; +use serenity_rich_interaction::core::EXTRA_LONG_TIMEOUT; +use serenity_rich_interaction::menu::{MenuBuilder, Page}; + +/// Creates a new Anime Theme Menu +pub async fn create_theme_menu( + ctx: &Context, + channel_id: ChannelId, + mut entries: Vec, +) -> BotResult<()> { + let nsfw = ctx.http.get_channel(channel_id.0).await?.is_nsfw(); + entries.sort_by_key(|t| { + if let Some(theme) = &t.theme { + match &theme.theme_type { + ThemeType::OP => theme.sequence.unwrap_or(1), + ThemeType::ED => theme.sequence.unwrap_or(1) * 100, + } + } else { + 10000 + } + }); + MenuBuilder::new_paginator() + .add_pages( + entries + .into_iter() + .filter(|e| { + if !nsfw && e.nsfw { + return false; + } + if let Some(videos) = &e.videos { + !videos.is_empty() + } else { + false + } + }) + .map(|e| create_theme_page(e, nsfw)), + ) + .timeout(EXTRA_LONG_TIMEOUT) + .build(ctx, channel_id) + .await?; + + Ok(()) +} + +/// Creates a new anime theme page +fn create_theme_page(entry: ThemeEntry, nsfw: bool) -> Page<'static> { + let mut message = CreateMessage::default(); + let videos = entry.videos.unwrap(); + let theme = entry.theme.unwrap(); + let anime = theme.anime.unwrap(); + let theme_type = match theme.theme_type { + ThemeType::OP => "Opening", + ThemeType::ED => "Ending", + }; + + message.content(format!( + "**{} {}** of **{}**\nhttps://animethemes.moe/video/{}", + theme_type, + theme.sequence.unwrap_or(1), + anime.name, + videos.first().unwrap().basename + )); + + Page::Static(message) +}