Rewriting
This commit is contained in:
+5
-1
@@ -16,4 +16,8 @@ target/
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
token.key
|
||||
token.key
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
||||
Generated
+1
-2241
File diff suppressed because it is too large
Load Diff
+2
-7
@@ -1,11 +1,6 @@
|
||||
[package]
|
||||
name = "auraclient-api"
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
minreq = { version = "2.13.2", features = ["https", "urlencoding", "json-using-serde"] }
|
||||
mongodb = { version = "3.2.1", features = ["sync"] }
|
||||
openssl = "0.10.72"
|
||||
serde = { version ="1.0.218", features = ["derive"] }
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
pub const CLOAKS: &[&str] = &["mercedes_flow", "glitch", "crimson_mark", "bmw", "amg", "amg_petronas", "ferrari", "redbull", "black_hole_amethyst", "black_hole_flame", "black_hole_white"];
|
||||
|
||||
pub const HATS: &[&str] = &["horns_black", "horns_white", "halo", "halo_black"];
|
||||
@@ -1,25 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Player {
|
||||
pub uuid: String,
|
||||
pub cloaks: Vec<String>,
|
||||
pub cloak: String,
|
||||
pub hats: Vec<String>,
|
||||
pub hat: String,
|
||||
pub coins: u32,
|
||||
pub online: bool,
|
||||
}
|
||||
|
||||
pub struct Database {
|
||||
pub players: mongodb::sync::Collection<Player>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn new(client: &mongodb::sync::Client) -> Self {
|
||||
let db = client.database("saturnclient");
|
||||
Self {
|
||||
players: db.collection("players"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io::{BufRead, BufReader, Write},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
use openssl::{
|
||||
pkey::Private,
|
||||
rsa::Rsa,
|
||||
symm::{decrypt, encrypt, Cipher},
|
||||
};
|
||||
|
||||
use crate::response::Error;
|
||||
|
||||
fn aes_encrypt(key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let cipher = Cipher::aes_256_ecb();
|
||||
encrypt(cipher, key, None, plaintext)
|
||||
.map_err(|e| Error::EncryptionError(format!("Failed to encrypt: {}", e)))
|
||||
}
|
||||
|
||||
fn aes_decrypt(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let cipher = Cipher::aes_256_ecb();
|
||||
decrypt(cipher, key, None, ciphertext)
|
||||
.map_err(|e| Error::EncryptionError(format!("Failed to decrypt: {}", e)))
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub fn handshake(mut stream: TcpStream, rsa: Rsa<Private>) -> Result<ETcp, Error> {
|
||||
let stream_clone = stream
|
||||
.try_clone()
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to clone stream: {}", e)))?;
|
||||
let mut reader = BufReader::new(stream_clone);
|
||||
|
||||
let public_key = rsa
|
||||
.public_key_to_pem()
|
||||
.map_err(|e| Error::EncryptionError(format!("Failed to get public key: {}", e)))?;
|
||||
stream
|
||||
.write_all(&public_key)
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to write public key: {}", e)))?;
|
||||
|
||||
stream
|
||||
.flush()
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to flush stream: {}", e)))?;
|
||||
|
||||
let mut aes_encoded = String::new();
|
||||
reader
|
||||
.read_line(&mut aes_encoded)
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to read AES key: {}", e)))?;
|
||||
|
||||
let encrypted_data = base64::decode(aes_encoded.trim())
|
||||
.map_err(|e| Error::EncryptionError(format!("Failed to decode base64: {}", e)))?;
|
||||
|
||||
let mut aes_decrypted = vec![0; 256];
|
||||
let aes_len = rsa
|
||||
.private_decrypt(
|
||||
&encrypted_data,
|
||||
&mut aes_decrypted,
|
||||
openssl::rsa::Padding::PKCS1,
|
||||
)
|
||||
.map_err(|e| Error::EncryptionError(format!("Failed to decrypt AES key: {}", e)))?;
|
||||
|
||||
println!("len: {}", aes_len);
|
||||
aes_decrypted.truncate(aes_len);
|
||||
|
||||
println!(
|
||||
"Decrypted AES key ({} bytes): {:?}",
|
||||
aes_decrypted.len(),
|
||||
aes_decrypted
|
||||
);
|
||||
|
||||
Ok(ETcp {
|
||||
stream,
|
||||
aes: aes_decrypted,
|
||||
reader,
|
||||
})
|
||||
}
|
||||
|
||||
pub struct ETcp {
|
||||
stream: TcpStream,
|
||||
reader: BufReader<TcpStream>,
|
||||
aes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ETcp {
|
||||
#[allow(deprecated)]
|
||||
pub fn send<T: Display>(&mut self, d: T) -> Result<(), Error> {
|
||||
let encrypted = aes_encrypt(&self.aes, d.to_string().as_bytes())?;
|
||||
let encoded = base64::encode(encrypted) + "\n";
|
||||
|
||||
self.stream
|
||||
.write_all(encoded.as_bytes())
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to write to stream: {}", e)))?;
|
||||
|
||||
self.stream
|
||||
.flush()
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to flush stream: {}", e)))
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub fn read(&mut self) -> Result<Option<String>, Error> {
|
||||
let mut v_encoded = String::new();
|
||||
|
||||
if self
|
||||
.reader
|
||||
.read_line(&mut v_encoded)
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to read line: {}", e)))?
|
||||
== 0
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let encrypted_data = base64::decode(v_encoded.trim())
|
||||
.map_err(|e| Error::EncryptionError(format!("Failed to decode base64: {}", e)))?;
|
||||
|
||||
let decrypted = aes_decrypt(&self.aes, &encrypted_data)?;
|
||||
let text = String::from_utf8(decrypted)
|
||||
.map_err(|e| Error::EncryptionError(format!("Failed to decode UTF-8: {}", e)))?;
|
||||
|
||||
Ok(Some(text))
|
||||
}
|
||||
|
||||
pub fn close(&self) -> Result<(), Error> {
|
||||
self.stream
|
||||
.shutdown(std::net::Shutdown::Both)
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to shutdown stream: {}", e)))
|
||||
}
|
||||
|
||||
pub fn try_clone(&mut self) -> Result<Self, Error> {
|
||||
let stream = self
|
||||
.stream
|
||||
.try_clone()
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to clone stream: {}", e)))?;
|
||||
let stream_clone = stream
|
||||
.try_clone()
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to clone stream: {}", e)))?;
|
||||
|
||||
Ok(ETcp {
|
||||
reader: BufReader::new(stream_clone),
|
||||
stream,
|
||||
aes: self.aes.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ETcp {
|
||||
fn clone(&self) -> Self {
|
||||
let stream = self
|
||||
.stream
|
||||
.try_clone()
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to clone stream: {}", e)))
|
||||
.unwrap();
|
||||
let stream_clone = stream
|
||||
.try_clone()
|
||||
.map_err(|e| Error::NetworkError(format!("Failed to clone stream: {}", e)))
|
||||
.unwrap();
|
||||
|
||||
ETcp {
|
||||
reader: BufReader::new(stream_clone),
|
||||
stream,
|
||||
aes: self.aes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-113
@@ -1,114 +1,3 @@
|
||||
use std::{
|
||||
net::{TcpListener, TcpStream},
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use openssl::{pkey::Private, rsa::Rsa};
|
||||
|
||||
use crate::methods::SocketMap;
|
||||
|
||||
pub mod cosmetics;
|
||||
pub mod database;
|
||||
pub mod encryption;
|
||||
pub mod methods;
|
||||
pub mod parser;
|
||||
pub mod response;
|
||||
|
||||
fn main() -> Result<(), response::Error> {
|
||||
let rsa = Rsa::generate(2048).map_err(|e| {
|
||||
response::Error::EncryptionError(format!("Failed to generate RSA keys: {}", e))
|
||||
})?;
|
||||
|
||||
let client =
|
||||
mongodb::sync::Client::with_uri_str("mongodb://admin:[email protected]/").map_err(|e| {
|
||||
response::Error::DatabaseError(format!("Failed to connect to MongoDB: {}", e))
|
||||
})?;
|
||||
let database = Arc::new(database::Database::new(&client));
|
||||
let sockets = SocketMap::default();
|
||||
|
||||
let listener = TcpListener::bind("0.0.0.0:8080").map_err(|e| {
|
||||
response::Error::NetworkError(format!("Failed to bind to port 8080: {}", e))
|
||||
})?;
|
||||
|
||||
println!("Server listening on port 8080");
|
||||
|
||||
for stream_result in listener.incoming() {
|
||||
let stream = match stream_result {
|
||||
Ok(stream) => stream,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to accept connection: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
println!("New connection");
|
||||
let database = Arc::clone(&database);
|
||||
let sockets = Arc::clone(&sockets);
|
||||
let rsa = rsa.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Err(e) = handle_client(stream, rsa, database, sockets) {
|
||||
eprintln!("Client error: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_client(
|
||||
stream: TcpStream,
|
||||
rsa: Rsa<Private>,
|
||||
database: Arc<database::Database>,
|
||||
sockets: SocketMap,
|
||||
) -> Result<(), response::Error> {
|
||||
let mut stream = encryption::handshake(stream, rsa)?;
|
||||
let mut last_activity = Instant::now();
|
||||
|
||||
match methods::Session::new(stream.try_clone()?, database, sockets) {
|
||||
Ok((session, res)) => {
|
||||
stream.send(res)?;
|
||||
|
||||
loop {
|
||||
// Check for inactivity
|
||||
if last_activity.elapsed() > Duration::from_secs(60) {
|
||||
println!(
|
||||
"[MOJANG] {} inactive for too long",
|
||||
session.local_player.name
|
||||
);
|
||||
methods::player::logout(&session)?;
|
||||
println!("[MOJANG] {} went offline", session.local_player.name);
|
||||
break;
|
||||
}
|
||||
|
||||
match stream.read()? {
|
||||
None => {
|
||||
// Client disconnected
|
||||
println!("[MOJANG] {} disconnected", session.local_player.name);
|
||||
methods::player::logout(&session)?;
|
||||
println!("[MOJANG] {} went offline", session.local_player.name);
|
||||
break;
|
||||
}
|
||||
Some(request_string) => {
|
||||
last_activity = Instant::now();
|
||||
let (method, params) = parser::parse(&request_string)?;
|
||||
let response = session.handle_request(&method, ¶ms);
|
||||
|
||||
stream.send(match response {
|
||||
Ok(response) => response.to_string(),
|
||||
Err(e) => format!("!{e}"),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
stream.send(format!("!{e}"))?;
|
||||
println!("Disconnected");
|
||||
stream.close()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
use mongodb::bson::doc;
|
||||
|
||||
use crate::{methods::Session, response::Result};
|
||||
|
||||
pub fn buy_cloak(session: &Session, cloak: String) -> Result {
|
||||
let filter = doc! {
|
||||
"uuid": session.local_player.id.clone()
|
||||
};
|
||||
|
||||
if let Some(doc) = session.database.players.find_one(filter.clone()).run()? {
|
||||
if doc.coins >= 100 && !doc.cloaks.contains(&cloak) {
|
||||
session
|
||||
.database
|
||||
.players
|
||||
.update_one(
|
||||
filter,
|
||||
doc! { "$inc": { "coins": -100 }, "$push": { "cloaks": cloak.clone() } },
|
||||
)
|
||||
.run()?;
|
||||
Ok(crate::response::Response::SuccessfulTransaction(format!(
|
||||
"cloak={cloak}"
|
||||
)))
|
||||
} else {
|
||||
Err(crate::response::Error::TransactionError(
|
||||
"Not enough coins".to_string(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(crate::response::Error::DatabaseError(
|
||||
"Player not found".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buy_hat(session: &Session, hat: String) -> Result {
|
||||
let filter = doc! {
|
||||
"uuid": session.local_player.id.clone()
|
||||
};
|
||||
|
||||
if let Some(doc) = session.database.players.find_one(filter.clone()).run()? {
|
||||
if doc.coins >= 50 && !doc.hats.contains(&hat) {
|
||||
session
|
||||
.database
|
||||
.players
|
||||
.update_one(
|
||||
filter,
|
||||
doc! { "$inc": { "coins": -50 }, "$push": { "hats": hat.clone() } },
|
||||
)
|
||||
.run()?;
|
||||
Ok(crate::response::Response::SuccessfulTransaction(format!(
|
||||
"hat={hat}"
|
||||
)))
|
||||
} else {
|
||||
Err(crate::response::Error::TransactionError(
|
||||
"Not enough coins".to_string(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(crate::response::Error::DatabaseError(
|
||||
"Player not found".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
use crate::response::{Response, Result};
|
||||
|
||||
use super::Session;
|
||||
|
||||
pub fn emote(session: &Session, emote: String, notify: Vec<&str>) -> Result {
|
||||
if notify.len() > 0 {
|
||||
session.notify(
|
||||
¬ify,
|
||||
&format!("emote@uuid={}@name={emote}", session.local_player.id),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(Response::Success)
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
mod buy;
|
||||
mod emote;
|
||||
pub mod player;
|
||||
mod set_cloak;
|
||||
mod set_hat;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
encryption::ETcp,
|
||||
parser::ParamMap,
|
||||
response::{PlayerResponse, Response, Result},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct LocalPlayer {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub type SocketMap = Arc<Mutex<HashMap<String, ETcp>>>;
|
||||
|
||||
pub struct Session {
|
||||
pub session_token: String,
|
||||
pub database: Arc<crate::database::Database>,
|
||||
pub local_player: LocalPlayer,
|
||||
sockets: SocketMap,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new(
|
||||
mut stream: ETcp,
|
||||
database: Arc<crate::database::Database>,
|
||||
sockets: SocketMap,
|
||||
) -> Result<(Self, Response)> {
|
||||
let (token_send, token_recv) =
|
||||
mpsc::channel::<std::result::Result<String, crate::response::Error>>();
|
||||
|
||||
let stream_2 = stream.try_clone()?;
|
||||
|
||||
std::thread::spawn(move || match stream.read() {
|
||||
Ok(Some(session_token)) => token_send.send(Ok(session_token)),
|
||||
_ => token_send.send(Err(crate::response::Error::InvalidHandshake(
|
||||
"Failed to read session token".to_string(),
|
||||
))),
|
||||
});
|
||||
|
||||
match token_recv.recv_timeout(std::time::Duration::from_secs(20)) {
|
||||
Ok(Ok(session_token)) => {
|
||||
// Validate session id
|
||||
let response = minreq::get("https://api.minecraftservices.com/minecraft/profile")
|
||||
.with_header("Authorization", &format!("Bearer {session_token}"))
|
||||
.send()
|
||||
.map_err(|_| {
|
||||
crate::response::Error::InvalidSession(
|
||||
"Failed to validate session".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// If the session is invalid, return an error
|
||||
if response.status_code != 200 {
|
||||
return Err(crate::response::Error::InvalidSession(format!(
|
||||
"Invalid session status code: {}",
|
||||
response.status_code
|
||||
)));
|
||||
}
|
||||
|
||||
// Parse the player data
|
||||
let local_player: LocalPlayer = response.json().map_err(|_| {
|
||||
crate::response::Error::InvalidSession(
|
||||
"Failed to parse player data".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
println!("[MOJANG] {} successfully logged on", &local_player.name);
|
||||
|
||||
let session = Self {
|
||||
session_token,
|
||||
database,
|
||||
local_player,
|
||||
sockets,
|
||||
};
|
||||
|
||||
// Capture the player
|
||||
match player::login(&session) {
|
||||
Ok(player) => {
|
||||
session.add_socket(stream_2);
|
||||
Ok((session, player))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => Err(crate::response::Error::Timeout(
|
||||
"Session handshake timed out".to_string(),
|
||||
)),
|
||||
_ => Err(crate::response::Error::InvalidHandshake(
|
||||
"Failed to receive handshake response".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_request(&self, method: &str, params: &HashMap<String, String>) -> Result {
|
||||
match method {
|
||||
"ping" => Ok(Response::Pong),
|
||||
|
||||
"set_cloak" => set_cloak::set_cloak(
|
||||
self,
|
||||
params.parse_param("cloak")?,
|
||||
params
|
||||
.parse_param::<String>("notify")
|
||||
.unwrap_or_default()
|
||||
.split("$")
|
||||
.collect(),
|
||||
),
|
||||
|
||||
"set_hat" => set_hat::set_hat(
|
||||
self,
|
||||
params.parse_param("hat")?,
|
||||
params
|
||||
.parse_param::<String>("notify")
|
||||
.unwrap_or_default()
|
||||
.split("$")
|
||||
.collect(),
|
||||
),
|
||||
|
||||
"emote" => emote::emote(
|
||||
self,
|
||||
params.parse_param("name")?,
|
||||
params
|
||||
.parse_param::<String>("notify")
|
||||
.unwrap_or_default()
|
||||
.split("$")
|
||||
.collect(),
|
||||
),
|
||||
|
||||
"player" => player::player(self, params.parse_param("uuid")?),
|
||||
|
||||
"players" => {
|
||||
let mut players: Vec<PlayerResponse> = Vec::new();
|
||||
let uuids = params.parse_param::<String>("uuids")?;
|
||||
if uuids.is_empty() {
|
||||
return Err(crate::response::Error::InvalidParameter {
|
||||
param: "uuids".to_string(),
|
||||
reason: "UUIDs list cannot be empty".to_string(),
|
||||
});
|
||||
}
|
||||
for uuid in uuids.split("$") {
|
||||
if uuid.is_empty() {
|
||||
return Err(crate::response::Error::InvalidParameter {
|
||||
param: "uuids".to_string(),
|
||||
reason: "UUID cannot be empty".to_string(),
|
||||
});
|
||||
}
|
||||
match player::player(self, uuid.to_string())? {
|
||||
Response::Player(p) => players.push(p),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(Response::Players(players))
|
||||
}
|
||||
|
||||
"buy_cloak" => buy::buy_cloak(self, params.parse_param("cloak")?),
|
||||
"buy_hat" => buy::buy_hat(self, params.parse_param("hat")?),
|
||||
|
||||
_ => Err(crate::response::Error::InvalidMethod(method.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn add_socket(&self, sock: ETcp) {
|
||||
self.sockets
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.local_player.id.clone(), sock);
|
||||
}
|
||||
|
||||
pub fn remove_from_sockets(&self) {
|
||||
self.sockets.lock().unwrap().remove(&self.local_player.id);
|
||||
}
|
||||
|
||||
pub fn notify(&self, players: &[&str], message: &str) -> Result<()> {
|
||||
let mut sockets = self.sockets.lock().unwrap();
|
||||
for player in players {
|
||||
if let Some(i) = sockets.get_mut(&player.to_string()) {
|
||||
i.send(message)
|
||||
.map_err(|e| crate::response::Error::DatabaseError(format!("{e}")))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
use mongodb::bson::doc;
|
||||
|
||||
use crate::{
|
||||
database::Player,
|
||||
response::{Response, Result},
|
||||
};
|
||||
|
||||
use super::Session;
|
||||
|
||||
pub fn player(session: &Session, uuid: String) -> Result {
|
||||
// Try to find the player with the given UUID
|
||||
let mut cursor = session
|
||||
.database
|
||||
.players
|
||||
.find(doc! { "uuid": uuid.clone() })
|
||||
.run()?;
|
||||
|
||||
// Check if there's a document in the cursor
|
||||
Ok(match cursor.next() {
|
||||
Some(Ok(player)) => {
|
||||
if player.online {
|
||||
Response::Player(crate::response::PlayerResponse::Player {
|
||||
cloak: player.cloak,
|
||||
uuid,
|
||||
cloaks: player.cloaks,
|
||||
hats: player.hats,
|
||||
hat: player.hat,
|
||||
})
|
||||
} else {
|
||||
crate::response::Response::Player(crate::response::PlayerResponse::NonSaturnPlayer(
|
||||
uuid,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
_ => crate::response::Response::Player(crate::response::PlayerResponse::NonSaturnPlayer(
|
||||
uuid,
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create(session: &Session) -> Result {
|
||||
let uuid = session.local_player.id.clone();
|
||||
|
||||
let player = Player {
|
||||
uuid: uuid.clone(),
|
||||
cloaks: Vec::new(),
|
||||
cloak: "".to_string(),
|
||||
hats: Vec::new(),
|
||||
hat: String::new(),
|
||||
coins: 0,
|
||||
online: true,
|
||||
};
|
||||
|
||||
session.database.players.insert_one(player.clone()).run()?;
|
||||
|
||||
Ok(Response::Player(crate::response::PlayerResponse::Player {
|
||||
cloak: player.cloak,
|
||||
uuid,
|
||||
cloaks: player.cloaks,
|
||||
hats: player.hats,
|
||||
hat: player.hat,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn login(session: &Session) -> Result {
|
||||
let uuid = session.local_player.id.clone();
|
||||
|
||||
// Try to find the player with the given UUID
|
||||
let mut cursor = session
|
||||
.database
|
||||
.players
|
||||
.find(doc! { "uuid": uuid.clone() })
|
||||
.run()?;
|
||||
|
||||
// Check if there's a document in the cursor
|
||||
match cursor.next() {
|
||||
Some(Ok(mut player)) => {
|
||||
// Update player's online status
|
||||
player.online = true;
|
||||
session
|
||||
.database
|
||||
.players
|
||||
.update_one(
|
||||
doc! { "uuid": uuid.clone() },
|
||||
doc! { "$set": { "online": true } },
|
||||
)
|
||||
.run()?;
|
||||
|
||||
// Player exists, deserialize and return their data
|
||||
Ok(Response::Player(crate::response::PlayerResponse::Player {
|
||||
cloak: player.cloak,
|
||||
uuid,
|
||||
cloaks: player.cloaks,
|
||||
hats: player.hats,
|
||||
hat: player.hat,
|
||||
}))
|
||||
}
|
||||
|
||||
// No document found or deserialization error
|
||||
_ => create(session),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn logout(session: &Session) -> Result {
|
||||
session.remove_from_sockets();
|
||||
let uuid = session.local_player.id.clone();
|
||||
|
||||
// Update player's online status to false
|
||||
session
|
||||
.database
|
||||
.players
|
||||
.update_one(
|
||||
doc! { "uuid": uuid.clone() },
|
||||
doc! { "$set": { "online": false } },
|
||||
)
|
||||
.run()?;
|
||||
|
||||
Ok(Response::Player(
|
||||
crate::response::PlayerResponse::NonSaturnPlayer(uuid),
|
||||
))
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
use mongodb::bson::doc;
|
||||
|
||||
use crate::response::{Error, Response, Result};
|
||||
|
||||
use super::{player, Session};
|
||||
|
||||
pub fn set_cloak(session: &Session, cloak: String, notify: Vec<&str>) -> Result {
|
||||
match player::player(session, session.local_player.id.clone())? {
|
||||
Response::Player(crate::response::PlayerResponse::Player { cloaks, .. }) => {
|
||||
if !cloaks.contains(&cloak) && !cloak.is_empty() {
|
||||
return Err(Error::ValidationError(format!(
|
||||
"Player does not own cloak: {}",
|
||||
cloak
|
||||
)));
|
||||
}
|
||||
|
||||
session
|
||||
.database
|
||||
.players
|
||||
.update_one(
|
||||
doc! {
|
||||
"uuid": session.local_player.id.clone(),
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"cloak": cloak.clone(),
|
||||
},
|
||||
},
|
||||
)
|
||||
.run()
|
||||
.map_err(|e| Error::DatabaseError(format!("Failed to update cloak: {}", e)))?;
|
||||
|
||||
if notify.len() > 0 {
|
||||
session.notify(
|
||||
¬ify,
|
||||
&format!(
|
||||
"update_cloak@uuid={}@cloak={cloak}",
|
||||
session.local_player.id
|
||||
),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(Response::Success)
|
||||
}
|
||||
_ => Err(Error::ValidationError(
|
||||
"Invalid player data received".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use mongodb::bson::doc;
|
||||
|
||||
use crate::response::{Error, Response, Result};
|
||||
|
||||
use super::{player, Session};
|
||||
|
||||
pub fn set_hat(session: &Session, hat: String, notify: Vec<&str>) -> Result {
|
||||
match player::player(session, session.local_player.id.clone())? {
|
||||
Response::Player(crate::response::PlayerResponse::Player { hats, .. }) => {
|
||||
if !hats.contains(&hat) && !hat.is_empty() {
|
||||
return Err(Error::ValidationError(format!(
|
||||
"Player does not own hat: {}",
|
||||
hat
|
||||
)));
|
||||
}
|
||||
|
||||
session
|
||||
.database
|
||||
.players
|
||||
.update_one(
|
||||
doc! {
|
||||
"uuid": session.local_player.id.clone(),
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"hat": hat.clone(),
|
||||
},
|
||||
},
|
||||
)
|
||||
.run()
|
||||
.map_err(|e| Error::DatabaseError(format!("Failed to update hat: {}", e)))?;
|
||||
|
||||
if notify.len() > 0 {
|
||||
session.notify(
|
||||
¬ify,
|
||||
&format!("update_hat@uuid={}@hat={hat}", session.local_player.id),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(Response::Success)
|
||||
}
|
||||
_ => Err(Error::ValidationError(
|
||||
"Invalid player data received".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
pub trait ParamMap {
|
||||
fn parse_param<T: FromStr>(&self, param: &str) -> Result<T, crate::response::Error>;
|
||||
}
|
||||
|
||||
impl ParamMap for HashMap<String, String> {
|
||||
fn parse_param<T: FromStr>(&self, param: &str) -> Result<T, crate::response::Error> {
|
||||
match self.get(param) {
|
||||
Some(value) => T::from_str(value).map_err(|_| crate::response::Error::InvalidParameter {
|
||||
param: param.to_string(),
|
||||
reason: "Failed to parse parameter value".to_string(),
|
||||
}),
|
||||
None => Err(crate::response::Error::ParameterNotFound(param.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Result<(String, HashMap<String, String>), crate::response::Error> {
|
||||
let mut parts = s.split("@");
|
||||
|
||||
let method = parts.next()
|
||||
.ok_or_else(|| crate::response::Error::InvalidRequest("Empty request".to_string()))?
|
||||
.trim()
|
||||
.to_lowercase();
|
||||
|
||||
if method.is_empty() {
|
||||
return Err(crate::response::Error::InvalidRequest("Method name cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
let mut params = HashMap::new();
|
||||
for part in parts {
|
||||
let key_value: Vec<&str> = part.split("=").collect();
|
||||
match key_value.as_slice() {
|
||||
[key, value] => {
|
||||
let key = key.trim();
|
||||
if key.is_empty() {
|
||||
return Err(crate::response::Error::InvalidRequest("Parameter key cannot be empty".to_string()));
|
||||
}
|
||||
params.insert(key.to_string(), value.trim().to_string());
|
||||
},
|
||||
_ => return Err(crate::response::Error::InvalidRequest(
|
||||
format!("Invalid parameter format in part: {}", part)
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((method, params))
|
||||
}
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
pub type Result<T = Response> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PlayerResponse {
|
||||
Player {
|
||||
uuid: String,
|
||||
cloaks: Vec<String>,
|
||||
cloak: String,
|
||||
hats: Vec<String>,
|
||||
hat: String,
|
||||
},
|
||||
NonSaturnPlayer(String),
|
||||
}
|
||||
impl std::fmt::Display for PlayerResponse {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PlayerResponse::Player {
|
||||
uuid,
|
||||
cloaks,
|
||||
cloak,
|
||||
hats,
|
||||
hat,
|
||||
} => write!(
|
||||
f,
|
||||
"player@cloak={cloak}@uuid={uuid}@cloaks={}@hats={}@hat={hat}@saturn=true",
|
||||
cloaks.join("$"),
|
||||
hats.join("$"),
|
||||
),
|
||||
PlayerResponse::NonSaturnPlayer(uuid) => write!(f, "@saturn=false@uuid={uuid}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Response {
|
||||
Pong,
|
||||
Success,
|
||||
SuccessfulTransaction(String),
|
||||
Player(PlayerResponse),
|
||||
Players(Vec<PlayerResponse>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Response {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Response::Pong => write!(f, "Pong"),
|
||||
Response::Success => write!(f, "Success"),
|
||||
Response::Player(player) => write!(f, "{player}"),
|
||||
Response::Players(players) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
players
|
||||
.iter()
|
||||
.map(|p| format!("{p}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
Response::SuccessfulTransaction(s) => write!(f, "successful_transaction@{s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Error {
|
||||
InvalidRequest(String),
|
||||
InvalidMethod(String),
|
||||
InvalidParameter { param: String, reason: String },
|
||||
ParameterNotFound(String),
|
||||
InvalidSession(String),
|
||||
InvalidHandshake(String),
|
||||
DatabaseError(String),
|
||||
NetworkError(String),
|
||||
Timeout(String),
|
||||
AuthenticationError(String),
|
||||
ValidationError(String),
|
||||
EncryptionError(String),
|
||||
TransactionError(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::InvalidRequest(msg) => write!(f, "Invalid request: {}", msg),
|
||||
Error::InvalidMethod(method) => write!(f, "Invalid method: {}", method),
|
||||
Error::InvalidParameter { param, reason } => {
|
||||
write!(f, "Invalid parameter '{}': {}", param, reason)
|
||||
}
|
||||
Error::ParameterNotFound(param) => write!(f, "Required parameter not found: {}", param),
|
||||
Error::InvalidSession(details) => write!(f, "Invalid session: {}", details),
|
||||
Error::InvalidHandshake(details) => write!(f, "Handshake failed: {}", details),
|
||||
Error::DatabaseError(details) => write!(f, "Database error: {}", details),
|
||||
Error::NetworkError(details) => write!(f, "Network error: {}", details),
|
||||
Error::Timeout(operation) => write!(f, "Operation timed out: {}", operation),
|
||||
Error::AuthenticationError(details) => write!(f, "Authentication failed: {}", details),
|
||||
Error::ValidationError(details) => write!(f, "Validation failed: {}", details),
|
||||
Error::EncryptionError(details) => write!(f, "Encryption error: {}", details),
|
||||
Error::TransactionError(details) => write!(f, "Transaction error: {}", details),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mongodb::error::Error> for Error {
|
||||
fn from(e: mongodb::error::Error) -> Self {
|
||||
println!("{e}");
|
||||
Error::DatabaseError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
Reference in New Issue
Block a user