Performance upgrades, fixes for 2 issues
This commit is contained in:
		
							parent
							
								
									dccf8c2057
								
							
						
					
					
						commit
						f3dff49c06
					
				| @ -7,12 +7,6 @@ edition = "2021" | |||||||
| name = "spoticord" | name = "spoticord" | ||||||
| path = "src/main.rs" | path = "src/main.rs" | ||||||
| 
 | 
 | ||||||
| [profile.release] |  | ||||||
| lto = true |  | ||||||
| codegen-units = 1 |  | ||||||
| strip = true |  | ||||||
| opt-level = "z" |  | ||||||
| 
 |  | ||||||
| [dependencies] | [dependencies] | ||||||
| chrono = "0.4.22" | chrono = "0.4.22" | ||||||
| dotenv = "0.15.0" | dotenv = "0.15.0" | ||||||
|  | |||||||
| @ -44,13 +44,32 @@ pub async fn respond_message( | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | pub async fn defer_message( | ||||||
|  |   ctx: &Context, | ||||||
|  |   command: &ApplicationCommandInteraction, | ||||||
|  |   ephemeral: bool, | ||||||
|  | ) { | ||||||
|  |   if let Err(why) = command | ||||||
|  |     .create_interaction_response(&ctx.http, |response| { | ||||||
|  |       response | ||||||
|  |         .kind(InteractionResponseType::DeferredChannelMessageWithSource) | ||||||
|  |         .interaction_response_data(|message| message.ephemeral(ephemeral)) | ||||||
|  |     }) | ||||||
|  |     .await | ||||||
|  |   { | ||||||
|  |     error!("Error deferring message: {:?}", why); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| pub type CommandOutput = Pin<Box<dyn Future<Output = ()> + Send>>; | pub type CommandOutput = Pin<Box<dyn Future<Output = ()> + Send>>; | ||||||
| pub type CommandExecutor = fn(Context, ApplicationCommandInteraction) -> CommandOutput; | pub type CommandExecutor = fn(Context, ApplicationCommandInteraction) -> CommandOutput; | ||||||
| 
 | 
 | ||||||
|  | #[derive(Clone)] | ||||||
| pub struct CommandManager { | pub struct CommandManager { | ||||||
|   commands: HashMap<String, CommandInfo>, |   commands: HashMap<String, CommandInfo>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Clone)] | ||||||
| pub struct CommandInfo { | pub struct CommandInfo { | ||||||
|   pub name: String, |   pub name: String, | ||||||
|   pub executor: CommandExecutor, |   pub executor: CommandExecutor, | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ use serenity::{ | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|   bot::commands::{respond_message, CommandOutput}, |   bot::commands::{defer_message, respond_message, CommandOutput}, | ||||||
|   session::manager::{SessionCreateError, SessionManager}, |   session::manager::{SessionCreateError, SessionManager}, | ||||||
|   utils::embed::{EmbedBuilder, Status}, |   utils::embed::{EmbedBuilder, Status}, | ||||||
| }; | }; | ||||||
| @ -46,6 +46,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
| 
 | 
 | ||||||
|     // Check if another session is already active in this server
 |     // Check if another session is already active in this server
 | ||||||
|     let session_opt = session_manager.get_session(guild.id).await; |     let session_opt = session_manager.get_session(guild.id).await; | ||||||
|  | 
 | ||||||
|     if let Some(session) = &session_opt { |     if let Some(session) = &session_opt { | ||||||
|       if let Some(owner) = session.get_owner().await { |       if let Some(owner) = session.get_owner().await { | ||||||
|         let msg = if owner == command.user.id { |         let msg = if owner == command.user.id { | ||||||
| @ -91,6 +92,8 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     defer_message(&ctx, &command, true).await; | ||||||
|  | 
 | ||||||
|     if let Some(session) = &session_opt { |     if let Some(session) = &session_opt { | ||||||
|       if let Err(why) = session.update_owner(&ctx, command.user.id).await { |       if let Err(why) = session.update_owner(&ctx, command.user.id).await { | ||||||
|         // Need to link first
 |         // Need to link first
 | ||||||
|  | |||||||
| @ -23,7 +23,10 @@ impl EventHandler for Handler { | |||||||
| 
 | 
 | ||||||
|     debug!("Ready received, logged in as {}", ready.user.name); |     debug!("Ready received, logged in as {}", ready.user.name); | ||||||
| 
 | 
 | ||||||
|     command_manager.register_commands(&ctx).await; |     // Set this to true only when a command is removed/updated/created
 | ||||||
|  |     if false { | ||||||
|  |       command_manager.register_commands(&ctx).await; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     ctx.set_activity(Activity::listening(MOTD)).await; |     ctx.set_activity(Activity::listening(MOTD)).await; | ||||||
| 
 | 
 | ||||||
| @ -32,10 +35,15 @@ impl EventHandler for Handler { | |||||||
| 
 | 
 | ||||||
|   // INTERACTION_CREATE event, emitted when the bot receives an interaction (slash command, button, etc.)
 |   // INTERACTION_CREATE event, emitted when the bot receives an interaction (slash command, button, etc.)
 | ||||||
|   async fn interaction_create(&self, ctx: Context, interaction: Interaction) { |   async fn interaction_create(&self, ctx: Context, interaction: Interaction) { | ||||||
|  |     trace!("interaction_create START"); | ||||||
|  | 
 | ||||||
|     if let Interaction::ApplicationCommand(command) = interaction { |     if let Interaction::ApplicationCommand(command) = interaction { | ||||||
|       // Commands must only be executed inside of guilds
 |       // Commands must only be executed inside of guilds
 | ||||||
|       if command.guild_id.is_none() { | 
 | ||||||
|         command |       let guild_id = match command.guild_id { | ||||||
|  |         Some(guild_id) => guild_id, | ||||||
|  |         None => { | ||||||
|  |           if let Err(why) = command | ||||||
|           .create_interaction_response(&ctx.http, |response| { |           .create_interaction_response(&ctx.http, |response| { | ||||||
|             response |             response | ||||||
|               .kind(serenity::model::prelude::interaction::InteractionResponseType::ChannelMessageWithSource) |               .kind(serenity::model::prelude::interaction::InteractionResponseType::ChannelMessageWithSource) | ||||||
| @ -43,17 +51,20 @@ impl EventHandler for Handler { | |||||||
|                 message.content("You can only execute commands inside of a server") |                 message.content("You can only execute commands inside of a server") | ||||||
|               }) |               }) | ||||||
|           }) |           }) | ||||||
|           .await |           .await { | ||||||
|           .unwrap(); |             error!("Failed to send run-in-guild-only error message: {}", why); | ||||||
|  |           } | ||||||
| 
 | 
 | ||||||
|         return; |           trace!("interaction_create END2"); | ||||||
|       } |           return; | ||||||
|  |         } | ||||||
|  |       }; | ||||||
| 
 | 
 | ||||||
|       trace!( |       trace!( | ||||||
|         "Received command interaction: command={} user={} guild={}", |         "Received command interaction: command={} user={} guild={}", | ||||||
|         command.data.name, |         command.data.name, | ||||||
|         command.user.id, |         command.user.id, | ||||||
|         command.guild_id.unwrap() |         guild_id | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|       let data = ctx.data.read().await; |       let data = ctx.data.read().await; | ||||||
| @ -61,5 +72,7 @@ impl EventHandler for Handler { | |||||||
| 
 | 
 | ||||||
|       command_manager.execute_command(&ctx, command).await; |       command_manager.execute_command(&ctx, command).await; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     trace!("interaction_create END"); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,3 +1,3 @@ | |||||||
| pub const VERSION: &str = env!("CARGO_PKG_VERSION"); | pub const VERSION: &str = env!("CARGO_PKG_VERSION"); | ||||||
| pub const MOTD: &str = "OPEN BETA (v2)"; | pub const MOTD: &str = "UNSTABLE BETA (v2)"; | ||||||
| // pub const MOTD: &str = "some good 'ol music";
 | // pub const MOTD: &str = "some good 'ol music";
 | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| use std::sync::{Arc, Mutex}; | use std::sync::{Arc, Mutex}; | ||||||
| 
 | 
 | ||||||
| use ipc_channel::ipc::{self, IpcError, IpcOneShotServer, IpcReceiver, IpcSender}; | use ipc_channel::ipc::{self, IpcError, IpcOneShotServer, IpcReceiver, IpcSender, TryRecvError}; | ||||||
| 
 | 
 | ||||||
| use self::packet::IpcPacket; | use self::packet::IpcPacket; | ||||||
| 
 | 
 | ||||||
| @ -66,4 +66,8 @@ impl Client { | |||||||
|   pub fn recv(&self) -> Result<IpcPacket, IpcError> { |   pub fn recv(&self) -> Result<IpcPacket, IpcError> { | ||||||
|     self.rx.lock().unwrap().recv() |     self.rx.lock().unwrap().recv() | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   pub fn try_recv(&self) -> Result<IpcPacket, TryRecvError> { | ||||||
|  |     self.rx.lock().unwrap().try_recv() | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -23,7 +23,7 @@ mod session; | |||||||
| mod stats; | mod stats; | ||||||
| mod utils; | mod utils; | ||||||
| 
 | 
 | ||||||
| #[tokio::main(flavor = "multi_thread")] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|   if std::env::var("RUST_LOG").is_err() { |   if std::env::var("RUST_LOG").is_err() { | ||||||
|     #[cfg(debug_assertions)] |     #[cfg(debug_assertions)] | ||||||
| @ -39,6 +39,14 @@ async fn main() { | |||||||
| 
 | 
 | ||||||
|   env_logger::init(); |   env_logger::init(); | ||||||
| 
 | 
 | ||||||
|  |   let orig_hook = std::panic::take_hook(); | ||||||
|  |   std::panic::set_hook(Box::new(move |panic_info| { | ||||||
|  |     error!("Panic: {}", panic_info); | ||||||
|  | 
 | ||||||
|  |     orig_hook(panic_info); | ||||||
|  |     std::process::exit(1); | ||||||
|  |   })); | ||||||
|  | 
 | ||||||
|   let args: Vec<String> = env::args().collect(); |   let args: Vec<String> = env::args().collect(); | ||||||
| 
 | 
 | ||||||
|   if args.len() > 2 { |   if args.len() > 2 { | ||||||
| @ -95,6 +103,7 @@ async fn main() { | |||||||
|   let shard_manager = client.shard_manager.clone(); |   let shard_manager = client.shard_manager.clone(); | ||||||
|   let cache = client.cache_and_http.cache.clone(); |   let cache = client.cache_and_http.cache.clone(); | ||||||
| 
 | 
 | ||||||
|  |   #[cfg(unix)] | ||||||
|   let mut sigterm = tokio::signal::unix::signal(SignalKind::terminate()).unwrap(); |   let mut sigterm = tokio::signal::unix::signal(SignalKind::terminate()).unwrap(); | ||||||
| 
 | 
 | ||||||
|   // Background tasks
 |   // Background tasks
 | ||||||
|  | |||||||
| @ -49,11 +49,12 @@ impl SessionManager { | |||||||
|     channel_id: ChannelId, |     channel_id: ChannelId, | ||||||
|     owner_id: UserId, |     owner_id: UserId, | ||||||
|   ) -> Result<(), SessionCreateError> { |   ) -> Result<(), SessionCreateError> { | ||||||
|  |     // Create session first to make sure locks are kept for as little time as possible
 | ||||||
|  |     let session = SpoticordSession::new(ctx, guild_id, channel_id, owner_id).await?; | ||||||
|  | 
 | ||||||
|     let mut sessions = self.sessions.write().await; |     let mut sessions = self.sessions.write().await; | ||||||
|     let mut owner_map = self.owner_map.write().await; |     let mut owner_map = self.owner_map.write().await; | ||||||
| 
 | 
 | ||||||
|     let session = SpoticordSession::new(ctx, guild_id, channel_id, owner_id).await?; |  | ||||||
| 
 |  | ||||||
|     sessions.insert(guild_id, Arc::new(session)); |     sessions.insert(guild_id, Arc::new(session)); | ||||||
|     owner_map.insert(owner_id, guild_id); |     owner_map.insert(owner_id, guild_id); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ use crate::{ | |||||||
|   ipc::{self, packet::IpcPacket, Client}, |   ipc::{self, packet::IpcPacket, Client}, | ||||||
|   utils::{self, spotify}, |   utils::{self, spotify}, | ||||||
| }; | }; | ||||||
| use ipc_channel::ipc::IpcError; | use ipc_channel::ipc::{IpcError, TryRecvError}; | ||||||
| use librespot::core::spotify_id::{SpotifyAudioType, SpotifyId}; | use librespot::core::spotify_id::{SpotifyAudioType, SpotifyId}; | ||||||
| use log::*; | use log::*; | ||||||
| use serenity::{ | use serenity::{ | ||||||
| @ -21,6 +21,7 @@ use songbird::{ | |||||||
| use std::{ | use std::{ | ||||||
|   process::{Command, Stdio}, |   process::{Command, Stdio}, | ||||||
|   sync::Arc, |   sync::Arc, | ||||||
|  |   time::Duration, | ||||||
| }; | }; | ||||||
| use tokio::sync::Mutex; | use tokio::sync::Mutex; | ||||||
| 
 | 
 | ||||||
| @ -262,11 +263,18 @@ impl SpoticordSession { | |||||||
|         // Required for IpcPacket::TrackChange to work
 |         // Required for IpcPacket::TrackChange to work
 | ||||||
|         tokio::task::yield_now().await; |         tokio::task::yield_now().await; | ||||||
| 
 | 
 | ||||||
|         let msg = match ipc_client.recv() { |         let msg = match ipc_client.try_recv() { | ||||||
|           Ok(msg) => msg, |           Ok(msg) => msg, | ||||||
|           Err(why) => { |           Err(why) => { | ||||||
|             if let IpcError::Disconnected = why { |             if let TryRecvError::Empty = why { | ||||||
|               break; |               // No message, wait a bit and try again
 | ||||||
|  |               tokio::time::sleep(Duration::from_millis(25)).await; | ||||||
|  | 
 | ||||||
|  |               continue; | ||||||
|  |             } else if let TryRecvError::IpcError(why) = &why { | ||||||
|  |               if let IpcError::Disconnected = why { | ||||||
|  |                 break; | ||||||
|  |               } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             error!("Failed to receive IPC message: {:?}", why); |             error!("Failed to receive IPC message: {:?}", why); | ||||||
| @ -407,8 +415,10 @@ impl SpoticordSession { | |||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let mut owner = self.owner.write().await; |     { | ||||||
|     *owner = Some(owner_id); |       let mut owner = self.owner.write().await; | ||||||
|  |       *owner = Some(owner_id); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     session_manager.set_owner(owner_id, self.guild_id).await; |     session_manager.set_owner(owner_id, self.guild_id).await; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -46,34 +46,53 @@ pub async fn get_username(token: impl Into<String>) -> Result<String, String> { | |||||||
|   let token = token.into(); |   let token = token.into(); | ||||||
|   let client = reqwest::Client::new(); |   let client = reqwest::Client::new(); | ||||||
| 
 | 
 | ||||||
|   let response = match client |   let mut retries = 3; | ||||||
|     .get("https://api.spotify.com/v1/me") |  | ||||||
|     .bearer_auth(token) |  | ||||||
|     .send() |  | ||||||
|     .await |  | ||||||
|   { |  | ||||||
|     Ok(response) => response, |  | ||||||
|     Err(why) => { |  | ||||||
|       error!("Failed to get username: {}", why); |  | ||||||
|       return Err(format!("{}", why)); |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
| 
 | 
 | ||||||
|   let body: Value = match response.json().await { |   loop { | ||||||
|     Ok(body) => body, |     let response = match client | ||||||
|     Err(why) => { |       .get("https://api.spotify.com/v1/me") | ||||||
|       error!("Failed to parse body: {}", why); |       .bearer_auth(&token) | ||||||
|       return Err(format!("{}", why)); |       .send() | ||||||
|     } |       .await | ||||||
|   }; |     { | ||||||
|  |       Ok(response) => response, | ||||||
|  |       Err(why) => { | ||||||
|  |         error!("Failed to get username: {}", why); | ||||||
|  |         return Err(format!("{}", why)); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|   if let Value::String(username) = &body["id"] { |     if response.status().as_u16() >= 500 && retries > 0 { | ||||||
|     trace!("Got username: {}", username); |       retries -= 1; | ||||||
|     return Ok(username.clone()); |       continue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if response.status() != 200 { | ||||||
|  |       return Err( | ||||||
|  |         format!( | ||||||
|  |           "Failed to get track info: Invalid status code: {}", | ||||||
|  |           response.status() | ||||||
|  |         ) | ||||||
|  |         .into(), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let body: Value = match response.json().await { | ||||||
|  |       Ok(body) => body, | ||||||
|  |       Err(why) => { | ||||||
|  |         error!("Failed to parse body: {}", why); | ||||||
|  |         return Err(format!("{}", why)); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     if let Value::String(username) = &body["id"] { | ||||||
|  |       trace!("Got username: {}", username); | ||||||
|  |       return Ok(username.clone()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     error!("Missing 'id' field in body: {:#?}", body); | ||||||
|  |     return Err("Failed to parse body: Invalid body received".to_string()); | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   error!("Missing 'id' field in body"); |  | ||||||
|   Err("Failed to parse body: Invalid body received".to_string()) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub async fn get_track_info( | pub async fn get_track_info( | ||||||
| @ -83,26 +102,35 @@ pub async fn get_track_info( | |||||||
|   let token = token.into(); |   let token = token.into(); | ||||||
|   let client = reqwest::Client::new(); |   let client = reqwest::Client::new(); | ||||||
| 
 | 
 | ||||||
|   let response = client |   let mut retries = 3; | ||||||
|     .get(format!( |  | ||||||
|       "https://api.spotify.com/v1/tracks/{}", |  | ||||||
|       track.to_base62()? |  | ||||||
|     )) |  | ||||||
|     .bearer_auth(token) |  | ||||||
|     .send() |  | ||||||
|     .await?; |  | ||||||
| 
 | 
 | ||||||
|   if response.status() != 200 { |   loop { | ||||||
|     return Err( |     let response = client | ||||||
|       format!( |       .get(format!( | ||||||
|         "Failed to get track info: Invalid status code: {}", |         "https://api.spotify.com/v1/tracks/{}", | ||||||
|         response.status() |         track.to_base62()? | ||||||
|       ) |       )) | ||||||
|       .into(), |       .bearer_auth(&token) | ||||||
|     ); |       .send() | ||||||
|  |       .await?; | ||||||
|  | 
 | ||||||
|  |     if response.status().as_u16() >= 500 && retries > 0 { | ||||||
|  |       retries -= 1; | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if response.status() != 200 { | ||||||
|  |       return Err( | ||||||
|  |         format!( | ||||||
|  |           "Failed to get track info: Invalid status code: {}", | ||||||
|  |           response.status() | ||||||
|  |         ) | ||||||
|  |         .into(), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return Ok(response.json().await?); | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   Ok(response.json().await?) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub async fn get_episode_info( | pub async fn get_episode_info( | ||||||
| @ -112,24 +140,33 @@ pub async fn get_episode_info( | |||||||
|   let token = token.into(); |   let token = token.into(); | ||||||
|   let client = reqwest::Client::new(); |   let client = reqwest::Client::new(); | ||||||
| 
 | 
 | ||||||
|   let response = client |   let mut retries = 3; | ||||||
|     .get(format!( |  | ||||||
|       "https://api.spotify.com/v1/episodes/{}", |  | ||||||
|       episode.to_base62()? |  | ||||||
|     )) |  | ||||||
|     .bearer_auth(token) |  | ||||||
|     .send() |  | ||||||
|     .await?; |  | ||||||
| 
 | 
 | ||||||
|   if response.status() != 200 { |   loop { | ||||||
|     return Err( |     let response = client | ||||||
|       format!( |       .get(format!( | ||||||
|         "Failed to get episode info: Invalid status code: {}", |         "https://api.spotify.com/v1/episodes/{}", | ||||||
|         response.status() |         episode.to_base62()? | ||||||
|       ) |       )) | ||||||
|       .into(), |       .bearer_auth(&token) | ||||||
|     ); |       .send() | ||||||
|  |       .await?; | ||||||
|  | 
 | ||||||
|  |     if response.status().as_u16() >= 500 && retries > 0 { | ||||||
|  |       retries -= 1; | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if response.status() != 200 { | ||||||
|  |       return Err( | ||||||
|  |         format!( | ||||||
|  |           "Failed to get episode info: Invalid status code: {}", | ||||||
|  |           response.status() | ||||||
|  |         ) | ||||||
|  |         .into(), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return Ok(response.json().await?); | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   Ok(response.json().await?) |  | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DaXcess
						DaXcess