Performance upgrades, fixes for 2 issues
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…
Reference in New Issue