diff --git a/.cargo/config.toml b/.cargo/config.toml index ce8c578..21d631d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,5 @@ [target.x86_64-pc-windows-gnu] -rustflags = "-C link-args=-lssp" # Does not compile without this line \ No newline at end of file +rustflags = "-C link-args=-lssp" # Does not compile without this line + +[build] +rustflags = ["--cfg", "tokio_unstable"] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 270eef8..b0f7868 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,12 +88,39 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + [[package]] name = "arrayvec" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.60" @@ -158,6 +185,52 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.67" @@ -327,6 +400,42 @@ dependencies = [ "syn", ] +[[package]] +name = "console-api" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57ff02e8ad8e06ab9731d5dc72dc23bef9200778eae1a89d555d8c42e5d4a86" +dependencies = [ + "prost", + "prost-types", + "tonic", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a3a81dfaf6b66bce5d159eddae701e3a002f194d378cbf7be5f053c281d9be" +dependencies = [ + "console-api", + "crossbeam-channel 0.5.6", + "crossbeam-utils 0.8.14", + "futures", + "hdrhistogram", + "humantime", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -371,6 +480,16 @@ dependencies = [ "maybe-uninit", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.14", +] + [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -524,6 +643,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -838,6 +963,19 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hdrhistogram" +version = "7.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" +dependencies = [ + "base64", + "byteorder", + "flate2", + "nom", + "num-traits 0.2.15", +] + [[package]] name = "headers" version = "0.3.8" @@ -930,6 +1068,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -1000,6 +1144,18 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1092,7 +1248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cb1d9211085f0ea6f1379d944b93c4d07e8207aa3bcf49f37eda12b85081887" dependencies = [ "bincode", - "crossbeam-channel", + "crossbeam-channel 0.4.4", "fnv", "futures", "futures-test", @@ -1112,6 +1268,15 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.5" @@ -1435,6 +1600,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -1472,6 +1643,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1584,6 +1761,16 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1888,6 +2075,39 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda8c0881ea9f722eb9629376db3d0b903b462477c1aafcb0566610ac28ac5d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788" +dependencies = [ + "bytes", + "prost", +] + [[package]] name = "protobuf" version = "2.28.0" @@ -2528,6 +2748,7 @@ dependencies = [ name = "spoticord" version = "2.0.0-beta" dependencies = [ + "console-subscriber", "dotenv", "env_logger", "ipc-channel", @@ -2593,6 +2814,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + [[package]] name = "tempfile" version = "3.3.0" @@ -2726,9 +2953,20 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.42.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "1.8.2" @@ -2786,6 +3024,83 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" diff --git a/Cargo.toml b/Cargo.toml index 10a5bfe..f500e2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,11 @@ name = "spoticord" path = "src/main.rs" [dependencies] +console-subscriber = "0.1.8" dotenv = "0.15.0" env_logger = "0.9.3" ipc-channel = { version = "0.16.0", features = ["async"] } -librespot = { version = "0.4.2", default-features = false } +librespot = { version = "0.4.2", default-features = false } log = "0.4.17" redis = "0.22.1" reqwest = "0.11.12" diff --git a/Dockerfile b/Dockerfile index 817e333..dca4d6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,4 +20,6 @@ RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/ # Copy spoticord binary from builder COPY --from=builder /usr/local/cargo/bin/spoticord ./spoticord +ENV TOKIO_CONSOLE_BIND=0.0.0.0:4567 + CMD ["./spoticord"] \ No newline at end of file diff --git a/src/audio/backend.rs b/src/audio/backend.rs index 5e11561..3241ae7 100644 --- a/src/audio/backend.rs +++ b/src/audio/backend.rs @@ -42,9 +42,7 @@ impl Sink for StdoutSink { self .output .take() - .ok_or(SinkError::NotConnected( - "StdoutSink is not connected".to_string(), - ))? + .ok_or_else(|| SinkError::NotConnected("StdoutSink is not connected".to_string()))? .flush() .map_err(|why| SinkError::OnWrite(why.to_string()))?; @@ -62,9 +60,9 @@ impl Sink for StdoutSink { 48000, 2, samplerate::ConverterType::Linear, - &samples_f32, + samples_f32, ) - .unwrap(); + .expect("to succeed"); let samples_i16 = &converter.f64_to_s16(&resampled.iter().map(|v| *v as f64).collect::>()); @@ -81,9 +79,7 @@ impl SinkAsBytes for StdoutSink { self .output .as_deref_mut() - .ok_or(SinkError::NotConnected( - "StdoutSink is not connected".to_string(), - ))? + .ok_or_else(|| SinkError::NotConnected("StdoutSink is not connected".to_string()))? .write_all(data) .map_err(|why| SinkError::OnWrite(why.to_string()))?; diff --git a/src/bot/commands/core/help.rs b/src/bot/commands/core/help.rs index 1bf9ee9..afc1313 100644 --- a/src/bot/commands/core/help.rs +++ b/src/bot/commands/core/help.rs @@ -19,14 +19,14 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu EmbedBuilder::new() .title("Spoticord Help") .icon_url("https://spoticord.com/logo-standard.webp") - .description(format!("**Welcome to Spoticord** + .description("**Welcome to Spoticord** It seems you have requested some help. Not to worry, we can help you out.\n **Not sure how the bot works?** **[Click here](https://spoticord.com/#how-to)** for a quick overview about how to set up Spoticord and how to use it.\n **Which commands are there?** You can find all **[the commands](https://spoticord.com/#commands)** on the website. You may also just type `/` in Discord and see which commands are available there.\n **Need more help?** - If you still need some help, whether you are having issues with the bot or you just want to give us some feedback, you can join our **[Discord server](https://discord.gg/wRCyhVqBZ5)**.")) + If you still need some help, whether you are having issues with the bot or you just want to give us some feedback, you can join our **[Discord server](https://discord.gg/wRCyhVqBZ5)**.".to_string()) .status(Status::Info) .build(), false, diff --git a/src/bot/commands/core/link.rs b/src/bot/commands/core/link.rs index 9078625..d6f4af1 100644 --- a/src/bot/commands/core/link.rs +++ b/src/bot/commands/core/link.rs @@ -17,9 +17,13 @@ pub const NAME: &str = "link"; pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { Box::pin(async move { let data = ctx.data.read().await; - let database = data.get::().unwrap(); + let database = data.get::().expect("to contain a value"); - if let Ok(_) = database.get_user_account(command.user.id.to_string()).await { + if database + .get_user_account(command.user.id.to_string()) + .await + .is_ok() + { respond_message( &ctx, &command, @@ -35,7 +39,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu } if let Ok(request) = database.get_user_request(command.user.id.to_string()).await { - let base = std::env::var("SPOTICORD_ACCOUNTS_URL").unwrap(); + let base = std::env::var("SPOTICORD_ACCOUNTS_URL").expect("to be present"); let link = format!("{}/spotify/{}", base, request.token); respond_message( @@ -102,7 +106,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu .await { Ok(request) => { - let base = std::env::var("SPOTICORD_ACCOUNTS_URL").unwrap(); + let base = std::env::var("SPOTICORD_ACCOUNTS_URL").expect("to be present"); let link = format!("{}/spotify/{}", base, request.token); respond_message( @@ -121,9 +125,8 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu true, ) .await; - - return; } + Err(why) => { error!("Error creating user request: {:?}", why); @@ -137,8 +140,6 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu true, ) .await; - - return; } }; }) diff --git a/src/bot/commands/core/rename.rs b/src/bot/commands/core/rename.rs index a82dd54..814bdbe 100644 --- a/src/bot/commands/core/rename.rs +++ b/src/bot/commands/core/rename.rs @@ -22,7 +22,7 @@ pub const NAME: &str = "rename"; pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { Box::pin(async move { let data = ctx.data.read().await; - let database = data.get::().unwrap(); + let database = data.get::().expect("to contain a value"); // Check if user exists, if not, create them if let Err(why) = database.get_user(command.user.id.to_string()).await { @@ -65,7 +65,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu let device_name = match command.data.options.get(0) { Some(option) => match option.value { - Some(ref value) => value.as_str().unwrap().to_string(), + Some(ref value) => value.as_str().expect("to be a string").to_string(), None => { respond_message( &ctx, diff --git a/src/bot/commands/core/unlink.rs b/src/bot/commands/core/unlink.rs index 415a8c2..e5d5a8a 100644 --- a/src/bot/commands/core/unlink.rs +++ b/src/bot/commands/core/unlink.rs @@ -17,8 +17,8 @@ pub const NAME: &str = "unlink"; pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { Box::pin(async move { let data = ctx.data.read().await; - let database = data.get::().unwrap(); - let session_manager = data.get::().unwrap(); + let database = data.get::().expect("to contain a value"); + let session_manager = data.get::().expect("to contain a value"); // Disconnect session if user has any if let Some(session) = session_manager.find(command.user.id).await { diff --git a/src/bot/commands/mod.rs b/src/bot/commands/mod.rs index 4d5e4a0..7d357c8 100644 --- a/src/bot/commands/mod.rs +++ b/src/bot/commands/mod.rs @@ -170,7 +170,7 @@ impl CommandManager { cmds: &HashMap, mut commands: &'a mut CreateApplicationCommands, ) -> &'a mut CreateApplicationCommands { - for (_, command_info) in cmds { + for command_info in cmds.values() { commands = commands.create_application_command(|command| (command_info.register)(command)); } diff --git a/src/bot/commands/music/join.rs b/src/bot/commands/music/join.rs index 55ddfbb..c9c8c9a 100644 --- a/src/bot/commands/music/join.rs +++ b/src/bot/commands/music/join.rs @@ -15,7 +15,10 @@ pub const NAME: &str = "join"; pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { Box::pin(async move { - let guild = ctx.cache.guild(command.guild_id.unwrap()).unwrap(); + let guild = ctx + .cache + .guild(command.guild_id.expect("to contain a value")) + .expect("to be present"); // Get the voice channel id of the calling user let channel_id = match guild @@ -81,8 +84,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu } }; - if let Ok(permissions) = - channel.permissions_for_user(&ctx.cache, &ctx.cache.current_user_id()) + if let Ok(permissions) = channel.permissions_for_user(&ctx.cache, ctx.cache.current_user_id()) { if !permissions.view_channel() || !permissions.connect() || !permissions.speak() { respond_message( @@ -142,8 +144,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu } }; - if let Ok(permissions) = - channel.permissions_for_user(&ctx.cache, &ctx.cache.current_user_id()) + if let Ok(permissions) = channel.permissions_for_user(&ctx.cache, ctx.cache.current_user_id()) { if !permissions.view_channel() || !permissions.send_messages() || !permissions.embed_links() { @@ -167,7 +168,10 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu } let data = ctx.data.read().await; - let session_manager = data.get::().unwrap().clone(); + let session_manager = data + .get::() + .expect("to contain a value") + .clone(); // Check if another session is already active in this server let mut session_opt = session_manager.get_session(guild.id).await; @@ -206,7 +210,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu .description( format!( "You are already playing music in another server ({}).\nStop playing in that server first before joining this one.", - ctx.cache.guild(session.guild_id().await).unwrap().name + ctx.cache.guild(session.guild_id().await).expect("to be present").name )).status(Status::Error).build(), true, ) @@ -230,7 +234,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu if let Some(session) = session_opt.as_mut() { if let Err(why) = session.update_owner(&ctx, command.user.id).await { // Need to link first - if let SessionCreateError::NoSpotifyError = why { + if let SessionCreateError::NoSpotify = why { update_message( &ctx, &command, @@ -243,7 +247,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu .await; return; - } else if let SessionCreateError::NoLongerSpotifyError = why { + } else if let SessionCreateError::SpotifyExpired = why { update_message( &ctx, &command, @@ -284,7 +288,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu .await { // Need to link first - if let SessionCreateError::NoSpotifyError = why { + if let SessionCreateError::NoSpotify = why { update_message( &ctx, &command, @@ -297,7 +301,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu .await; return; - } else if let SessionCreateError::NoLongerSpotifyError = why { + } else if let SessionCreateError::SpotifyExpired = why { update_message( &ctx, &command, diff --git a/src/bot/commands/music/leave.rs b/src/bot/commands/music/leave.rs index f850402..2cd3b35 100644 --- a/src/bot/commands/music/leave.rs +++ b/src/bot/commands/music/leave.rs @@ -15,9 +15,15 @@ pub const NAME: &str = "leave"; pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { Box::pin(async move { let data = ctx.data.read().await; - let session_manager = data.get::().unwrap().clone(); + let session_manager = data + .get::() + .expect("to contain a value") + .clone(); - let session = match session_manager.get_session(command.guild_id.unwrap()).await { + let session = match session_manager + .get_session(command.guild_id.expect("to contain a value")) + .await + { Some(session) => session, None => { respond_message( diff --git a/src/bot/commands/music/playing.rs b/src/bot/commands/music/playing.rs index f7080f0..724727a 100644 --- a/src/bot/commands/music/playing.rs +++ b/src/bot/commands/music/playing.rs @@ -37,9 +37,15 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu }; let data = ctx.data.read().await; - let session_manager = data.get::().unwrap().clone(); + let session_manager = data + .get::() + .expect("to contain a value") + .clone(); - let session = match session_manager.get_session(command.guild_id.unwrap()).await { + let session = match session_manager + .get_session(command.guild_id.expect("to contain a value")) + .await + { Some(session) => session, None => { not_playing.await; @@ -86,8 +92,8 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu // Create title let title = format!( "{} - {}", - pbi.get_artists().unwrap(), - pbi.get_name().unwrap() + pbi.get_artists().expect("to contain a value"), + pbi.get_name().expect("to contain a value") ); // Create description @@ -100,9 +106,9 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu for i in 0..20 { if i == spot { - description.push_str("🔵"); + description.push('🔵'); } else { - description.push_str("▬"); + description.push('▬'); } } @@ -141,7 +147,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu }; // Get the thumbnail image - let thumbnail = pbi.get_thumbnail_url().unwrap(); + let thumbnail = pbi.get_thumbnail_url().expect("to contain a value"); if let Err(why) = command .create_interaction_response(&ctx.http, |response| { @@ -159,7 +165,9 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu .url(format!( "https://open.spotify.com/{}/{}", audio_type, - spotify_id.to_base62().unwrap() + spotify_id + .to_base62() + .expect("to be able to convert to base62") )) .description(description) .footer(|footer| footer.text(&owner.name).icon_url(owner.face())) diff --git a/src/bot/commands/ping.rs b/src/bot/commands/ping.rs index 39724cc..217189d 100644 --- a/src/bot/commands/ping.rs +++ b/src/bot/commands/ping.rs @@ -22,7 +22,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu .interaction_response_data(|message| message.content("Pong!")) }) .await - .unwrap(); + .ok(); }) } diff --git a/src/bot/commands/token.rs b/src/bot/commands/token.rs index bc78216..4615411 100644 --- a/src/bot/commands/token.rs +++ b/src/bot/commands/token.rs @@ -15,7 +15,7 @@ pub const NAME: &str = "token"; pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { Box::pin(async move { let data = ctx.data.read().await; - let db = data.get::().unwrap(); + let db = data.get::().expect("to contain a value"); let token = db.get_access_token(command.user.id.to_string()).await; @@ -31,7 +31,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu .interaction_response_data(|message| message.content(content).ephemeral(true)) }) .await - .unwrap(); + .ok(); }) } diff --git a/src/bot/events.rs b/src/bot/events.rs index f4b45e3..0db84a3 100644 --- a/src/bot/events.rs +++ b/src/bot/events.rs @@ -19,7 +19,7 @@ impl EventHandler for Handler { // READY event, emitted when the bot/shard starts up async fn ready(&self, ctx: Context, ready: Ready) { let data = ctx.data.read().await; - let command_manager = data.get::().unwrap(); + let command_manager = data.get::().expect("to contain a value"); debug!("Ready received, logged in as {}", ready.user.name); @@ -78,7 +78,7 @@ impl EventHandler for Handler { ); let data = ctx.data.read().await; - let command_manager = data.get::().unwrap(); + let command_manager = data.get::().expect("to contain a value"); command_manager.execute_command(&ctx, command).await; } diff --git a/src/database.rs b/src/database.rs index 7666545..a433894 100644 --- a/src/database.rs +++ b/src/database.rs @@ -320,7 +320,7 @@ impl Database { ) -> Result<(), DatabaseError> { let device_name: String = name.into(); - if device_name.len() > 16 || device_name.len() < 1 { + if device_name.len() > 16 || device_name.is_empty() { return Err(DatabaseError::InvalidInputBody( "Invalid device name length".into(), )); @@ -345,7 +345,7 @@ impl Database { StatusCode::OK | StatusCode::CREATED | StatusCode::ACCEPTED | StatusCode::NO_CONTENT => { Ok(()) } - status => return Err(DatabaseError::InvalidStatusCode(status)), + status => Err(DatabaseError::InvalidStatusCode(status)), } } } diff --git a/src/ipc/mod.rs b/src/ipc/mod.rs index 0c9dff6..266e908 100644 --- a/src/ipc/mod.rs +++ b/src/ipc/mod.rs @@ -58,12 +58,12 @@ impl Client { self .tx .lock() - .unwrap() + .expect("to be able to lock") .send(packet) .map_err(IpcError::Bincode) } pub fn try_recv(&self) -> Result { - self.rx.lock().unwrap().try_recv() + self.rx.lock().expect("to be able to lock").try_recv() } } diff --git a/src/main.rs b/src/main.rs index 063ef13..fd1658a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,8 @@ mod utils; #[tokio::main] async fn main() { + console_subscriber::init(); + if std::env::var("RUST_LOG").is_err() { #[cfg(debug_assertions)] { @@ -42,18 +44,16 @@ async fn main() { let args: Vec = env::args().collect(); - if args.len() > 2 { - if &args[1] == "--player" { - // Woah! We're running in player mode! + if args.len() > 2 && &args[1] == "--player" { + // Woah! We're running in player mode! - debug!("Starting Spoticord player"); + debug!("Starting Spoticord player"); - player::main().await; + player::main().await; - debug!("Player exited, shutting down"); + debug!("Player exited, shutting down"); - return; - } + return; } info!("It's a good day"); @@ -62,7 +62,10 @@ async fn main() { let result = dotenv(); if let Ok(path) = result { - debug!("Loaded environment file: {}", path.to_str().unwrap()); + debug!( + "Loaded environment file: {}", + path.to_str().expect("to get the string") + ); } else { warn!("No .env file found, expecting all necessary environment variables"); } @@ -83,7 +86,7 @@ async fn main() { .framework(StandardFramework::new()) .register_songbird() .await - .unwrap(); + .expect("to create a client"); { let mut data = client.data.write().await; @@ -98,7 +101,8 @@ async fn main() { #[cfg(unix)] let mut term: Option> = Some(Box::new( - tokio::signal::unix::signal(SignalKind::terminate()).unwrap(), + tokio::signal::unix::signal(SignalKind::terminate()) + .expect("to be able to create the signal stream"), )); #[cfg(not(unix))] @@ -145,7 +149,7 @@ async fn main() { #[cfg(unix)] match term { Some(ref mut term) => { - let term = term.downcast_mut::().unwrap(); + let term = term.downcast_mut::().expect("to be able to downcast"); term.recv().await } diff --git a/src/player.rs b/src/player.rs index 894b5ae..1e464ac 100644 --- a/src/player.rs +++ b/src/player.rs @@ -43,7 +43,9 @@ impl SpoticordPlayer { let token = token.into(); // Get the username (required for librespot) - let username = utils::spotify::get_username(&token).await.unwrap(); + let username = utils::spotify::get_username(&token) + .await + .expect("to get the username"); let session_config = SessionConfig::default(); let player_config = PlayerConfig { @@ -68,7 +70,7 @@ impl SpoticordPlayer { self .client .send(IpcPacket::ConnectError(why.to_string())) - .unwrap(); + .ok(); return; } }; @@ -77,7 +79,7 @@ impl SpoticordPlayer { self.session = Some(session.clone()); // Volume mixer - let mixer = (mixer::find(Some("softvol")).unwrap())(MixerConfig { + let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig { volume_ctrl: librespot::playback::config::VolumeCtrl::Linear, ..MixerConfig::default() }); @@ -138,13 +140,13 @@ impl SpoticordPlayer { .send(IpcPacket::ConnectError( "Switch to Spoticord device timed out".to_string(), )) - .unwrap(); + .ok(); break; } } Err(why) => { error!("Failed to set device: {}", why); - ipc.send(IpcPacket::ConnectError(why.to_string())).unwrap(); + ipc.send(IpcPacket::ConnectError(why.to_string())).ok(); break; } } @@ -167,7 +169,7 @@ impl SpoticordPlayer { duration_ms, } => { if let Err(why) = ipc.send(IpcPacket::Playing( - track_id.to_uri().unwrap(), + track_id.to_uri().expect("to not fail"), position_ms, duration_ms, )) { @@ -182,7 +184,7 @@ impl SpoticordPlayer { duration_ms, } => { if let Err(why) = ipc.send(IpcPacket::Paused( - track_id.to_uri().unwrap(), + track_id.to_uri().expect("to not fail"), position_ms, duration_ms, )) { @@ -194,7 +196,9 @@ impl SpoticordPlayer { old_track_id: _, new_track_id, } => { - if let Err(why) = ipc.send(IpcPacket::TrackChange(new_track_id.to_uri().unwrap())) { + if let Err(why) = ipc.send(IpcPacket::TrackChange( + new_track_id.to_uri().expect("to not fail"), + )) { error!("Failed to send track change packet: {}", why); } } @@ -247,11 +251,9 @@ pub async fn main() { tokio::time::sleep(Duration::from_millis(25)).await; continue; - } else if let TryRecvError::IpcError(why) = &why { - if let IpcError::Disconnected = why { - debug!("IPC connection closed, goodbye"); - break; - } + } else if let TryRecvError::IpcError(IpcError::Disconnected) = &why { + debug!("IPC connection closed, goodbye"); + break; } error!("Failed to receive message: {}", why); diff --git a/src/session/manager.rs b/src/session/manager.rs index fa4c265..6421963 100644 --- a/src/session/manager.rs +++ b/src/session/manager.rs @@ -11,13 +11,13 @@ use super::SpoticordSession; #[derive(Debug, Error)] pub enum SessionCreateError { #[error("This session has no owner assigned")] - NoOwnerError, + NoOwner, #[error("The user has not linked their Spotify account")] - NoSpotifyError, + NoSpotify, #[error("The application no longer has access to the user's Spotify account")] - NoLongerSpotifyError, + SpotifyExpired, #[error("An error has occured while communicating with the database")] DatabaseError, @@ -99,7 +99,7 @@ impl InnerSessionManager { pub fn find(&self, owner_id: UserId) -> Option { let guild_id = self.owner_map.get(&owner_id)?; - self.sessions.get(&guild_id).cloned() + self.sessions.get(guild_id).cloned() } /// Get the amount of sessions diff --git a/src/session/mod.rs b/src/session/mod.rs index 7e59d24..c9e813b 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -70,10 +70,13 @@ impl SpoticordSession { ) -> Result { // Get the Spotify token of the owner let data = ctx.data.read().await; - let session_manager = data.get::().unwrap().clone(); + let session_manager = data + .get::() + .expect("to contain a value") + .clone(); // Join the voice channel - let songbird = songbird::get(ctx).await.unwrap().clone(); + let songbird = songbird::get(ctx).await.expect("to be present").clone(); let (call, result) = songbird.join(guild_id, channel_id).await; @@ -83,7 +86,7 @@ impl SpoticordSession { } let inner = InnerSpoticordSession { - owner: Some(owner_id.clone()), + owner: Some(owner_id), guild_id, channel_id, text_channel_id, @@ -124,7 +127,10 @@ impl SpoticordSession { ) -> Result<(), SessionCreateError> { // Get the Spotify token of the owner let data = ctx.data.read().await; - let session_manager = data.get::().unwrap().clone(); + let session_manager = data + .get::() + .expect("to contain a value") + .clone(); { let mut inner = self.0.write().await; @@ -144,22 +150,22 @@ impl SpoticordSession { } async fn create_player(&mut self, ctx: &Context) -> Result<(), SessionCreateError> { - let owner_id = match self.owner().await.clone() { + let owner_id = match self.owner().await { Some(owner_id) => owner_id, - None => return Err(SessionCreateError::NoOwnerError), + None => return Err(SessionCreateError::NoOwner), }; let data = ctx.data.read().await; - let database = data.get::().unwrap(); + let database = data.get::().expect("to contain a value"); let token = match database.get_access_token(owner_id.to_string()).await { Ok(token) => token, Err(why) => { if let DatabaseError::InvalidStatusCode(code) = why { if code == 404 { - return Err(SessionCreateError::NoSpotifyError); + return Err(SessionCreateError::NoSpotify); } else if code == 400 { - return Err(SessionCreateError::NoLongerSpotifyError); + return Err(SessionCreateError::SpotifyExpired); } } @@ -185,24 +191,25 @@ impl SpoticordSession { }; // Spawn player process - let child = match Command::new(std::env::current_exe().unwrap()) - .args([ - "--player", - &tx_name, - &rx_name, - "--debug-guild-id", - &self.guild_id().await.to_string(), - ]) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn() - { - Ok(child) => child, - Err(why) => { - error!("Failed to start player process: {:?}", why); - return Err(SessionCreateError::ForkError); - } - }; + let child = + match Command::new(std::env::current_exe().expect("to know the current executable path")) + .args([ + "--player", + &tx_name, + &rx_name, + "--debug-guild-id", + &self.guild_id().await.to_string(), + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + { + Ok(child) => child, + Err(why) => { + error!("Failed to start player process: {:?}", why); + return Err(SessionCreateError::ForkError); + } + }; // Establish bi-directional IPC channel let client = match server.accept() { @@ -256,11 +263,9 @@ impl SpoticordSession { tokio::time::sleep(Duration::from_millis(25)).await; continue; - } else if let TryRecvError::IpcError(why) = &why { - if let IpcError::Disconnected = why { - trace!("IPC connection closed, exiting IPC handler"); - break; - } + } else if let TryRecvError::IpcError(IpcError::Disconnected) = &why { + trace!("IPC connection closed, exiting IPC handler"); + break; } error!("Failed to receive IPC message: {:?}", why); @@ -310,7 +315,7 @@ impl SpoticordSession { // A new track has been set by the player IpcPacket::TrackChange(track) => { // Convert to SpotifyId - let track_id = SpotifyId::from_uri(&track).unwrap(); + let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); let instance = instance.clone(); let ctx = ctx.clone(); @@ -329,7 +334,7 @@ impl SpoticordSession { // The player has started playing a track IpcPacket::Playing(track, position_ms, duration_ms) => { // Convert to SpotifyId - let track_id = SpotifyId::from_uri(&track).unwrap(); + let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); let was_none = instance .update_playback(duration_ms, position_ms, true) @@ -350,7 +355,7 @@ impl SpoticordSession { instance.start_disconnect_timer().await; // Convert to SpotifyId - let track_id = SpotifyId::from_uri(&track).unwrap(); + let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); let was_none = instance .update_playback(duration_ms, position_ms, false) @@ -415,7 +420,7 @@ impl SpoticordSession { let pbi = self.playback_info().await; if let Some(pbi) = pbi { - pbi.spotify_id.is_none() || pbi.spotify_id.unwrap() != spotify_id + pbi.spotify_id.is_none() || pbi.spotify_id != Some(spotify_id) } else { false } @@ -426,7 +431,7 @@ impl SpoticordSession { } let data = ctx.data.read().await; - let database = data.get::().unwrap(); + let database = data.get::().expect("to contain a value"); let token = match database.get_access_token(&owner_id.to_string()).await { Ok(token) => token, @@ -549,7 +554,7 @@ impl SpoticordSession { inner .playback_info .as_mut() - .unwrap() + .expect("to contain a value") .update_pos_dur(position_ms, duration_ms, playing); }; diff --git a/src/session/pbi.rs b/src/session/pbi.rs index 2443b1b..36df553 100644 --- a/src/session/pbi.rs +++ b/src/session/pbi.rs @@ -66,10 +66,8 @@ impl PlaybackInfo { pub fn get_name(&self) -> Option { if let Some(track) = &self.track { Some(track.name.clone()) - } else if let Some(episode) = &self.episode { - Some(episode.name.clone()) } else { - None + self.episode.as_ref().map(|episode| episode.name.clone()) } } @@ -84,10 +82,11 @@ impl PlaybackInfo { .collect::>() .join(", "), ) - } else if let Some(episode) = &self.episode { - Some(episode.show.name.clone()) } else { - None + self + .episode + .as_ref() + .map(|episode| episode.show.name.clone()) } } @@ -97,12 +96,12 @@ impl PlaybackInfo { let mut images = track.album.images.clone(); images.sort_by(|a, b| b.width.cmp(&a.width)); - Some(images.get(0).unwrap().url.clone()) + images.get(0).as_ref().map(|image| image.url.clone()) } else if let Some(episode) = &self.episode { let mut images = episode.show.images.clone(); images.sort_by(|a, b| b.width.cmp(&a.width)); - Some(images.get(0).unwrap().url.clone()) + images.get(0).as_ref().map(|image| image.url.clone()) } else { None } diff --git a/src/utils/discord.rs b/src/utils/discord.rs index 9643c8d..e0068e5 100644 --- a/src/utils/discord.rs +++ b/src/utils/discord.rs @@ -7,11 +7,11 @@ pub fn escape(text: impl Into) -> String { let text: String = text.into(); text - .replace("\\", "\\\\") - .replace("*", "\\*") - .replace("_", "\\_") - .replace("~", "\\~") - .replace("`", "\\`") + .replace('\\', "\\\\") + .replace('*', "\\*") + .replace('_', "\\_") + .replace('~', "\\~") + .replace('`', "\\`") } pub async fn get_user(ctx: &Context, id: UserId) -> Option { diff --git a/src/utils/embed.rs b/src/utils/embed.rs index 075baaa..ae52522 100644 --- a/src/utils/embed.rs +++ b/src/utils/embed.rs @@ -65,10 +65,10 @@ impl EmbedBuilder { } } -pub fn make_embed_message<'a>( - embed: &'a mut CreateEmbed, +pub fn make_embed_message( + embed: &'_ mut CreateEmbed, options: EmbedMessageOptions, -) -> &'a mut CreateEmbed { +) -> &'_ mut CreateEmbed { let status = options.status.unwrap_or(Status::None); embed.author(|author| { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c9520ed..3199249 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -23,15 +23,15 @@ pub fn time_to_str(time: u32) -> String { let min = 60; if time / hour >= 1 { - return format!( + format!( "{}h{}m{}s", time / hour, (time % hour) / min, (time % hour) % min - ); + ) } else if time / min >= 1 { - return format!("{}m{}s", time / min, time % min); + format!("{}m{}s", time / min, time % min) } else { - return format!("{}s", time); + format!("{}s", time) } } diff --git a/src/utils/spotify.rs b/src/utils/spotify.rs index 2707d08..31929c3 100644 --- a/src/utils/spotify.rs +++ b/src/utils/spotify.rs @@ -69,13 +69,10 @@ pub async fn get_username(token: impl Into) -> Result { if response.status() != 200 { error!("Failed to get username: {}", response.status()); - return Err( - format!( - "Failed to get track info: Invalid status code: {}", - response.status() - ) - .into(), - ); + return Err(format!( + "Failed to get track info: Invalid status code: {}", + response.status() + )); } let body: Value = match response.json().await {