/* * * See this in 3D, all lights out for me * All lights out for me, lightning strikes the beach * 80 degrees, warm it up for menally free, found the God in me * And I want you to see, I can walk on water * Thousand miles from shore, I can float on the water * Father, hold me close, don't let me drown * I know you won't Sophie, sad.ovh 2025 (C) MIT */ use crate::client::Client; use crate::client::ClientEvent; use crate::commands::Arguments; use crate::commands::CommandRegistry; use std::collections::HashMap; use cap::Cap; use serde::Deserialize; use sqlx::postgres::PgPoolOptions; use tokio::sync::Mutex; pub mod client; pub mod commands; pub mod log; pub mod midi_helper; use crate::commands::*; use std::sync::Arc; #[global_allocator] static ALLOCATOR: Cap = Cap::new(std::alloc::System, usize::MAX); #[derive(sqlx::FromRow, Clone, Debug)] pub struct User { _id: String, balance: i32, items: sqlx::types::Json>, extra_permissions: sqlx::types::Json>, rank: String, } #[derive(Clone, Debug)] pub struct Rank { name: String, permissions: Vec, } pub async fn get_ranks(registry: CommandRegistry) -> HashMap { let mut ranks: HashMap = HashMap::new(); let mut command_permissions: Vec = Vec::new(); for command in registry.values() { let locked_command = command.lock().await; command_permissions.push(format!("commands.{}", locked_command.name())); drop(locked_command); } ranks.insert( "owner".to_string(), Rank { name: "owner".to_string(), permissions: { let mut perms = command_permissions.clone(); perms.push("play.high_note_counts".to_string()); perms }, }, ); ranks.insert( "user".to_string(), Rank { name: "user".to_string(), permissions: { let mut perms = command_permissions.clone(); perms.retain(|p| p != "commands.launch"); perms }, }, ); ranks } pub fn has_permission(user: &User, ranks: HashMap, permission: String) -> bool { let rank = ranks.get(&user.rank); rank.map(|r| r.permissions.contains(&permission)) .unwrap_or(false) || user.extra_permissions.0.contains(&permission) } macro_rules! register_all { ($registry:expr, $client:expr, [ $($cmd:expr),+ $(,)? ]) => { $( $registry.register($cmd, $client.clone()).await; )+ }; } #[derive(Deserialize, Clone)] pub struct Configuration { database: DatabaseConfig, commands: CommandsConfig, client: ClientConfig, } #[derive(Deserialize, Clone)] struct DatabaseConfig { url: String, } #[derive(Deserialize, Clone)] struct CommandsConfig { prefix: String, name: String, copyparty: String, playlists: HashMap>, } #[derive(Deserialize, Clone)] struct ClientConfig { token: String, ws: String, room: String, } #[tokio::main] async fn main() -> Result<(), Box> { rustls::crypto::CryptoProvider::install_default(rustls_rustcrypto::provider()) .expect("install rustcrypto provider"); let s = tokio::fs::read_to_string("config.hocon").await?; let conf: Configuration = hocon::de::from_str(&s)?; let mut mode = "R"; if cfg!(debug_assertions) { mode = "D"; } let (mut client, event_rx) = Client::new(); let pool = PgPoolOptions::new() .max_connections(5) .connect(conf.database.url.as_str()) .await?; let arc_pool = Arc::new(pool); let midi_state = Arc::new(Mutex::new(MidiState::new())); let mut registry = CommandRegistry::new(); let ranks: HashMap = HashMap::new(); register_all!( registry, client, [ StopCommand::new(midi_state.clone()), PlaylistCommand::new(midi_state.clone(), conf.clone()), QueueCommand::new(midi_state.clone()), SkipCommand::new(midi_state.clone()), LaunchCommand, FollowCommand::new(), TestCommand::new(), BalanceCommand::new(arc_pool.clone()), InventoryCommand::new(arc_pool.clone()), FishCommand::new(arc_pool.clone()), FarmCommand::new(arc_pool.clone()), ShopCommand::new(arc_pool.clone()), CoinflipCommand::new(arc_pool.clone()), TranslateCommand, AboutCommand, RankCommand::new(arc_pool.clone(), ranks.clone()), PlayCommand::new(midi_state.clone(), conf.clone(), ranks.clone()), ] ); registry .register(HelpCommand::new(registry.clone()), client.clone()) .await; let ranks = get_ranks(registry.clone()).await; registry .register( PlayCommand::new(midi_state, conf.clone(), ranks.clone()), client.clone(), ) .await; registry .register( RankCommand::new(arc_pool.clone(), ranks.clone()), client.clone(), ) .await; let client_events = client.clone(); let events_pool = arc_pool.clone(); tokio::spawn(async move { while let Ok(event) = event_rx.recv_async().await { match event { ClientEvent::Connected => { log!(CONNECTED); } ClientEvent::Sync { e } => { let t = e - chrono::Utc::now().timestamp_millis(); let username = { let ram = ALLOCATOR.allocated() / 1_000_000; if ram != 0 { format!("{} 📶{} ms 🐏{} mb {}", conf.commands.name, -t, ram, mode) } else { format!("{} 📶{} ms {}", conf.commands.name, -t, mode) } }; let _ = client_events .userset(username.as_str().into(), "#B7410E".into()) .await; } ClientEvent::Message { player, message } => { if let Some(no_prefix) = message.strip_prefix(conf.commands.prefix.as_str()) { let mut parts = no_prefix.split_whitespace(); if let Some(cmd_name) = parts.next() { let args = Arguments::new(parts.map(|s| s.to_string()).collect()); let mut cmd_opt: Option = None; for cmd in registry.values() { let cmd_lock = cmd.lock().await; if cmd_lock.name() == cmd_name || cmd_lock.aliases().contains(&cmd_name) { cmd_opt = Some(cmd.clone()); break; } } if let Some(cmd) = cmd_opt { let mut cmd_lock = cmd.lock().await; let specs = cmd_lock.argument_spec(); let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE _id = $1") .bind(&player._id) .fetch_optional(events_pool.as_ref()) .await .unwrap() .unwrap(); if !has_permission( &user, ranks.clone(), format!("commands.{}", cmd_name), ) { client_events .message(format!("You do not have permission \"commands.{}\" to run this command.", cmd_name)) .await; } else { match crate::commands::argument::parse_arguments( specs, &args.args, ) { Ok(parsed_args_struct) => { cmd_lock .execute( client_events.clone(), player.clone(), parsed_args_struct, user, ) .await; } Err(e) => { client_events .message(format!("Argument error: {}", e)) .await; } } } } } } log!(MSG, player.name, message); } ClientEvent::PlayerJoined(player) => { log!(PLAYER_JOINED, player.name); let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE _id = $1") .bind(&player._id) .fetch_optional(events_pool.as_ref()) .await; let user_exists = user.unwrap().is_some(); if !user_exists { // INSERT with extra_permissions and rank columns (defaults) let _ = sqlx::query( "INSERT INTO users (_id, balance, items, extra_permissions, rank) VALUES ($1, $2, $3, $4, $5)", ) .bind(&player._id) .bind(0_i32) .bind(serde_json::json!([])) .bind(serde_json::json!([])) .bind("user") .execute(events_pool.as_ref()) .await; } } ClientEvent::PlayerLeft(id) => { log!(PLAYER_LEFT, id); } ClientEvent::Mouse { x, y, id } => { let cmd_lock = registry.get("follow").unwrap(); let mut cmd = cmd_lock.lock().await; cmd.event(client_events.clone(), ClientEvent::Mouse { x, y, id }) .await; } ClientEvent::NameChanged { now: _, before: _ } => {} ClientEvent::ChannelInfo(channel) => { log!(CHANNEL_INFO, channel.id); } } } }); client .connect(conf.client.ws, conf.client.token, conf.client.room) .await?; Ok(()) }