From 23f5009c4ab4a2a9f989d342651ab55eaec72a68 Mon Sep 17 00:00:00 2001 From: Ladislav Hano Date: Sun, 6 Oct 2024 16:53:12 +0200 Subject: [PATCH 1/4] feat: implemented new slash commands --- src/commands/mod.rs | 13 ++++++- src/commands/moover/mod.rs | 7 ++++ src/commands/{ => moover}/moove.rs | 0 src/commands/{ => moover}/say.rs | 0 src/commands/notice/birthday.rs | 0 src/commands/notice/events.rs | 0 src/commands/user_interactions/headpat.rs | 20 ++++++++++ src/commands/user_interactions/hug.rs | 20 ++++++++++ src/commands/user_interactions/interaction.rs | 38 +++++++++++++++++++ src/commands/user_interactions/mod.rs | 6 +++ src/message_handler.rs | 2 +- src/other/notice.rs | 4 +- src/types.rs | 2 +- src/util/gifs.rs | 9 +++++ src/util/mod.rs | 3 +- src/util/utilities.rs | 2 +- 16 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 src/commands/moover/mod.rs rename src/commands/{ => moover}/moove.rs (100%) rename src/commands/{ => moover}/say.rs (100%) create mode 100644 src/commands/notice/birthday.rs create mode 100644 src/commands/notice/events.rs create mode 100644 src/commands/user_interactions/headpat.rs create mode 100644 src/commands/user_interactions/hug.rs create mode 100644 src/commands/user_interactions/interaction.rs create mode 100644 src/commands/user_interactions/mod.rs create mode 100644 src/util/gifs.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 98ea7cd..92a8ea2 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,2 +1,11 @@ -pub mod moove; -pub mod say; \ No newline at end of file +pub use moover::*; +pub use notice::*; +pub use other::*; +pub use user_interactions::*; + +pub mod moover; +pub mod notice; +pub mod user_interactions; +pub mod other; + +pub mod channel_test; \ No newline at end of file diff --git a/src/commands/moover/mod.rs b/src/commands/moover/mod.rs new file mode 100644 index 0000000..4b6fd56 --- /dev/null +++ b/src/commands/moover/mod.rs @@ -0,0 +1,7 @@ +pub use moove::*; +pub use say::*; +pub use gif::*; + +pub mod moove; +pub mod say; +pub mod gif; \ No newline at end of file diff --git a/src/commands/moove.rs b/src/commands/moover/moove.rs similarity index 100% rename from src/commands/moove.rs rename to src/commands/moover/moove.rs diff --git a/src/commands/say.rs b/src/commands/moover/say.rs similarity index 100% rename from src/commands/say.rs rename to src/commands/moover/say.rs diff --git a/src/commands/notice/birthday.rs b/src/commands/notice/birthday.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/commands/notice/events.rs b/src/commands/notice/events.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/commands/user_interactions/headpat.rs b/src/commands/user_interactions/headpat.rs new file mode 100644 index 0000000..8d33ad8 --- /dev/null +++ b/src/commands/user_interactions/headpat.rs @@ -0,0 +1,20 @@ +use poise; +use serenity::all::User; + +use super::interaction::send_with_embed; +use crate::types::{Error, Context}; + +#[poise::command( + slash_command, + description_localized("en-US", "Headpat all your friends!") +)] +pub async fn headpat(ctx: Context<'_>, + #[description = "Who is the lucky one?"] + user: User +) -> Result<(), Error> { + let title = "HEADPATS!"; + let desc = format!("{} headpats {}", ctx.author(), user); + send_with_embed(ctx, "headpat", &title, &desc).await?; + ctx.reply("Done!").await?; + Ok(()) +} \ No newline at end of file diff --git a/src/commands/user_interactions/hug.rs b/src/commands/user_interactions/hug.rs new file mode 100644 index 0000000..31dbe8c --- /dev/null +++ b/src/commands/user_interactions/hug.rs @@ -0,0 +1,20 @@ +use poise; +use serenity::all::User; + +use super::interaction::send_with_embed; +use crate::types::{Error, Context}; + +#[poise::command( + slash_command, + description_localized("en-US", "Hug all your friends!") +)] +pub async fn hug(ctx: Context<'_>, + #[description = "Who is the lucky one?"] + user: User +) -> Result<(), Error> { + let title = "HUGS!"; + let desc = format!("{} hugs {}", ctx.author(), user); + send_with_embed(ctx, "hug", &title, &desc).await?; + ctx.reply("Done!").await?; + Ok(()) +} \ No newline at end of file diff --git a/src/commands/user_interactions/interaction.rs b/src/commands/user_interactions/interaction.rs new file mode 100644 index 0000000..439ef72 --- /dev/null +++ b/src/commands/user_interactions/interaction.rs @@ -0,0 +1,38 @@ +use anyhow::anyhow; +use serenity::all::{Colour, CreateEmbed, CreateMessage}; +use tenorv2::tenor_builder::Tenor; + +use crate::{types::Context, util::{gifs::get_random_tenor_gif, utilities}}; + +pub(super) async fn send_with_embed(ctx: Context<'_>, query: &str, title: &str, desc: &str) -> anyhow::Result<()> { + let tenor_response = Tenor::new()? + .random(true) + .search(query).await?; + + const LIMIT: u8 = 20; + let url = get_random_tenor_gif(tenor_response, LIMIT).await?; + + let embed = CreateEmbed::new() + .color(Colour::new(rand::random::() % 0xFFFFFF)) + .title(title) + .description(desc) + .image(url.as_str()); + + if ctx.guild_id().is_none() { + return Err(anyhow!("Guild id not available in context")); + } + + utilities::get_system_channel( + ctx.guild_id().unwrap(), ctx.http() + ).await? + .send_message( + ctx.http(), + CreateMessage::new().add_embed(embed) + ).await?; + + Ok(()) +} + +pub(super) async fn send(ctx: Context<'_>, msg: &str) -> anyhow::Result<()> { + Ok(()) +} \ No newline at end of file diff --git a/src/commands/user_interactions/mod.rs b/src/commands/user_interactions/mod.rs new file mode 100644 index 0000000..0e8654b --- /dev/null +++ b/src/commands/user_interactions/mod.rs @@ -0,0 +1,6 @@ +pub use headpat::*; +pub use hug::*; + +pub mod interaction; +pub mod headpat; +pub mod hug; \ No newline at end of file diff --git a/src/message_handler.rs b/src/message_handler.rs index 1183998..767448a 100644 --- a/src/message_handler.rs +++ b/src/message_handler.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use crate::util::debug::send_error; use crate::util::utilities; -use crate::commands::moove::{moove, moove_check}; +use crate::commands::moover::moove::{moove, moove_check}; pub async fn handle(ctx: Context, msg: Message) { if msg.author.bot { diff --git a/src/other/notice.rs b/src/other/notice.rs index e7fde2e..2294637 100644 --- a/src/other/notice.rs +++ b/src/other/notice.rs @@ -30,7 +30,7 @@ async fn announce_event(guild_id: GuildId, name: &str, special_message: &str, ht .color(Colour::new(rand::random::() % 0xFFFFFF)) .title("Today's event is:"); - let system_channel = utilities::get_system_channel(guild_id, http.clone()).await?; + let system_channel = utilities::get_system_channel(guild_id, &http).await?; if special_message.contains("http") { event_embed = event_embed.description(name); @@ -65,7 +65,7 @@ async fn celebrate_birthday(guild_id: GuildId, user_id: UserId, nick: &str, http } }; - let system_channel = utilities::get_system_channel(guild_id, http.clone()).await?; + let system_channel = utilities::get_system_channel(guild_id, &http).await?; let mut embed = CreateEmbed::new() .color(Colour::new(rand::random::() % 0xFFFFFF)) diff --git a/src/types.rs b/src/types.rs index 2571aab..b5ebced 100644 --- a/src/types.rs +++ b/src/types.rs @@ -2,4 +2,4 @@ pub type Error = Box; // replace () with Data if you ever need to store some additional data -// pub type Context<'a> = poise::Context<'a, (), Error>; +pub type Context<'a> = poise::Context<'a, (), Error>; diff --git a/src/util/gifs.rs b/src/util/gifs.rs new file mode 100644 index 0000000..f128ea9 --- /dev/null +++ b/src/util/gifs.rs @@ -0,0 +1,9 @@ +use tenorv2::{tenor, tenor_types::{MediaFilter, TenorError}, JsonValue}; + +pub async fn get_random_tenor_gif(tenor_response: JsonValue, limit: u8) -> Result { + let index = rand::random::() % limit as usize; + match tenor::get_gif_url(MediaFilter::gif, tenor_response) { + Ok(urls) => Ok(urls[index].clone()), + Err(e) => Err(e) + } +} \ No newline at end of file diff --git a/src/util/mod.rs b/src/util/mod.rs index d3d2689..a259bb4 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,2 +1,3 @@ pub mod debug; -pub mod utilities; \ No newline at end of file +pub mod utilities; +pub mod gifs; \ No newline at end of file diff --git a/src/util/utilities.rs b/src/util/utilities.rs index 7669eb7..8b5f470 100644 --- a/src/util/utilities.rs +++ b/src/util/utilities.rs @@ -4,7 +4,7 @@ use serenity::{all::{ChannelId, CreateMessage, GuildId, Message}, http::Http}; use anyhow::Context; -pub async fn get_system_channel(guild_id: GuildId, http: Arc) -> anyhow::Result { +pub async fn get_system_channel(guild_id: GuildId, http: &Http) -> anyhow::Result { return http.get_guild(guild_id).await?.system_channel_id .context(format!("System channel of guild: {} not found", guild_id.get())); } From d7dbdea65969cced73932d158d57740113cbff33 Mon Sep 17 00:00:00 2001 From: Ladislav Hano Date: Sun, 6 Oct 2024 16:54:08 +0200 Subject: [PATCH 2/4] feat: add commands to main, guild/global command registration --- Cargo.toml | 1 + src/main.rs | 32 +++++++++++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7b8e356..359cd1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ tenorv2 = { path = "./tenor-v2/tenorv2" } [features] DEBUG = [] RELEASE = [] +GUILD_COMMAND = [] \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index de3c551..59da021 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,14 @@ use std::time::Duration; use std::error; use std::env; -use poise::samples::register_in_guild; +use poise::samples::register_globally; +use poise::samples::register_in_guild; use serenity::async_trait; use serenity::prelude::GatewayIntents; use serenity::client::Context; use serenity::model::gateway::Ready; -use serenity::all::{EventHandler, GuildId, Message}; +use serenity::all::{EventHandler, Message}; use serenity::Client; use dotenv::dotenv; @@ -82,7 +83,10 @@ async fn main() -> anyhow::Result<()> { // create poise framework for registering commands let options: poise::FrameworkOptions<(), Box> = poise::FrameworkOptions { - commands: vec![], + commands: vec![ + commands::say(), + commands::hug() + ], prefix_options: poise::PrefixFrameworkOptions { prefix: Some("/".into()), edit_tracker: Some(Arc::new(poise::EditTracker::for_timespan( @@ -99,15 +103,21 @@ async fn main() -> anyhow::Result<()> { ..Default::default() }; - let debug_guild_id = env::var("DEBUG_GUILD_ID") - .context("DEBUG_GUILD_ID not found in env")? - .parse::().unwrap(); - + let framework = poise::Framework::builder() - .setup(move |ctx, _ready, framework| { - Box::pin(async move { - register_in_guild(ctx, &framework.options().commands, GuildId::new(debug_guild_id)).await?; - Ok(()) + .setup(move |ctx, _ready, framework| { + Box::pin(async move { + #[cfg(feature="GUILD_COMMAND")] { + let debug_guild_id = env::var("DEBUG_GUILD_ID") + .context("DEBUG_GUILD_ID not found in env")? + .parse::().unwrap(); + + register_in_guild(ctx, &framework.options().commands, debug_guild_id).await?; + } + #[cfg(not(feature="GUILD_COMMAND"))] { + register_globally(ctx, &framework.options().commands).await?; + } + Ok(()) }) }) .options(options) From eaed7733ff29448f40f5034f9c7aed6fb7588530 Mon Sep 17 00:00:00 2001 From: Ladislav Hano Date: Sat, 7 Dec 2024 13:07:24 +0100 Subject: [PATCH 3/4] chore: documentation, dependency updates, etc. --- .env.example | 10 ++++++ Cargo.toml | 6 ++-- README.md | 35 +++++++++++++++++++ makefile | 2 +- src/commands/moover/moove.rs | 10 +++--- src/commands/notice/mod.rs | 5 +++ src/commands/other/mod.rs | 0 src/commands/user_interactions/interaction.rs | 2 ++ src/other/notice.rs | 4 +++ 9 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 .env.example create mode 100644 README.md create mode 100644 src/commands/notice/mod.rs create mode 100644 src/commands/other/mod.rs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1363c8f --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +TOKEN=token used for release version +DEBUGTOKEN=token I use for debugging on standalone bot +DBPASS=password for database (currently not used) +DEBUG=ON + +DEBUG_CHANNEL_ID=channel where debug info will be sent +DEBUG_GUILD_ID=guild where local slash commands will be registered + +DATABASE_URL=sqlite://path_to_database +TENORV2_TOKEN=token to tenor API v2 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 359cd1d..1a16780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,10 @@ anyhow = "1.0.89" tokio-cron-scheduler = "0.13.0" dotenv = "0.15.0" poise = "0.6.1" -serenity = { version = "0.12.2", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "http", "cache"] } +serenity = { version = "0.12.4", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "http", "cache"] } # serenity_utils = "0.7.0" -tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] } -regex = "1.11.0" +tokio = { version = "1.42.0", features = ["macros", "rt-multi-thread"] } +regex = "1.11.1" chrono = "0.4.38" sqlx = {version="0.8.2", features=["runtime-tokio", "sqlite"]} form_urlencoded = "1.2.1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..7ff7f51 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Discord bot made in rust + +## Current feature list: + - move message from one channel to another + - send hug and headpat embed and tag user in it + - announces events and birthdays that are in database + +## Technologies used + - Discord API - serenity, poise + - Database - sqlite + - gifs - my partial implementation of tenor API + +## Compilation +Make sure you have cargo installed! +Edit .env.example and rename to .env + +Compile debug version +``` +make dev +``` + +Compile release version +``` +make release +``` + +Compile debug version and run it +``` +make run +``` + +Run release version +``` +make run_rel +``` diff --git a/makefile b/makefile index e16831f..7815468 100644 --- a/makefile +++ b/makefile @@ -8,5 +8,5 @@ run: cargo build --features DEBUG ./target/debug/moover_rust -run_release: +run_rel: ./target/release/moover_rust \ No newline at end of file diff --git a/src/commands/moover/moove.rs b/src/commands/moover/moove.rs index 85539d8..046e433 100644 --- a/src/commands/moover/moove.rs +++ b/src/commands/moover/moove.rs @@ -9,10 +9,9 @@ use tokio::time::sleep; use regex::Regex; use serenity::model::id::ChannelId; -// Checks if the message should be mooved -// If the message should be mooved, try to move it and return Ok if mooved succesfully -// else returns Err() +/// Checks if the message should be mooved +/// If the message should be mooved, try to move it and return Ok if mooved succesfully pub async fn moove_check(msg: &Message) -> Option { let word_count = msg.content.trim().split_whitespace().count(); let re = Regex::new(r"<#[0-9]*>$").unwrap(); @@ -29,8 +28,11 @@ pub async fn moove_check(msg: &Message) -> Option { return Some(channel_id); } +/// Move message to new channel (will delete the old message) pub async fn moove(http: Arc, msg: Message, m_channel_id: u64) -> anyhow::Result<()> { - // this should be in moove_check, but I need to find a good way to return in with channel_id + msg.react(http.clone(), '🐮').await?; + + // this should be in moove_check, but I need to find a good way to return it with channel_id let msg_to_moove = msg.clone().referenced_message.context("Referenced message not found")?; //steals all attachments, but sets all of them as Image urls, so rip actual docs etc diff --git a/src/commands/notice/mod.rs b/src/commands/notice/mod.rs new file mode 100644 index 0000000..d63767d --- /dev/null +++ b/src/commands/notice/mod.rs @@ -0,0 +1,5 @@ +pub use birthday::*; +pub use events::*; + +pub mod birthday; +pub mod events; \ No newline at end of file diff --git a/src/commands/other/mod.rs b/src/commands/other/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/commands/user_interactions/interaction.rs b/src/commands/user_interactions/interaction.rs index 439ef72..6db121a 100644 --- a/src/commands/user_interactions/interaction.rs +++ b/src/commands/user_interactions/interaction.rs @@ -4,6 +4,8 @@ use tenorv2::tenor_builder::Tenor; use crate::{types::Context, util::{gifs::get_random_tenor_gif, utilities}}; +/// Sends embed with random tenor gif from searched query +/// title and desc are used in the embed pub(super) async fn send_with_embed(ctx: Context<'_>, query: &str, title: &str, desc: &str) -> anyhow::Result<()> { let tenor_response = Tenor::new()? .random(true) diff --git a/src/other/notice.rs b/src/other/notice.rs index 2294637..69b39ed 100644 --- a/src/other/notice.rs +++ b/src/other/notice.rs @@ -24,6 +24,7 @@ pub async fn notice_wrapper(ctx: Context) { } } +/// Send embed with event name and optional special message to guild's general channel async fn announce_event(guild_id: GuildId, name: &str, special_message: &str, http: Arc) -> anyhow::Result<()> { let mut event_embed = CreateEmbed::new() @@ -46,6 +47,7 @@ async fn announce_event(guild_id: GuildId, name: &str, special_message: &str, ht Ok(()) } +/// Send birthday embed to guild's general channel async fn celebrate_birthday(guild_id: GuildId, user_id: UserId, nick: &str, http: Arc) -> anyhow::Result<()> { const LIMIT: u8 = 20; @@ -98,6 +100,8 @@ struct EventRow { special_message: String, } +/// Fetches guild/global events and birthdays for current day +/// Sends notification to relevant channels async fn notice(http: Arc) -> anyhow::Result<()> { use anyhow::Context; From aa065f0775f3cecef3f2d348c6e5bd975f482a83 Mon Sep 17 00:00:00 2001 From: Ladislav Hano Date: Tue, 31 Dec 2024 12:49:53 +0100 Subject: [PATCH 4/4] fix: x site change from fxtwitter to fixvx --- README.md | 3 +++ src/commands/mod.rs | 2 +- src/commands/moover/mod.rs | 4 ++-- src/message_handler.rs | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7ff7f51..a687c2e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ Make sure you have cargo installed! Edit .env.example and rename to .env +Check if you have openssl (libssl-dev Ubuntu, openssl-devel Fedora) +audiopus_sys requires cmake + Compile debug version ``` make dev diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 92a8ea2..39b9bfb 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -8,4 +8,4 @@ pub mod notice; pub mod user_interactions; pub mod other; -pub mod channel_test; \ No newline at end of file +// pub mod channel_test; \ No newline at end of file diff --git a/src/commands/moover/mod.rs b/src/commands/moover/mod.rs index 4b6fd56..fbf6377 100644 --- a/src/commands/moover/mod.rs +++ b/src/commands/moover/mod.rs @@ -1,7 +1,7 @@ pub use moove::*; pub use say::*; -pub use gif::*; +// pub use gif::*; pub mod moove; pub mod say; -pub mod gif; \ No newline at end of file +// pub mod gif; \ No newline at end of file diff --git a/src/message_handler.rs b/src/message_handler.rs index 767448a..4aa263e 100644 --- a/src/message_handler.rs +++ b/src/message_handler.rs @@ -33,7 +33,7 @@ pub async fn handle(ctx: Context, msg: Message) { // X and IG not embedding correctly (IG fix does not work for now need to find different one) let link_fixes = HashMap::from([ - ("//x.com", "//fxtwitter.com") + ("//x.com", "//fixvx.com") ]); for (site, fix) in link_fixes {