332 lines
11 KiB
Rust
332 lines
11 KiB
Rust
/*
|
|
*
|
|
* 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<std::alloc::System> = Cap::new(std::alloc::System, usize::MAX);
|
|
|
|
#[derive(sqlx::FromRow, Clone, Debug)]
|
|
pub struct User {
|
|
_id: String,
|
|
balance: i32,
|
|
items: sqlx::types::Json<Vec<String>>,
|
|
extra_permissions: sqlx::types::Json<Vec<String>>,
|
|
rank: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Rank {
|
|
name: String,
|
|
permissions: Vec<String>,
|
|
}
|
|
|
|
pub async fn get_ranks(registry: CommandRegistry) -> HashMap<String, Rank> {
|
|
let mut ranks: HashMap<String, Rank> = HashMap::new();
|
|
let mut command_permissions: Vec<String> = 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<String, Rank>, 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<String, Vec<String>>,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone)]
|
|
struct ClientConfig {
|
|
token: String,
|
|
ws: String,
|
|
room: String,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
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<String, Rank> = 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<CommandArc> = 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(())
|
|
}
|