diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..0ca8fde --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2024-11-13" +targets = ["x86_64-unknown-linux-gnu"] diff --git a/src/commands/voice/spotify/player.rs b/src/commands/voice/spotify/player.rs index 9b3b9d5..258dbea 100644 --- a/src/commands/voice/spotify/player.rs +++ b/src/commands/voice/spotify/player.rs @@ -4,32 +4,26 @@ use songbird::events::{Event, EventContext, EventHandler as VoiceEventHandler}; use songbird::TrackEvent; use crate::util::debug::send_error; -use crate::{types::{Context, Error}, util::utilities::get_channel_by_name}; +use crate::{ + types::{Context, Error}, + util::utilities::get_channel_by_name, +}; use serenity::all::User; -#[poise::command( - slash_command, - description_localized("en-US", "Play song") -)] -pub async fn play(ctx: Context<'_>, - #[description = "Song: "] - _url: String -) -> Result<(), Error> { - +#[poise::command(slash_command, description_localized("en-US", "Play song"))] +pub async fn play(ctx: Context<'_>, #[description = "Song: "] _url: String) -> Result<(), Error> { ctx.reply("Done!").await?; Ok(()) } - - #[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 +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); @@ -38,85 +32,70 @@ pub async fn headpat(ctx: Context<'_>, Ok(()) } +enum ConnectStatus<'a> { + Ok, + ErrorReplyMessage(&'a str), +} -#[poise::command( - slash_command, - description_localized("en-US", "Connect to channel") -)] -pub async fn connect(ctx: Context<'_>, +#[poise::command(slash_command, description_localized("en-US", "Connect to channel"))] +pub async fn connect( + ctx: Context<'_>, #[autocomplete = "autocomplete_channel"] #[description = "Voice channel name: "] - name: Option + name: Option, ) -> Result<(), Error> { - if ctx.guild().is_none() { ctx.reply("Can't use this outside of guild").await?; - return Ok(()) + return Ok(()); } + let mut maybe_reply = ConnectStatus::Ok; - let channel: ChannelId; - if name.is_none() || name.clone().unwrap() == "" { - // ERROR: <> - // !### This will work if you comment out one of the replies ###! - // let Some(guild) = ctx.guild() else { - // ctx.reply("You must be in a voice channel or specify explicit voice channel by name").await?; - // return Ok(()) - // }; - - // let Some(voice_channel) = guild.voice_states.get(&ctx.author().id).and_then(|voice_state| voice_state.channel_id) else { - // ctx.reply("You must be in a voice channel or specify explicit voice channel by name").await?; - // return Ok(()) - // }; - - // channel = voice_channel; - // !### ###! - - // This one liner works only if you comment out the reply - // channel = match ctx.guild().unwrap().voice_states.get(&ctx.author().id).and_then(|voice_state| voice_state.channel_id) { - // Some(channel) => channel, - // None => { - // // ERROR: <> - // // Will work if you comment this out - // ctx.reply("You must be in a voice channel or specify explicit voice channel by name").await?; - // return Ok(()) - // } - // } - - // This one works - // Ugly one liner since I don't know how to do this another way yet - // TODO fix please - let Some(voice_channel) = ctx.guild().unwrap().voice_states.get(&ctx.author().id).and_then(|voice_state| voice_state.channel_id) else { - ctx.reply("You must be in a voice channel or specify explicit voice channel by name").await?; - return Ok(()) - }; - - channel = voice_channel; - } - else { - channel = match get_channel_by_name(ctx.guild().unwrap(), name.unwrap()) { - Some(channel) => channel, - None => { - ctx.reply("Channel with this name does not exist").await?; - return Ok(()); + let channel_id: Option = + match name.is_none() || name.as_ref().is_some_and(|n| n != "") { + true => { + //Radko: Ak nechceš vyvíjať na nightly (alebo ešte nevyšiel rust 2024 edition) tak + //chainuj .and_then() / .map() + if let Some(voice_channel) = ctx.guild() + && let Some(voice_state) = voice_channel.voice_states.get(&ctx.author().id) + && let Some(channel_id) = voice_state.channel_id + { + Some(channel_id) + } else { + maybe_reply = ConnectStatus::ErrorReplyMessage( + "You must be in a voice channel or specify explicit voice channel by name", + ); + None + } } + false => match get_channel_by_name(ctx.guild().unwrap(), name.unwrap()) { + Some(channel) => Some(channel), + None => { + maybe_reply = + ConnectStatus::ErrorReplyMessage("Channel with this name does not exist"); + None + } + }, }; + + if let ConnectStatus::ErrorReplyMessage(m) = maybe_reply { + ctx.reply(m).await?; } - let manager = songbird::get(ctx.serenity_context()) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + if let Some(channel_id) = channel_id { + let manager = songbird::get(ctx.serenity_context()) + .await + .expect("Songbird Voice client placed in at initialisation.") + .clone(); - if let Ok(handler_lock) = manager.join(ctx.guild_id().unwrap(), channel).await { - let mut handler = handler_lock.lock().await; - handler.add_global_event(TrackEvent::Error.into(), TrackErrorNotifier); + if let Ok(handler_lock) = manager.join(ctx.guild_id().unwrap(), channel_id).await { + let mut handler = handler_lock.lock().await; + handler.add_global_event(TrackEvent::Error.into(), TrackErrorNotifier); + } } - ctx.reply("Done!").await?; Ok(()) } - struct TrackErrorNotifier; #[async_trait] @@ -136,15 +115,11 @@ impl VoiceEventHandler for TrackErrorNotifier { } } -async fn autocomplete_channel( - ctx: Context<'_>, - _partial: &str, -) -> Vec { - +async fn autocomplete_channel(ctx: Context<'_>, _partial: &str) -> Vec { use crate::util::utilities::get_vc_names; match ctx.guild() { Some(guild) => get_vc_names(guild), - None => [].to_vec() + None => [].to_vec(), } } @@ -152,10 +127,7 @@ async fn autocomplete_channel( slash_command, description_localized("en-US", "Disconnect from voice channel") )] -async fn disconnect( - ctx: Context<'_> -) -> Result<(), Error> { - +async fn disconnect(ctx: Context<'_>) -> Result<(), Error> { let Some(guild_id) = ctx.guild_id() else { ctx.reply("Can't use this outside of guild").await?; return Ok(()); @@ -168,19 +140,19 @@ async fn disconnect( let has_handler = manager.get(guild_id).is_some(); - if ! has_handler { + if !has_handler { ctx.reply("I am not connected to a channel!").await?; - return Ok(()) + return Ok(()); } match manager.remove(guild_id).await { Ok(()) => { ctx.reply("Disconnected").await?; - return Ok(()) + return Ok(()); } Err(e) => { let _ = send_error(ctx.serenity_context().http.clone(), e.to_string()).await; - return Ok(()) + return Ok(()); } } } diff --git a/src/main.rs b/src/main.rs index abfaf56..52a5ffd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,22 @@ +#![feature(let_chains)] +use std::env; +use std::error; use std::future::Future; use std::pin::Pin; use std::sync::Arc; use std::time::Duration; -use std::error; -use std::env; +use serenity::all::{EventHandler, Message}; use serenity::async_trait; -use serenity::prelude::GatewayIntents; use serenity::client::Context; use serenity::model::gateway::Ready; -use serenity::all::{EventHandler, Message}; +use serenity::prelude::GatewayIntents; use serenity::Client; use dotenv::dotenv; use songbird::SerenityInit; -use tokio_cron_scheduler::{JobScheduler, Job}; +use tokio_cron_scheduler::{Job, JobScheduler}; mod message_handler; use message_handler::handle; @@ -54,7 +55,8 @@ impl EventHandler for Handler { async fn ready(&self, ctx: Context, ready: Ready) { println!("{} v0.3 is connected!", ready.user.name); - #[cfg(feature="RELEASE")] { + #[cfg(feature = "RELEASE")] + { use util::debug::hello; hello(ctx.http.clone()).await; } @@ -63,12 +65,17 @@ impl EventHandler for Handler { let job_closure = move |_, _| -> Pin + Send>> { let ctx_clone = ctx.clone(); - Box::pin( async move { + Box::pin(async move { notice::notice_wrapper(ctx_clone).await; }) }; - sched.add(Job::new_async("0 0 13 * * *", job_closure).expect("Cron job not set up correctly")).await.unwrap(); + sched + .add( + Job::new_async("0 0 13 * * *", job_closure).expect("Cron job not set up correctly"), + ) + .await + .unwrap(); sched.start().await.unwrap(); } } @@ -80,50 +87,52 @@ async fn main() -> anyhow::Result<()> { dotenv().ok(); // create poise framework for registering commands - let options: poise::FrameworkOptions<(), Box> = poise::FrameworkOptions { - commands: vec![ - commands::say(), - commands::hug(), - // commands::spotify::player::play(), - commands::spotify::player::connect(), - ], - prefix_options: poise::PrefixFrameworkOptions { - prefix: Some("/".into()), - edit_tracker: Some(Arc::new(poise::EditTracker::for_timespan( - Duration::from_secs(3600), - ))), + let options: poise::FrameworkOptions<(), Box> = + poise::FrameworkOptions { + commands: vec![ + commands::say(), + commands::hug(), + // commands::spotify::player::play(), + commands::spotify::player::connect(), + ], + prefix_options: poise::PrefixFrameworkOptions { + prefix: Some("/".into()), + edit_tracker: Some(Arc::new(poise::EditTracker::for_timespan( + Duration::from_secs(3600), + ))), + ..Default::default() + }, + on_error: |err| Box::pin(on_error(err)), + command_check: Some(|ctx| Box::pin(async move { return Ok(!ctx.author().bot) })), ..Default::default() - }, - on_error: |err| Box::pin(on_error(err)), - command_check: Some(|ctx| { - Box::pin(async move { - return Ok(!ctx.author().bot) - }) - }), - ..Default::default() - }; + }; - let framework = poise::Framework::builder() - .setup(move |ctx, _ready, framework| { - Box::pin(async move { + .setup(move |ctx, _ready, framework| { + Box::pin(async move { + #[cfg(feature = "GUILD_COMMAND")] + { + use poise::samples::register_in_guild; + use serenity::all::GuildId; - - #[cfg(feature="GUILD_COMMAND")] { - use poise::samples::register_in_guild; - use serenity::all::GuildId; + let debug_guild_id = env::var("DEBUG_GUILD_ID") + .context("DEBUG_GUILD_ID not found in env")? + .parse::() + .unwrap(); - 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, GuildId::new(debug_guild_id)).await?; - } - #[cfg(not(feature="GUILD_COMMAND"))] { - use poise::samples::register_globally; - register_globally(ctx, &framework.options().commands).await?; - } - Ok(()) + register_in_guild( + ctx, + &framework.options().commands, + GuildId::new(debug_guild_id), + ) + .await?; + } + #[cfg(not(feature = "GUILD_COMMAND"))] + { + use poise::samples::register_globally; + register_globally(ctx, &framework.options().commands).await?; + } + Ok(()) }) }) .options(options) @@ -131,16 +140,16 @@ async fn main() -> anyhow::Result<()> { let token_str = "TOKEN"; - #[cfg(feature="DEBUG")] + #[cfg(feature = "DEBUG")] let token_str = "DEBUGTOKEN"; let token = env::var(token_str).context("TOKEN not found in env")?; let intents = GatewayIntents::GUILD_MESSAGES - | GatewayIntents::MESSAGE_CONTENT - | GatewayIntents::GUILD_VOICE_STATES - | GatewayIntents::GUILD_MESSAGE_REACTIONS - | GatewayIntents::GUILD_MESSAGE_TYPING; + | GatewayIntents::MESSAGE_CONTENT + | GatewayIntents::GUILD_VOICE_STATES + | GatewayIntents::GUILD_MESSAGE_REACTIONS + | GatewayIntents::GUILD_MESSAGE_TYPING; let mut client = Client::builder(&token, intents) .event_handler(Handler)