Performance upgrades, fixes for 2 issues

main
DaXcess 2022-11-05 15:39:19 +01:00
parent dccf8c2057
commit f3dff49c06
10 changed files with 177 additions and 87 deletions

View File

@ -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"

View File

@ -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,

View File

@ -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

View File

@ -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");
} }
} }

View File

@ -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";

View File

@ -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()
}
} }

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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?)
} }