mod commands; mod util; mod music; use serenity::all::{CommandInteraction, CreateInteractionResponseFollowup, OnlineStatus, VoiceState}; use serenity::async_trait; use serenity::builder::{CreateEmbed}; use serenity::gateway::ActivityData; use serenity::model::application::{Command, Interaction}; use serenity::model::gateway::Ready; use serenity::prelude::*; use util::{config, embed::Embed, user_util}; // This trait adds the `register_songbird` and `register_songbird_with` methods // to the client builder below, making it easy to install this voice client. // The voice client can be retrieved in any command using `songbird::get(ctx).await`. use songbird::SerenityInit; // YtDl requests need an HTTP client to operate -- we'll create and store our own. use reqwest::Client as HttpClient; struct HttpKey; impl TypeMapKey for HttpKey { type Value = HttpClient; } struct Handler; #[async_trait] impl EventHandler for Handler { async fn interaction_create(&self, ctx: Context, interaction: Interaction) { if let Interaction::Command(command) = interaction { let _ = &command.defer(&ctx.http()).await.expect("Cannot defer"); let content = Some(match command.data.name.as_str() { "info" => commands::info::run(&ctx, &command).await, "play" => commands::play::run(&ctx, &command).await, "stop" => commands::stop::run(&ctx, &command).await, _ => respond_with_error(&ctx, &command).await, }); if let Some(embed) = content { let followup = CreateInteractionResponseFollowup::new().embed(embed); if let Err(why) = command.create_followup(&ctx.http, followup).await { println!("Cannot followup to slash command: {why}") } } } } async fn ready(&self, ctx: Context, ready: Ready) { println!("{} is connected!", ready.user.name); let _command = Command::create_global_command(&ctx.http, commands::info::register()).await; let _command = Command::create_global_command(&ctx.http, commands::stop::register()).await; let _command = Command::create_global_command(&ctx.http, commands::play::register()).await; println!("Commands are registered and Rustendo is ready for Freddy."); } async fn voice_state_update(&self, ctx: Context, old: Option, new: VoiceState) { if !new.channel_id.is_none() { return; // User did not leave, ignore } if let Some(old) = old { if !user_util::is_self_connected_to_vc(&ctx, &old.guild_id.unwrap()).await { return; // Bot is not connected to a VC, ignore } if user_util::get_amount_of_members_in_vc(&ctx, &old.guild_id.unwrap(), &old.channel_id.unwrap()).await < 2 { let manager = songbird::get(&ctx).await.expect("Cannot get Songbird"); if let Err(e) = manager.remove(old.guild_id.unwrap()).await { println!("Failed to remove handler: {:?}", e); } } } // else: new user joined, ignore } } pub async fn respond_with_error(_ctx: &Context, command: &CommandInteraction) -> CreateEmbed { Embed::create( command.user.name.to_owned(), "Command not found", "Cannot find the executed command", ) } #[tokio::main] async fn main() { println!( r"__________ __ .___ \______ \__ __ _______/ |_ ____ ____ __| _/____ | _/ | | ___/\ __\_/ __ \ / \ / __ |/ _ \ | | \ | |___ \ | | \ ___/ | | | /_/ ( <_> ) |____|_ /____/____ > |__| \___ >|___| |____ |\____/ \/ \/ \/ \/ \/ " ); // Load config let config = config::load().unwrap(); // Set status let status = OnlineStatus::DoNotDisturb; let activity = ActivityData::streaming("music", "https://twitch.tv/moonleaytv").unwrap(); // Build the client let mut client = Client::builder(config.discord_token, GatewayIntents::empty()) .event_handler(Handler) .status(status) .activity(activity) .register_songbird() .type_map_insert::(HttpClient::new()) .intents(GatewayIntents::all()) .await .expect("Error creating client"); // Finally, start a single shard, and start listening to events. // // Shards will automatically attempt to reconnect, and will perform exponential backoff until // it reconnects. if let Err(why) = client.start().await { println!("Client error: {why:?}"); } }