Rework BlueMap claiming and expand ranks guilds chat and warps

This commit is contained in:
scoped committed 2026-09-30 18:09:18 -04:00
1 parent 434ed5338a
commit 6a2944a88e
68 files changed
+1453 -2500

No files matched your search

+84 -30
View File
@@ -150,6 +150,16 @@ pub fn save_instance_options(state: State<'_, AppState>, instance_id: String) ->
Ok(options)
}
#[tauri::command]
pub fn set_instance_fov(state: State<'_, AppState>, instance_id: String, fov: f64) -> Res<()> {
if !(-1.0..=1.0).contains(&fov) || !fov.is_finite() {
return Err("FOV must be between 30 and 110 degrees".into());
}
state.settings.write().unwrap().instance_game_options.entry(instance_id).or_default()
.insert("fov".into(), format!("{fov:.3}"));
state.save_settings().map_err(aerr)
}
// ---- accounts ----
#[tauri::command]
@@ -630,7 +640,7 @@ pub async fn get_public_servers(state: State<'_, AppState>) -> Res<serde_json::V
.as_array()
.into_iter()
.flatten()
.filter_map(|s| Some((s["id"].as_i64()?, s["map_url"].as_str()?.to_owned())))
.filter_map(|s| Some((s["id"].as_i64()?, s["map_address"].as_str()?.to_owned())))
.collect();
*state.map_urls.lock().unwrap() = urls;
Ok(data)
@@ -643,15 +653,14 @@ pub struct MapResource {
etag: Option<String>,
}
/// Native transport for Vantage assets. Browser WebViews cannot fetch a
/// cross-origin Vantage live server, so the launcher uses its HTTP client.
/// Only paths under a map URL supplied by the panel can be fetched.
/// Native transport for BlueMap settings and 2D tiles. Only paths under the
/// BlueMap address supplied by the panel can be fetched.
#[tauri::command]
pub async fn fetch_vantage_resource(state: State<'_, AppState>, server_id: i64, url: String, etag: Option<String>) -> Res<MapResource> {
pub async fn fetch_bluemap_resource(state: State<'_, AppState>, server_id: i64, url: String, etag: Option<String>) -> Res<MapResource> {
let configured = state.map_urls.lock().unwrap().get(&server_id).cloned().ok_or("map not configured for this server")?;
let base = reqwest::Url::parse(&configured).map_err(err)?;
let base = reqwest::Url::parse(&format!("{}/", configured.trim_end_matches('/'))).map_err(err)?;
let target = reqwest::Url::parse(&url).map_err(err)?;
let root = base.join(".").map_err(err)?;
let root = base.clone();
if !matches!(target.scheme(), "http" | "https")
|| target.origin() != base.origin()
|| !target.path().starts_with(root.path())
@@ -659,7 +668,7 @@ pub async fn fetch_vantage_resource(state: State<'_, AppState>, server_id: i64,
|| target.password().is_some()
|| target.fragment().is_some()
{
return Err("map asset is outside the configured Vantage world".into());
return Err("map asset is outside the configured BlueMap site".into());
}
fetch_map_asset(target, etag, None).await
}
@@ -676,26 +685,6 @@ pub async fn get_livemap_info(state: State<'_, AppState>, server_id: i64) -> Res
resp.json().await.map_err(err)
}
/// Native transport for the panel-hosted live map. The session token is
/// attached here, so it never reaches page scripts, and only URLs under
/// this panel's `/api/livemap/<server>/` can be fetched.
#[tauri::command]
pub async fn fetch_livemap_resource(state: State<'_, AppState>, server_id: i64, url: String, etag: Option<String>) -> Res<MapResource> {
let panel = state.panel_url().ok_or("no panel configured")?;
let token = accounts::panel_token(&state).ok_or("sign in to view the live map")?;
let base = reqwest::Url::parse(&format!("{}/api/livemap/{server_id}/", panel.trim_end_matches('/'))).map_err(err)?;
let target = reqwest::Url::parse(&url).map_err(err)?;
if target.origin() != base.origin()
|| !target.path().starts_with(base.path())
|| target.username() != ""
|| target.password().is_some()
|| target.fragment().is_some()
{
return Err("map asset is outside this server's live map".into());
}
fetch_map_asset(target, etag, Some(token)).await
}
async fn fetch_map_asset(target: reqwest::Url, etag: Option<String>, bearer: Option<String>) -> Res<MapResource> {
use base64::Engine;
if target.as_str().len() > 2048 {
@@ -723,11 +712,11 @@ async fn fetch_map_asset(target: reqwest::Url, etag: Option<String>, bearer: Opt
return Ok(MapResource { status, data: None, etag });
}
if response.content_length().is_some_and(|n| n > 64 * 1024 * 1024) {
return Err("Vantage asset exceeds 64 MiB".into());
return Err("BlueMap asset exceeds 64 MiB".into());
}
let bytes = response.bytes().await.map_err(err)?;
if bytes.len() > 64 * 1024 * 1024 {
return Err("Vantage asset exceeds 64 MiB".into());
return Err("BlueMap asset exceeds 64 MiB".into());
}
Ok(MapResource { status, data: Some(base64::engine::general_purpose::STANDARD.encode(bytes)), etag })
}
@@ -807,6 +796,71 @@ pub async fn get_my_guild(state: State<'_, AppState>, instance_id: Option<String
resp.json().await.map_err(err)
}
async fn guild_response_error(resp: reqwest::Response, fallback: &str) -> String {
let body: serde_json::Value = resp.json().await.unwrap_or_default();
body["error"].as_str().unwrap_or(fallback).to_string()
}
#[tauri::command]
pub async fn request_guild_join(state: State<'_, AppState>, guild_id: String, message: String) -> Res<()> {
let req = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/requests")).await?;
let resp = req.json(&serde_json::json!({"message": message})).send().await.map_err(err)?;
if !resp.status().is_success() { return Err(guild_response_error(resp, "could not request to join guild").await); }
Ok(())
}
#[tauri::command]
pub async fn get_guild_join_requests(state: State<'_, AppState>, guild_id: String) -> Res<serde_json::Value> {
let resp = account_api(&state, reqwest::Method::GET, &format!("/guilds/{guild_id}/requests")).await?.send().await.map_err(err)?;
if !resp.status().is_success() { return Err(guild_response_error(resp, "could not load guild requests").await); }
resp.json().await.map_err(err)
}
#[tauri::command]
pub async fn respond_guild_join_request(state: State<'_, AppState>, guild_id: String, uuid: String, accept: bool) -> Res<()> {
let req = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/requests/{uuid}/respond")).await?;
let resp = req.json(&serde_json::json!({"accept": accept})).send().await.map_err(err)?;
if !resp.status().is_success() { return Err(guild_response_error(resp, "could not review guild request").await); }
Ok(())
}
#[tauri::command]
pub async fn get_guild_roles(state: State<'_, AppState>, guild_id: String) -> Res<serde_json::Value> {
let resp = account_api(&state, reqwest::Method::GET, &format!("/guilds/{guild_id}/roles")).await?.send().await.map_err(err)?;
if !resp.status().is_success() { return Err(guild_response_error(resp, "could not load guild roles").await); }
resp.json().await.map_err(err)
}
#[tauri::command]
pub async fn create_guild_role(state: State<'_, AppState>, guild_id: String, role: serde_json::Value) -> Res<()> {
let resp = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/roles")).await?.json(&role).send().await.map_err(err)?;
if !resp.status().is_success() { return Err(guild_response_error(resp, "could not create guild role").await); }
Ok(())
}
#[tauri::command]
pub async fn assign_guild_role(state: State<'_, AppState>, guild_id: String, uuid: String, role: String) -> Res<()> {
let resp = account_api(&state, reqwest::Method::PUT, &format!("/guilds/{guild_id}/members/{uuid}/role")).await?.json(&serde_json::json!({"role":role})).send().await.map_err(err)?;
if !resp.status().is_success() { return Err(guild_response_error(resp, "could not assign guild role").await); }
Ok(())
}
#[tauri::command]
pub async fn delete_guild_role(state: State<'_, AppState>, guild_id: String, role_id: i64) -> Res<()> {
let resp = account_api(&state, reqwest::Method::DELETE, &format!("/guilds/{guild_id}/roles/{role_id}")).await?.send().await.map_err(err)?;
if !resp.status().is_success() { return Err(guild_response_error(resp, "could not delete guild role").await); }
Ok(())
}
#[tauri::command]
pub async fn update_guild(state: State<'_, AppState>, guild_id: String, description: String, motd: String, icon_url: String, banner_url: String) -> Res<()> {
let resp = account_api(&state, reqwest::Method::PUT, &format!("/guilds/{guild_id}")).await?
.json(&serde_json::json!({"description":description,"motd":motd,"icon_url":icon_url,"banner_url":banner_url}))
.send().await.map_err(err)?;
if !resp.status().is_success() { return Err(guild_response_error(resp, "could not update guild").await); }
Ok(())
}
#[tauri::command]
pub async fn create_guild(
state: State<'_, AppState>,