use crate::api::packages::data::{ ExternalPackage, ManagedPackage, ManagedPackageBody, ManagedUser, ManagedVersion, PackageAuthor, SearchQuery, SearchResult, }; use crate::api::packages::modpack::data::ModpackArchive; use crate::api::packages::provider::ProviderExt; use crate::api::setting_profiles::get_global_profile; use crate::store::processes::Process; use crate::store::{Settings, State}; use crate::utils::pagination::Paginated; use interpulse::api::minecraft::Version; use onelauncher_entity::cluster_stage::ClusterStage; use onelauncher_entity::icon::Icon; use onelauncher_entity::loader::GameLoader; use onelauncher_entity::package::Provider; use onelauncher_entity::packages; use onelauncher_entity::resolution::Resolution; use sea_orm::ActiveValue::Set; use tauri::{AppHandle, Runtime}; use crate::api::cluster::dao::ClusterId; use crate::api::{self}; use crate::error::LauncherResult; use crate::store::Core; use crate::store::credentials::MinecraftCredentials; use onelauncher_entity::prelude::model::*; #[taurpc::procedures(path = "core")] pub trait TauriLauncherApi { // MARK: API: clusters // region: clusters #[taurpc(alias = "getClusters")] async fn get_clusters() -> LauncherResult>; #[taurpc(alias = "getClusterById")] async fn get_cluster_by_id(id: ClusterId) -> LauncherResult>; #[taurpc(alias = "removeCluster")] async fn remove_cluster(id: ClusterId) -> LauncherResult<()>; #[taurpc(alias = "createCluster")] async fn create_cluster(options: CreateCluster) -> LauncherResult; #[taurpc(alias = "launchCluster")] async fn launch_cluster( id: ClusterId, uuid: Option, search_for_java: Option, ) -> LauncherResult<()>; #[taurpc(alias = "updateClusterById")] async fn update_cluster_by_id(id: ClusterId, request: ClusterUpdate) -> LauncherResult<()>; #[taurpc(alias = "setClusterStage")] async fn set_cluster_stage(id: ClusterId, stage: ClusterStage) -> LauncherResult<()>; #[taurpc(alias = "getScreenshots")] async fn get_screenshots(id: ClusterId) -> LauncherResult>; #[taurpc(alias = "getWorlds")] async fn get_worlds(id: ClusterId) -> LauncherResult>; #[taurpc(alias = "getLogs")] async fn get_logs(id: ClusterId) -> LauncherResult>; #[taurpc(alias = "getLogByName")] async fn get_log_by_name(id: ClusterId, name: String) -> LauncherResult>; #[taurpc(alias = "getProcessLogTail")] async fn get_process_log_tail( id: ClusterId, max_lines: usize, ) -> LauncherResult>; // endregion: clusters // MARK: API: processes // region: processes #[taurpc(alias = "getRunningProcesses")] async fn get_running_processes() -> LauncherResult>; #[taurpc(alias = "getRunningProcessesByClusterId")] async fn get_running_processes_by_cluster_id( cluster_id: ClusterId, ) -> LauncherResult>; #[taurpc(alias = "isClusterRunning")] async fn is_cluster_running(cluster_id: ClusterId) -> LauncherResult; #[taurpc(alias = "killProcess")] async fn kill_process(pid: u32) -> LauncherResult<()>; // endregion: processes // MARK: API: profiles // region: profiles #[taurpc(alias = "getProfileOrDefault")] async fn get_profile_or_default(name: Option) -> LauncherResult; #[taurpc(alias = "getGlobalProfile")] async fn get_global_profile() -> SettingsProfile; #[taurpc(alias = "updateClusterProfile")] async fn update_cluster_profile( name: String, profile: ProfileUpdate, ) -> LauncherResult; #[taurpc(alias = "createSettingsProfile")] async fn create_settings_profile(name: String) -> LauncherResult; // endregion: profiles // MARK: API: metadata // region: metadata #[taurpc(alias = "getGameVersions")] async fn get_game_versions() -> LauncherResult>; #[taurpc(alias = "getLoadersForVersion")] async fn get_loaders_for_version(mc_version: String) -> LauncherResult>; // endregion: metadata // MARK: API: users // region: users #[taurpc(alias = "getUsers")] async fn get_users() -> LauncherResult>; #[taurpc(alias = "getUser")] async fn get_user(uuid: uuid::Uuid) -> LauncherResult>; #[taurpc(alias = "removeUser")] async fn remove_user(uuid: uuid::Uuid) -> LauncherResult<()>; #[taurpc(alias = "getDefaultUser")] async fn get_default_user( fallback: Option, ) -> LauncherResult>; #[taurpc(alias = "setDefaultUser")] async fn set_default_user(uuid: Option) -> LauncherResult<()>; #[taurpc(alias = "openMsaLogin")] async fn open_msa_login( app_handle: AppHandle, ) -> LauncherResult>; #[taurpc(alias = "refreshAccounts")] async fn refresh_accounts() -> LauncherResult>; #[taurpc(alias = "refreshAccount")] async fn refresh_account(uuid: uuid::Uuid) -> LauncherResult>; // endregion: users // MARK: API: settings // region: settings #[taurpc(alias = "readSettings")] async fn read_settings() -> LauncherResult; #[taurpc(alias = "writeSettings")] async fn write_settings(setting: Settings) -> LauncherResult<()>; // endregion: settings // MARK: API: package // region: package #[taurpc(alias = "searchPackages")] async fn search_packages( provider: Provider, query: SearchQuery, ) -> LauncherResult>; #[taurpc(alias = "getPackage")] async fn get_package(provider: Provider, slug: String) -> LauncherResult; #[taurpc(alias = "getPackageBody")] async fn get_package_body( provider: Provider, body: ManagedPackageBody, ) -> LauncherResult; #[taurpc(alias = "getMultiplePackages")] async fn get_multiple_packages( provider: Provider, slugs: Vec, ) -> LauncherResult>; #[taurpc(alias = "getPackageVersions")] async fn get_package_versions( provider: Provider, slug: String, mc_version: Option, loader: Option, offset: usize, limit: usize, ) -> LauncherResult>; // #[taurpc(alias = "getPackageUser")] // async fn get_package_user(provider: Provider, slug: String) -> LauncherResult; #[taurpc(alias = "downloadPackage")] async fn download_package( provider: Provider, package_id: String, version_id: String, cluster_id: ClusterId, skip_compatibility: Option, ) -> LauncherResult; #[taurpc(alias = "downloadExternalPackage")] async fn download_external_package( package: ExternalPackage, cluster_id: ClusterId, force: Option, skip_compatibility: Option, ) -> LauncherResult>; #[taurpc(alias = "getUsersFromAuthor")] async fn get_users_from_author( provider: Provider, author: PackageAuthor, ) -> LauncherResult>; #[taurpc(alias = "syncCluster")] async fn sync_cluster(cluster_id: ClusterId) -> LauncherResult<()>; #[taurpc(alias = "getLinkedPackages")] async fn get_linked_packages(cluster_id: ClusterId) -> LauncherResult>; #[taurpc(alias = "removePackage")] async fn remove_package(cluster_id: ClusterId, package_hash: String) -> LauncherResult<()>; #[taurpc(alias = "togglePackage")] async fn toggle_package(cluster_id: ClusterId, package_hash: String) -> LauncherResult; // endregion: package // MARK: API: modpack // region: modpack #[taurpc(alias = "installModpack")] async fn install_modpack(modpack: ModpackArchive, cluster_id: ClusterId) -> LauncherResult<()>; // endregion: modpack // MARK: API: minecraft // region: minecraft #[taurpc(alias = "fetchMinecraftProfile")] async fn fetch_player_profile( uuid: String, ) -> LauncherResult; #[taurpc(alias = "fetchLoggedInProfile")] async fn fetch_logged_in_profile( access_token: String, ) -> LauncherResult; #[taurpc(alias = "uploadSkinBytes")] async fn upload_skin_bytes( access_token: String, skin_data: Vec, image_format: String, skin_variant: crate::utils::minecraft::SkinVariant, ) -> LauncherResult; #[taurpc(alias = "changeSkin")] async fn change_skin( access_token: String, skin_url: String, skin_variant: crate::utils::minecraft::SkinVariant, ) -> LauncherResult; #[taurpc(alias = "changeCape")] async fn change_cape( access_token: String, cape_uuid: String, ) -> LauncherResult; #[taurpc(alias = "removeCape")] async fn remove_cape( access_token: String, ) -> LauncherResult; #[taurpc(alias = "convertUsernameUUID")] async fn convert_username_uuid( username_uuid: String, ) -> LauncherResult; // endregion: minecraft // MARK: API: discord RPC // region: discord RPC #[taurpc(alias = "setDiscordRPCMessage")] async fn set_discord_rpc_message(message: String) -> LauncherResult<()>; // endregion: discord RPC // MARK: API: Other async fn open(input: String) -> LauncherResult<()>; } #[derive(serde::Serialize, serde::Deserialize, specta::Type, Clone)] pub struct CreateCluster { name: String, mc_version: String, mc_loader: GameLoader, mc_loader_version: Option, icon: Option, } #[derive(serde::Serialize, serde::Deserialize, specta::Type, Clone)] pub struct ClusterUpdate { name: Option, icon_url: Option, setting_profile_name: Option, } #[derive(serde::Serialize, serde::Deserialize, specta::Type, Clone)] pub struct ProfileUpdate { pub res: Option, pub force_fullscreen: Option, pub mem_max: Option, pub launch_args: Option, pub launch_env: Option, pub hook_pre: Option, pub hook_wrapper: Option, pub hook_post: Option, } #[taurpc::ipc_type] pub struct TauriLauncherApiImpl; #[allow(clippy::too_many_lines)] #[taurpc::resolvers] impl TauriLauncherApi for TauriLauncherApiImpl { // MARK: Impl: clusters // region: clusters async fn get_clusters(self) -> LauncherResult> { api::cluster::dao::get_all_clusters().await } async fn get_cluster_by_id(self, id: ClusterId) -> LauncherResult> { api::cluster::dao::get_cluster_by_id(id).await } async fn remove_cluster(self, id: ClusterId) -> LauncherResult<()> { api::cluster::dao::delete_cluster_by_id(id).await } async fn create_cluster(self, options: CreateCluster) -> LauncherResult { let cluster = api::cluster::create_cluster( &options.name, &options.mc_version, options.mc_loader, options.mc_loader_version.as_deref(), options.icon, ) .await?; api::setting_profiles::create_profile( &options.name, |mut active_model: SettingsProfilePartial| async move { active_model.force_fullscreen = Set(Some(false)); active_model.mem_max = Set(Some(2048)); Ok(active_model) }, ) .await?; api::cluster::dao::update_cluster_by_id( cluster.id, |mut active_model: ClusterPartial| async move { active_model.setting_profile_name = Set(Some(options.name.clone())); Ok(active_model) }, ) .await?; Ok(cluster) } async fn launch_cluster( self, id: ClusterId, uuid: Option, search_for_java: Option, ) -> LauncherResult<()> { let mut cluster = api::cluster::dao::get_cluster_by_id(id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", id))?; let uuid = match uuid { Some(uuid) => uuid, None => api::credentials::get_default_user() .await? .ok_or_else(|| anyhow::anyhow!("no default user set"))?, }; let user = api::credentials::get_user(uuid) .await? .ok_or_else(|| anyhow::anyhow!("user with uuid {} not found", uuid))?; // let user = api::credentials::get_fake_user(); let _ = api::game::launch::launch_minecraft(&mut cluster, user, None, search_for_java).await?; Ok(()) } async fn update_cluster_by_id( self, id: ClusterId, request: ClusterUpdate, ) -> LauncherResult<()> { api::cluster::dao::update_cluster_by_id( id, |mut active_model: ClusterPartial| async move { if let Some(name) = request.name { active_model.name = Set(name); } // ok i know this is so wrong but im not a rust guy if let Some(icon_url) = request.icon_url { api::cluster::dao::set_icon_by_id(id, &icon_url).await?; } if let Some(setting_profile_name) = request.setting_profile_name { active_model.setting_profile_name = Set(Some(setting_profile_name)); } Ok(active_model) }, ) .await?; Ok(()) } async fn set_cluster_stage(self, id: ClusterId, stage: ClusterStage) -> LauncherResult<()> { api::cluster::dao::update_cluster_by_id(id, |mut model: ClusterPartial| async move { model.stage = Set(stage); Ok(model) }) .await?; Ok(()) } async fn get_screenshots(self, id: ClusterId) -> LauncherResult> { let cluster = api::cluster::dao::get_cluster_by_id(id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", id))?; api::cluster::content::get_screenshots(&cluster).await } async fn get_worlds(self, id: ClusterId) -> LauncherResult> { let cluster = api::cluster::dao::get_cluster_by_id(id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", id))?; api::cluster::content::get_worlds(&cluster).await } async fn get_logs(self, id: ClusterId) -> LauncherResult> { let cluster = api::cluster::dao::get_cluster_by_id(id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", id))?; api::cluster::content::get_logs(&cluster).await } async fn get_log_by_name(self, id: ClusterId, name: String) -> LauncherResult> { let cluster = api::cluster::dao::get_cluster_by_id(id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", id))?; api::cluster::content::get_log_by_name(&cluster, &name).await } async fn get_process_log_tail( self, id: ClusterId, max_lines: usize, ) -> LauncherResult> { let cluster = api::cluster::dao::get_cluster_by_id(id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", id))?; api::cluster::content::get_process_log_tail(&cluster, max_lines).await } // endregion: clusters // MARK: Impl: processes // region: processes async fn get_running_processes(self) -> LauncherResult> { api::processes::get_running_processes().await } async fn get_running_processes_by_cluster_id( self, cluster_id: ClusterId, ) -> LauncherResult> { api::processes::get_running_processes_by_cluster_id(cluster_id).await } async fn is_cluster_running(self, cluster_id: ClusterId) -> LauncherResult { api::processes::is_cluster_running(cluster_id).await } async fn kill_process(self, pid: u32) -> LauncherResult<()> { api::processes::kill_process(pid).await } // endregion: processes // MARK: Impl: profiles // region: profiles async fn get_global_profile(self) -> SettingsProfile { api::setting_profiles::get_global_profile().await } async fn get_profile_or_default(self, name: Option) -> LauncherResult { api::setting_profiles::dao::get_profile_or_default(name.as_ref()).await } async fn create_settings_profile(self, name: String) -> LauncherResult { let mut profile = get_global_profile().await; profile.name = name; api::setting_profiles::dao::insert_profile(profile.into()).await } // please kill this with fire async fn update_cluster_profile( self, name: String, profile: ProfileUpdate, ) -> LauncherResult { let profile = api::setting_profiles::dao::update_profile_by_name( &name, |mut active_model: SettingsProfilePartial| async move { if let Some(res) = profile.res { active_model.res = Set(Some(res)); } if let Some(force_fullscreen) = profile.force_fullscreen { active_model.force_fullscreen = Set(Some(force_fullscreen)); } if let Some(mem_max) = profile.mem_max { active_model.mem_max = Set(Some(mem_max)); } if let Some(launch_args) = profile.launch_args { active_model.launch_args = Set(Some(launch_args)); } if let Some(launch_env) = profile.launch_env { active_model.launch_env = Set(Some(launch_env)); } if let Some(hook_pre) = profile.hook_pre { active_model.hook_pre = Set(Some(hook_pre)); } if let Some(hook_wrapper) = profile.hook_wrapper { active_model.hook_wrapper = Set(Some(hook_wrapper)); } if let Some(hook_post) = profile.hook_post { active_model.hook_post = Set(Some(hook_post)); } Ok(active_model) }, ) .await?; Ok(profile) } // endregion: profiles // MARK: Impl: metadata // region: metadata async fn get_loaders_for_version(self, mc_version: String) -> LauncherResult> { api::game::metadata::get_loaders_for_version(&mc_version).await } async fn get_game_versions(self) -> LauncherResult> { api::game::metadata::get_game_versions().await } // endregion: metadata // MARK: Impl: users // region: users async fn get_users(self) -> LauncherResult> { api::credentials::get_users().await } async fn get_user(self, uuid: uuid::Uuid) -> LauncherResult> { api::credentials::get_user(uuid).await } async fn remove_user(self, uuid: uuid::Uuid) -> LauncherResult<()> { api::credentials::remove_user(uuid).await } async fn get_default_user( self, fallback: Option, ) -> LauncherResult> { let uuid = api::credentials::get_default_user().await?; if fallback.is_some_and(|fallback| fallback) && uuid.is_none() { return Ok(api::credentials::get_users().await?.first().cloned()); } match uuid { Some(uuid) => Ok(api::credentials::get_user(uuid).await?), None => Ok(None), } } async fn set_default_user(self, uuid: Option) -> LauncherResult<()> { api::credentials::set_default_user(uuid).await } async fn open_msa_login( self, app_handle: AppHandle, ) -> LauncherResult> { use tauri::Manager; let flow = api::credentials::begin().await.map_err(|e| { tracing::error!( "[auth] open_msa_login() - credentials::begin() failed: {:?}", e ); e })?; let now = chrono::Utc::now(); if let Some(win) = app_handle.get_webview_window("login") { win.close()?; } let win = tauri::WebviewWindowBuilder::new( &app_handle, "login", tauri::WebviewUrl::External( flow.redirect_uri .parse() .map_err(|_| anyhow::anyhow!("failed to parse auth redirect url"))?, ), ) .title(format!("Login to {}", Core::get().launcher_name)) .inner_size(800.0, 600.0) .center() .focused(true) .build() .map_err(|e| { tracing::error!( "[auth] open_msa_login() - failed to create login window: {:?}", e ); e })?; win.request_user_attention(Some(tauri::UserAttentionType::Critical))?; while (chrono::Utc::now() - now) < chrono::Duration::minutes(10) { if win.title().is_err() { return Ok(None); } let current_url = match win.url() { Ok(u) => u, Err(_) => { tokio::time::sleep(std::time::Duration::from_millis(50)).await; continue; } }; if current_url .as_str() .starts_with("https://login.live.com/oauth20_desktop.srf") { if let Some((_, code)) = win.url()?.query_pairs().find(|x| x.0 == "code") { win.close()?; let value = api::credentials::finish(&code.clone(), flow) .await .map_err(|e| { tracing::error!( "[auth] open_msa_login() - credentials::finish() failed: {:?}", e ); e })?; return Ok(Some(value)); } } tokio::time::sleep(std::time::Duration::from_millis(50)).await; } win.close()?; Ok(None) } async fn refresh_accounts(self) -> LauncherResult> { api::credentials::refresh_accounts().await } async fn refresh_account( self, uuid: uuid::Uuid, ) -> LauncherResult> { api::credentials::refresh_account(uuid).await } async fn get_users_from_author( self, provider: Provider, author: PackageAuthor, ) -> LauncherResult> { provider.get_users_from_author(author).await } // endregion: users // MARK: Impl: settings // region: settings async fn read_settings(self) -> LauncherResult { let state = State::get().await?; let settings = state.settings.read().await; Ok(settings.clone()) } async fn write_settings(self, setting: Settings) -> LauncherResult<()> { let state = State::get().await?; let mut settings = state.settings.write().await; *settings = setting; settings.save().await?; Ok(()) } // endregion: settings // MARK: Impl: package // region: package async fn search_packages( self, provider: Provider, query: SearchQuery, ) -> LauncherResult> { provider.search(&query).await } async fn get_package(self, provider: Provider, slug: String) -> LauncherResult { provider.get(&slug).await } async fn get_package_body( self, provider: Provider, body: ManagedPackageBody, ) -> LauncherResult { provider.get_body(&body).await } async fn get_multiple_packages( self, provider: Provider, slugs: Vec, ) -> LauncherResult> { provider.get_multiple(&slugs).await } async fn get_package_versions( self, provider: Provider, slug: String, mc_version: Option, loader: Option, offset: usize, limit: usize, ) -> LauncherResult> { provider .get_versions_paginated(&slug, mc_version, loader, offset, limit) .await } async fn download_package( self, provider: Provider, package_id: String, version_id: String, cluster_id: ClusterId, skip_compatibility: Option, ) -> LauncherResult { let cluster = api::cluster::dao::get_cluster_by_id(cluster_id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", cluster_id))?; let package = provider.get(&package_id).await?; let versions = provider.get_versions(&[version_id]).await?; let version = versions .into_iter() .next() .ok_or_else(|| anyhow::anyhow!("Version not found"))?; let model = api::packages::download_package(&package, &version, None, None).await?; api::packages::link_package(&model, &cluster, skip_compatibility).await?; Ok(model) } async fn download_external_package( self, package: ExternalPackage, cluster_id: ClusterId, force: Option, skip_compatibility: Option, // default: true ) -> LauncherResult> { let cluster = api::cluster::dao::get_cluster_by_id(cluster_id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", cluster_id))?; let model = api::packages::download_external_package( &package, &cluster, force, skip_compatibility.or(Some(true)), None, ) .await?; if let Some(model) = &model { api::packages::link_package(model, &cluster, skip_compatibility.or(Some(true))).await?; } Ok(model) } async fn get_linked_packages( self, cluster_id: ClusterId, ) -> LauncherResult> { let cluster = api::cluster::dao::get_cluster_by_id(cluster_id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", cluster_id))?; let model = api::packages::dao::get_linked_packages(&cluster).await?; Ok(model) } async fn remove_package( self, cluster_id: ClusterId, package_hash: String, ) -> LauncherResult<()> { // record_override=true: user explicitly removed this package from the cluster api::packages::remove_package(cluster_id, package_hash, true).await } async fn toggle_package( self, cluster_id: ClusterId, package_hash: String, ) -> LauncherResult { api::packages::toggle_package(cluster_id, package_hash).await } async fn sync_cluster(self, cluster_id: ClusterId) -> LauncherResult<()> { let result = crate::api::cluster::sync_cluster_by_id(cluster_id).await; match &result { Ok(action) if !matches!(action, crate::api::cluster::SyncAction::NoAction) => { tracing::info!( "syncCluster complete for cluster_id={}, action={:?}", cluster_id, action ); } Err(e) => tracing::error!("syncCluster failed for cluster_id={}: {}", cluster_id, e), _ => {} } result?; Ok(()) } // endregion: package // MARK: Impl: modpack // region: modpack async fn install_modpack( self, modpack: ModpackArchive, cluster_id: ClusterId, ) -> LauncherResult<()> { let cluster = api::cluster::dao::get_cluster_by_id(cluster_id) .await? .ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", cluster_id))?; modpack .format .install_modpack_archive(&modpack, &cluster, None, None) .await } // endregion: modpack // MARK: Impl: minecraft // region: minecraft async fn fetch_player_profile( self, uuid: String, ) -> LauncherResult { crate::utils::minecraft::fetch_player_profile(&uuid).await } async fn fetch_logged_in_profile( self, access_token: String, ) -> LauncherResult { crate::utils::minecraft::fetch_logged_in_profile(&access_token).await } async fn upload_skin_bytes( self, access_token: String, skin_data: Vec, image_format: String, skin_variant: crate::utils::minecraft::SkinVariant, ) -> LauncherResult { crate::utils::minecraft::upload_skin_bytes( &access_token, skin_data, &image_format, skin_variant, ) .await } async fn change_skin( self, access_token: String, skin_url: String, skin_variant: crate::utils::minecraft::SkinVariant, ) -> LauncherResult { crate::utils::minecraft::change_skin(&access_token, &skin_url, skin_variant).await } async fn change_cape( self, access_token: String, cape_uuid: String, ) -> LauncherResult { crate::utils::minecraft::change_cape(&access_token, &cape_uuid).await } async fn remove_cape( self, access_token: String, ) -> LauncherResult { crate::utils::minecraft::remove_cape(&access_token).await } async fn convert_username_uuid( self, username_uuid: String, ) -> LauncherResult { crate::utils::minecraft::convert_username_uuid(&username_uuid).await } // endregion: minecraft // MARK: Impl: discord RPC // region: discord RPC async fn set_discord_rpc_message(self, message: String) -> LauncherResult<()> { let state = State::get().await?; if state.ensure_rpc().await { let rpc = state.rpc.read().await; if let Some(discord) = rpc.as_ref() { if !state.settings.read().await.discord_enabled { discord.clear_activity().await; return Ok(()); } discord.set_message(&message, None).await; } } Ok(()) } // endregion: discord RPC // MARK: Impl: Other // region: other async fn open(self, input: String) -> LauncherResult<()> { opener::open(input)?; Ok(()) } // endregion: other }