+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "voxa-server"
|
name = "axiom-server"
|
||||||
version = "0.1.0-beta2"
|
version = "0.1.1-beta1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# voxa-server
|
# axiom-server
|
||||||
|
|
||||||
Self host your voxa community server with customizable plugins
|
Self host your axiom community server with customizable plugins
|
||||||
|
|
||||||
# Protocol
|
# Protocol
|
||||||
|
|
||||||
1. When the client is running in the background or windowed, it's connected to the [Voxa Cloud server](#voxa-cloud)
|
1. When the client is running in the background or windowed, it's connected to the [Axiom Cloud server](#axiom-cloud)
|
||||||
|
|
||||||
## Handshake
|
## Handshake
|
||||||
|
|
||||||
@@ -28,6 +28,6 @@ Server -> Cloud: `POST /message { content: <Message>, author: <User-Id> }`
|
|||||||
|
|
||||||
Cloud -> Client(s): `{ Message: { content: <Message>, author: <User-Id> } }`
|
Cloud -> Client(s): `{ Message: { content: <Message>, author: <User-Id> } }`
|
||||||
|
|
||||||
# Voxa Cloud
|
# Axiom Cloud
|
||||||
|
|
||||||
The voxa cloud server is the main auth and notification handler.
|
The axiom cloud server is the main auth and notification handler.
|
||||||
|
|||||||
+12
@@ -1,5 +1,6 @@
|
|||||||
mod cli;
|
mod cli;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
mod node_requests;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
mod requests;
|
mod requests;
|
||||||
mod server;
|
mod server;
|
||||||
@@ -22,6 +23,17 @@ fn main() -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
vfs::read_config(&root.join("config.json"))?
|
vfs::read_config(&root.join("config.json"))?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Ok(a) = std::env::var("AXIOM_NODE") {
|
||||||
|
if a == "true" {
|
||||||
|
config
|
||||||
|
.build_req(&root, crate::server::Server::call_node_request)
|
||||||
|
.run()?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config.build(&root).run()?;
|
config.build(&root).run()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
use crate::{server::Server, types::message::ServerMessage, utils::client::Client};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
crate::logger!(LOGGER "Chunk Loader");
|
||||||
|
|
||||||
|
pub fn load_chunk(
|
||||||
|
server: &Arc<Server>,
|
||||||
|
client: &Client,
|
||||||
|
channel_id: &str,
|
||||||
|
chunk_id: usize,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let mut chunk = server
|
||||||
|
.db
|
||||||
|
.get_chunk_node(&client.get_uuid()?, channel_id, chunk_id)?;
|
||||||
|
chunk.reverse();
|
||||||
|
client.send(ServerMessage::Chunk(chunk))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
use crate::{requests::indicator::IndicatorContext, server::Server, types, utils::client::Client};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub fn start_typing(server: &Arc<Server>, client: &Client, channel_id: &str) -> crate::Result<()> {
|
||||||
|
let user_id = client.get_uuid()?;
|
||||||
|
|
||||||
|
server.broadcast_to(
|
||||||
|
&[&user_id.clone(), &channel_id.to_string()],
|
||||||
|
types::message::ServerMessage::Indicator(IndicatorContext {
|
||||||
|
indicator: crate::requests::indicator::Indicator::Typing {
|
||||||
|
user_id,
|
||||||
|
channel_id: channel_id.to_string(),
|
||||||
|
},
|
||||||
|
expires: 2, // 2 secs
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
|
||||||
|
use crate::{server::Server, types, utils::client::Client};
|
||||||
|
|
||||||
|
crate::logger!(LOGGER "Message Manager");
|
||||||
|
|
||||||
|
pub fn send(
|
||||||
|
server: &Arc<Server>,
|
||||||
|
client: &Client,
|
||||||
|
channel_id: &str,
|
||||||
|
contents: &str,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
LOGGER.info(format!("SendMessage to {channel_id}: {contents}"));
|
||||||
|
|
||||||
|
if contents.is_empty() {
|
||||||
|
client.send(types::message::ResponseError::InvalidRequest(format!(
|
||||||
|
"Invalid message: empty message"
|
||||||
|
)))?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = server.db.insert_message(
|
||||||
|
&channel_id,
|
||||||
|
&client.get_uuid()?,
|
||||||
|
&contents,
|
||||||
|
chrono::Utc::now().timestamp(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
server.broadcast_to(
|
||||||
|
&[&msg.channel_id, &msg.from],
|
||||||
|
types::message::ServerMessage::MessageCreate(msg.clone()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn edit(
|
||||||
|
server: &Arc<Server>,
|
||||||
|
client: &Client,
|
||||||
|
message_id: i64,
|
||||||
|
new_contents: &str,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
LOGGER.info(format!("EditMessage {message_id}: {new_contents}"));
|
||||||
|
let Some(msg) = server.db.get_message_by_id(message_id)? else {
|
||||||
|
return Err(anyhow!("Message does not exist"));
|
||||||
|
};
|
||||||
|
|
||||||
|
if msg.from != client.get_uuid()? {
|
||||||
|
return Err(anyhow!("You are not the author of this message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
server.db.edit_message(message_id, new_contents)?;
|
||||||
|
|
||||||
|
server.broadcast_to(
|
||||||
|
&[&msg.channel_id, &msg.from],
|
||||||
|
types::message::ServerMessage::MessageCreate(msg.clone()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(server: &Arc<Server>, client: &Client, message_id: i64) -> crate::Result<()> {
|
||||||
|
LOGGER.info(format!("DeleteMessage {message_id}"));
|
||||||
|
let Some(msg) = server.db.get_message_by_id(message_id)? else {
|
||||||
|
return Err(anyhow!("Message does not exist"));
|
||||||
|
};
|
||||||
|
|
||||||
|
if msg.from != client.get_uuid()? {
|
||||||
|
return Err(anyhow!("You are not the author of this message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
server.db.delete_message(message_id)?;
|
||||||
|
|
||||||
|
server.broadcast_to(
|
||||||
|
&[&msg.channel_id, &msg.from],
|
||||||
|
types::message::ServerMessage::MessageDelete { message_id },
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
pub mod chunk;
|
||||||
|
pub mod indicator;
|
||||||
|
pub mod message;
|
||||||
|
pub mod voice;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
server::Server,
|
||||||
|
types::message::{ClientMessage, ServerMessage, WsMessage},
|
||||||
|
utils::client::Client,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub fn call_node_request(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
req: &WsMessage<ClientMessage>,
|
||||||
|
client: &Client,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
match req {
|
||||||
|
WsMessage::Message(req) => match req {
|
||||||
|
ClientMessage::SendMessage {
|
||||||
|
channel_id,
|
||||||
|
contents,
|
||||||
|
} => {
|
||||||
|
message::send(self, client, channel_id, contents)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientMessage::EditMessage {
|
||||||
|
message_id,
|
||||||
|
new_contents,
|
||||||
|
} => message::edit(self, client, *message_id, new_contents)?,
|
||||||
|
|
||||||
|
ClientMessage::DeleteMessage { message_id } => {
|
||||||
|
message::delete(self, client, *message_id)?
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientMessage::LoadChunk {
|
||||||
|
chunk_id,
|
||||||
|
channel_id,
|
||||||
|
} => chunk::load_chunk(self, client, channel_id, *chunk_id)?,
|
||||||
|
|
||||||
|
ClientMessage::Typing { channel_id } => {
|
||||||
|
indicator::start_typing(self, client, channel_id)?
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientMessage::JoinVoice { channel_id } => voice::join(self, client, channel_id)?,
|
||||||
|
ClientMessage::LeaveVoice { channel_id } => voice::leave(self, client, channel_id)?,
|
||||||
|
},
|
||||||
|
|
||||||
|
WsMessage::Binary(data) => {
|
||||||
|
// Self::LOGGER.info(format!("Binary message: {data:?}"));
|
||||||
|
voice::voice(self, client, data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
WsMessage::String(s) => {
|
||||||
|
Self::LOGGER.info(format!("String message: {s}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn broadcast_to(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
targets: &[&String],
|
||||||
|
message: ServerMessage,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
for c in self.clients.lock().unwrap().iter() {
|
||||||
|
if !targets.contains(&&c.get_uuid()?) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = c.clone();
|
||||||
|
let server = self.clone();
|
||||||
|
let message = message.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
server
|
||||||
|
.wrap_err(&c, c.send(&message))
|
||||||
|
.expect("Failed to broadcast");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
use crate::{server::Server, utils::client::Client};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
crate::logger!(LOGGER "Voice chat");
|
||||||
|
|
||||||
|
pub fn join(server: &Arc<Server>, client: &Client, channel_id: &str) -> crate::Result<()> {
|
||||||
|
let user_id = client.get_uuid()?;
|
||||||
|
|
||||||
|
let voice_id = server
|
||||||
|
.voice
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set(user_id.clone(), channel_id.to_string());
|
||||||
|
|
||||||
|
server.broadcast(crate::types::message::ServerMessage::VoiceJoin {
|
||||||
|
user_id,
|
||||||
|
channel_id: channel_id.to_string(),
|
||||||
|
voice_id: voice_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn leave(server: &Arc<Server>, client: &Client, channel_id: &str) -> crate::Result<()> {
|
||||||
|
let user_id = client.get_uuid()?;
|
||||||
|
|
||||||
|
let Some(voice_id) = server.voice.lock().unwrap().remove(channel_id, &user_id) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
server.broadcast(crate::types::message::ServerMessage::VoiceLeave {
|
||||||
|
user_id,
|
||||||
|
channel_id: channel_id.to_string(),
|
||||||
|
voice_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn voice(server: &Arc<Server>, client: &Client, data: &[u8]) -> crate::Result<()> {
|
||||||
|
let v = server.voice.lock().unwrap();
|
||||||
|
let user_id = client.get_uuid()?;
|
||||||
|
let Some((channel_id, voice_id)) = v.find_user(&user_id) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut targets = v.get(&channel_id);
|
||||||
|
|
||||||
|
if let Some(pos) = targets.iter().position(|x| *x == &user_id) {
|
||||||
|
targets.remove(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = voice_id.to_le_bytes();
|
||||||
|
let mut payload = Vec::with_capacity(prefix.len() + data.len());
|
||||||
|
payload.extend_from_slice(&prefix);
|
||||||
|
payload.extend_from_slice(data);
|
||||||
|
|
||||||
|
server.broadcast_bin_to(&targets, payload)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+1
-1
@@ -12,7 +12,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn call_request(
|
pub fn call_server_request(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
req: &WsMessage<ClientMessage>,
|
req: &WsMessage<ClientMessage>,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
|
|||||||
+40
-14
@@ -8,24 +8,22 @@ use std::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const WELCOME: &str = "
|
const WELCOME: &str = "\x1b[38;2;169;86;252m
|
||||||
|
_ _
|
||||||
$$\\ $$\\ $$$$$$\\ $$\\ $$\\ $$$$$$\\
|
/ \\ __ _(_) ___ _ __ ___
|
||||||
$$ | $$ |$$ __$$\\ $$ | $$ |$$ __$$\\
|
/ _ \\ \\ \\/ / |/ _ \\| '_ ` _ \\
|
||||||
$$ | $$ |$$ / $$ |\\$$\\ $$ |$$ / $$ |
|
/ ___ \\ > <| | (_) | | | | | |
|
||||||
\\$$\\ $$ |$$ | $$ | \\$$$$ / $$$$$$$$ |
|
/_/ \\_\\/_/\\_\\_|\\___/|_| |_| |_|
|
||||||
\\$$\\$$ / $$ | $$ | $$ $$< $$ __$$ |
|
\x1b[0m";
|
||||||
\\$$$ / $$ | $$ |$$ /\\$$\\ $$ | $$ |
|
|
||||||
\\$ / $$$$$$ |$$ / $$ |$$ | $$ |
|
|
||||||
\\_/ \\______/ \\__| \\__|\\__| \\__|
|
|
||||||
|
|
||||||
";
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli, logger,
|
cli, logger,
|
||||||
plugin::{Plugin, loader::PluginLoader, types::LoaderMessage},
|
plugin::{Plugin, loader::PluginLoader, types::LoaderMessage},
|
||||||
requests::voice,
|
requests::voice,
|
||||||
types::{self, message::WsMessage},
|
types::{
|
||||||
|
self,
|
||||||
|
message::{ClientMessage, WsMessage},
|
||||||
|
},
|
||||||
utils::{self, auth, client::Client, voice::Voice},
|
utils::{self, auth, client::Client, voice::Voice},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,6 +45,7 @@ pub struct Server {
|
|||||||
pub shutting_down: AtomicBool,
|
pub shutting_down: AtomicBool,
|
||||||
pub indicators: Mutex<Vec<crate::requests::indicator::IndicatorContext>>,
|
pub indicators: Mutex<Vec<crate::requests::indicator::IndicatorContext>>,
|
||||||
pub voice: Mutex<crate::utils::voice::Voice>,
|
pub voice: Mutex<crate::utils::voice::Voice>,
|
||||||
|
pub call_request: fn(&Arc<Self>, &WsMessage<ClientMessage>, &Client) -> crate::Result<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServerConfig {
|
impl Default for ServerConfig {
|
||||||
@@ -66,6 +65,14 @@ impl ServerConfig {
|
|||||||
Server::new_config(root, self)
|
Server::new_config(root, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_req(
|
||||||
|
self,
|
||||||
|
root: &Path,
|
||||||
|
req: fn(&Arc<Server>, &WsMessage<ClientMessage>, &Client) -> crate::Result<()>,
|
||||||
|
) -> Arc<Server> {
|
||||||
|
Server::new_req_config(root, req, self)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_str(s: &str) -> std::result::Result<Self, serde_json::Error> {
|
pub fn from_str(s: &str) -> std::result::Result<Self, serde_json::Error> {
|
||||||
serde_json::from_str(s)
|
serde_json::from_str(s)
|
||||||
}
|
}
|
||||||
@@ -74,6 +81,24 @@ impl ServerConfig {
|
|||||||
impl Server {
|
impl Server {
|
||||||
logger!(LOGGER "Server");
|
logger!(LOGGER "Server");
|
||||||
|
|
||||||
|
pub fn new_req_config(
|
||||||
|
root: &Path,
|
||||||
|
call_request: fn(&Arc<Self>, &WsMessage<ClientMessage>, &Client) -> crate::Result<()>,
|
||||||
|
config: ServerConfig,
|
||||||
|
) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
db: utils::database::Database::new(&config).unwrap(),
|
||||||
|
root: root.to_path_buf(),
|
||||||
|
config,
|
||||||
|
clients: Mutex::new(HashSet::new()),
|
||||||
|
plugins: Mutex::new(Vec::new()),
|
||||||
|
shutting_down: AtomicBool::new(false),
|
||||||
|
indicators: Mutex::new(Vec::new()),
|
||||||
|
voice: Mutex::new(Voice::new()),
|
||||||
|
call_request: call_request,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_config(root: &Path, config: ServerConfig) -> Arc<Self> {
|
pub fn new_config(root: &Path, config: ServerConfig) -> Arc<Self> {
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
db: utils::database::Database::new(&config).unwrap(),
|
db: utils::database::Database::new(&config).unwrap(),
|
||||||
@@ -84,6 +109,7 @@ impl Server {
|
|||||||
shutting_down: AtomicBool::new(false),
|
shutting_down: AtomicBool::new(false),
|
||||||
indicators: Mutex::new(Vec::new()),
|
indicators: Mutex::new(Vec::new()),
|
||||||
voice: Mutex::new(Voice::new()),
|
voice: Mutex::new(Voice::new()),
|
||||||
|
call_request: Self::call_server_request,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +240,7 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.wrap_err(&client, self.call_request(r, &client))?;
|
self.wrap_err(&client, (self.call_request)(self, r, &client))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -129,6 +129,41 @@ impl Database {
|
|||||||
|
|
||||||
Ok(messages)
|
Ok(messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_chunk_node(
|
||||||
|
&self,
|
||||||
|
author: &str,
|
||||||
|
channel_id: &str,
|
||||||
|
chunk_id: usize,
|
||||||
|
) -> Result<Vec<Message>> {
|
||||||
|
let mut stmt = self.0.prepare(
|
||||||
|
"SELECT id, channel_id, user_id, contents, timestamp
|
||||||
|
FROM chat
|
||||||
|
WHERE (
|
||||||
|
(channel_id = ?1 AND user_id = ?3)
|
||||||
|
OR (channel_id = ?3 AND user_id = ?1)
|
||||||
|
)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 16 OFFSET (?2 * 16)",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let rows = stmt.query_map(params![channel_id, chunk_id, author], |row| {
|
||||||
|
Ok(Message {
|
||||||
|
id: row.get::<_, i64>(0)?,
|
||||||
|
channel_id: row.get::<_, String>(1)?,
|
||||||
|
from: row.get::<_, String>(2)?,
|
||||||
|
contents: row.get::<_, String>(3)?,
|
||||||
|
timestamp: row.get::<_, i64>(4)?,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut messages = Vec::new();
|
||||||
|
for row in rows {
|
||||||
|
messages.push(row?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(messages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for Database {}
|
unsafe impl Send for Database {}
|
||||||
|
|||||||
Reference in New Issue
Block a user