feat: add suggestions, other minor bugfixes

This commit is contained in:
Soph :3 2025-09-15 00:34:05 +03:00
parent efd28daf30
commit 6371ab1886
6 changed files with 107 additions and 10 deletions

View file

@ -11,6 +11,8 @@ Clone this repository with `git clone --recursive https://git.sad.ovh/sophie/cop
- 2 billion note per second parser with all modern midi features supported - 2 billion note per second parser with all modern midi features supported
- Full economy system written using `sqlx` and postgres - Full economy system written using `sqlx` and postgres
- Lots of generic MPP bot features such as following, moving between rooms, et cetera - Lots of generic MPP bot features such as following, moving between rooms, et cetera
- Suggestions via NTFY
- Very good argument system
## Todo ## Todo
- No todo :3 - No todo :3

View file

@ -409,7 +409,7 @@ impl Command for FishCommand {
tokio::spawn(async move { tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(timeout_secs as u64)).await; tokio::time::sleep(std::time::Duration::from_secs(timeout_secs as u64)).await;
client_clone client_clone
.dm(format!("🎣 You can fish again now!"), player._id) .dm("🎣 You can fish again now!", player._id.as_str())
.await; .await;
}); });
} }

View file

@ -1,3 +1,3 @@
use crate::submods; use crate::submods;
submods!(follow, help, launch, test, translate, about, rank); submods!(follow, help, launch, test, translate, about, rank, suggest);

View file

@ -0,0 +1,95 @@
use crate::Configuration;
use crate::User;
use crate::client::Client;
use crate::client::ClientEvent;
use crate::client::Player;
use crate::commands::Command;
use crate::commands::argument::{ArgumentSpec, ArgumentType, ParsedArgument, ParsedArguments};
use async_trait::async_trait;
pub struct SuggestCommand {
conf: Configuration,
}
impl SuggestCommand {
pub fn new(conf: Configuration) -> Self {
Self { conf }
}
}
#[async_trait]
impl Command for SuggestCommand {
fn name(&self) -> &'static str {
"suggest"
}
fn aliases(&self) -> &[&'static str] {
&["suggestion", "sg"]
}
fn category(&self) -> &'static str {
"system"
}
fn description(&self) -> &'static str {
"Suggest something to the bot owner."
}
fn argument_spec(&self) -> &'static [ArgumentSpec] {
&[ArgumentSpec {
name: "text",
arg_type: ArgumentType::GreedyString,
required: true,
default: None,
children: &[],
}]
}
async fn constructed(&mut self, _: Client) {}
async fn event(&mut self, _: Client, _: ClientEvent) {}
async fn execute(&mut self, client: Client, player: Player, args: ParsedArguments, _: User) {
let suggestion = match args.get("text") {
Some(ParsedArgument::GreedyString(s)) if !s.is_empty() => s,
_ => {
client.message("Please provide a suggestion.").await;
return;
}
};
let ntfy_url = self.conf.commands.ntfy.clone();
if ntfy_url.is_none() {
client.message("Suggestions are currently disabled.").await;
return;
}
let username = player.name.clone();
let player_id_short = player._id.chars().take(6).collect::<String>();
let message_body = format!("{} ({}): {}", username, player_id_short, suggestion);
let res = reqwest::Client::new()
.post(ntfy_url.unwrap())
.header("Priority", "1")
.header("Title", "New Copper suggestion")
.body(message_body)
.send()
.await;
match res {
Ok(resp) if resp.status().is_success() => {
client
.message("Your suggestion has been sent. Thank you!")
.await;
}
Ok(resp) => {
client
.message(format!("Failed to send suggestion: HTTP {}", resp.status()))
.await;
}
Err(e) => {
client
.message(format!("Failed to send suggestion: {}", e))
.await;
}
}
}
}

View file

@ -114,27 +114,28 @@ macro_rules! register_all {
)+ )+
}; };
} }
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone, Debug)]
pub struct Configuration { pub struct Configuration {
database: DatabaseConfig, database: DatabaseConfig,
commands: CommandsConfig, commands: CommandsConfig,
client: ClientConfig, client: ClientConfig,
} }
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone, Debug)]
struct DatabaseConfig { struct DatabaseConfig {
url: String, url: String,
} }
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone, Debug)]
struct CommandsConfig { struct CommandsConfig {
prefix: String, prefix: String,
name: String, name: String,
ntfy: Option<String>,
copyparty: String, copyparty: String,
playlists: HashMap<String, Vec<String>>, playlists: HashMap<String, Vec<String>>,
} }
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone, Debug)]
struct ClientConfig { struct ClientConfig {
token: String, token: String,
ws: String, ws: String,
@ -171,6 +172,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
[ [
StopCommand::new(midi_state.clone()), StopCommand::new(midi_state.clone()),
PlaylistCommand::new(midi_state.clone(), conf.clone()), PlaylistCommand::new(midi_state.clone(), conf.clone()),
SuggestCommand::new(conf.clone()),
QueueCommand::new(midi_state.clone()), QueueCommand::new(midi_state.clone()),
SkipCommand::new(midi_state.clone()), SkipCommand::new(midi_state.clone()),
LaunchCommand, LaunchCommand,

View file

@ -1,8 +1,6 @@
use flume::Sender; use flume::Sender;
use midiplayer_rs::midi::loader::load_midi_file; use midiplayer_rs::midi::loader::load_midi_file;
use midiplayer_rs::midi::player::ParsedMidi;
use midiplayer_rs::midi::player::parse_midi_events; use midiplayer_rs::midi::player::parse_midi_events;
use midiplayer_rs::midi::player::play_parsed_events;
use midiplayer_rs::midi::utils::get_time_100ns; use midiplayer_rs::midi::utils::get_time_100ns;
use midiplayer_rs::midi::utils::unpack_event; use midiplayer_rs::midi::utils::unpack_event;
use std::time::Duration; use std::time::Duration;
@ -29,7 +27,7 @@ pub enum MidiEvent {
seconds: u128, seconds: u128,
millis: u128, millis: u128,
parse_time: Duration, parse_time: Duration,
entry: QueueEntry, entry: Box<QueueEntry>,
}, },
} }
@ -69,7 +67,7 @@ pub async fn play_midi(
minutes, minutes,
seconds, seconds,
millis, millis,
entry, entry: Box::new(entry),
parse_time: start.elapsed(), parse_time: start.elapsed(),
}) })
.await; .await;