Rework BlueMap claiming and expand ranks guilds chat and warps
This commit is contained in:
1 parent
434ed5338a
commit
6a2944a88e
68 files changed
+1453
-2500
No files matched your search
@@ -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>,
|
||||
|
||||
@@ -129,6 +129,9 @@ pub async fn run(app: AppHandle, instance_id: String, start: bool, deep: bool) -
|
||||
} else {
|
||||
current_options
|
||||
};
|
||||
if let Some(fov) = settings.instance_game_options.get(&instance_id).and_then(|o| o.get("fov")) {
|
||||
saved_options.insert("fov".into(), fov.clone());
|
||||
}
|
||||
// Do not rewrite pack files or options while another copy is using this directory.
|
||||
if !already_running {
|
||||
sync::sync(&state.http, &panel, &game_dir, inst.revision, &manifest.files, spec.concurrency, deep, &report).await?;
|
||||
|
||||
@@ -52,6 +52,7 @@ pub fn run() {
|
||||
commands::set_panel_url,
|
||||
commands::save_settings,
|
||||
commands::save_instance_options,
|
||||
commands::set_instance_fov,
|
||||
commands::login_panel,
|
||||
commands::discord_sign_in_start,
|
||||
commands::discord_sign_in_poll,
|
||||
@@ -96,8 +97,7 @@ pub fn run() {
|
||||
commands::get_player_stats,
|
||||
commands::get_leaderboard,
|
||||
commands::get_public_servers,
|
||||
commands::fetch_vantage_resource,
|
||||
commands::fetch_livemap_resource,
|
||||
commands::fetch_bluemap_resource,
|
||||
commands::get_livemap_info,
|
||||
commands::get_my_level,
|
||||
commands::get_my_quests,
|
||||
@@ -105,6 +105,14 @@ pub fn run() {
|
||||
commands::get_my_achievements,
|
||||
commands::get_guilds,
|
||||
commands::get_my_guild,
|
||||
commands::request_guild_join,
|
||||
commands::get_guild_join_requests,
|
||||
commands::respond_guild_join_request,
|
||||
commands::get_guild_roles,
|
||||
commands::create_guild_role,
|
||||
commands::assign_guild_role,
|
||||
commands::delete_guild_role,
|
||||
commands::update_guild,
|
||||
commands::create_guild,
|
||||
commands::get_guild_claims,
|
||||
commands::claim_guild_chunk,
|
||||
|
||||
@@ -41,7 +41,7 @@ pub struct AppState {
|
||||
pub accounts: RwLock<AccountsFile>,
|
||||
pub secrets: Secrets,
|
||||
pub manifest: RwLock<Option<LauncherManifest>>,
|
||||
/// Vantage URLs received from the panel, keyed by linked game server.
|
||||
/// BlueMap addresses received from the panel, keyed by linked game server.
|
||||
pub map_urls: Mutex<HashMap<i64, String>>,
|
||||
/// Install/launch pipeline in progress (abortable).
|
||||
pub task: Mutex<Option<tokio::task::AbortHandle>>,
|
||||
|
||||
Reference in new issue
Block a user