From 648d57dae5531a0b05eb524e774e85eaae15b461 Mon Sep 17 00:00:00 2001 From: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> Date: Sun, 17 May 2026 17:10:16 +0100 Subject: [PATCH 1/7] Add modrinth://launch deep link to start a profile Support external profile launching via modrinth://launch/{profile_path} for integrations such as Stream Deck. Co-authored-by: Cursor --- apps/app-frontend/src/App.vue | 4 +++- packages/app-lib/src/api/handler.rs | 4 ++++ packages/app-lib/src/event/mod.rs | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 1939fa8787..8a65395b05 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -98,7 +98,7 @@ import { get_user, get_version } from '@/helpers/cache.js' import { command_listener, warning_listener } from '@/helpers/events.js' import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.ts' import { create_profile_and_install_from_file } from '@/helpers/pack' -import { list } from '@/helpers/profile.js' +import { list, run } from '@/helpers/profile.js' import { mergeUrlQuery, parseModrinthLink } from '@/helpers/project-links.ts' import { get as getSettings, set as setSettings } from '@/helpers/settings.ts' import { get_opening_command, initialize_state } from '@/helpers/state' @@ -796,6 +796,8 @@ async function handleCommand(e) { source: 'CreationModalFileDrop', }) } + } else if (e.event === 'LaunchProfile') { + await run(e.path).catch(handleError) } else if (e.event === 'InstallServer') { await router.push(`/project/${e.id}`) await playServerProject(e.id).catch(handleError) diff --git a/packages/app-lib/src/api/handler.rs b/packages/app-lib/src/api/handler.rs index 500aaeb385..16a9a9fd03 100644 --- a/packages/app-lib/src/api/handler.rs +++ b/packages/app-lib/src/api/handler.rs @@ -28,6 +28,10 @@ pub async fn handle_url(sublink: &str) -> crate::Result { Some(("server", id)) => { CommandPayload::InstallServer { id: id.to_string() } } + // /launch/{id} - Launches a profile + Some(("launch", id)) => CommandPayload::LaunchProfile { + path: id.to_string(), + }, _ => { emit_warning(&format!( "Invalid command, unrecognized path: {sublink}" diff --git a/packages/app-lib/src/event/mod.rs b/packages/app-lib/src/event/mod.rs index f570d78a9a..1f642466a4 100644 --- a/packages/app-lib/src/event/mod.rs +++ b/packages/app-lib/src/event/mod.rs @@ -212,6 +212,9 @@ pub enum CommandPayload { InstallServer { id: String, }, + LaunchProfile { + path: String, + }, RunMRPack { // run or install .mrpack path: PathBuf, From 325d7ee7992d264ad4130f3fafb0c51e60d104f8 Mon Sep 17 00:00:00 2001 From: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> Date: Sat, 6 Jun 2026 10:57:48 +0100 Subject: [PATCH 2/7] Change route to /launch/profile/{id} for future extensibility --- packages/app-lib/src/api/handler.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/app-lib/src/api/handler.rs b/packages/app-lib/src/api/handler.rs index 16a9a9fd03..5e484753e6 100644 --- a/packages/app-lib/src/api/handler.rs +++ b/packages/app-lib/src/api/handler.rs @@ -28,10 +28,12 @@ pub async fn handle_url(sublink: &str) -> crate::Result { Some(("server", id)) => { CommandPayload::InstallServer { id: id.to_string() } } - // /launch/{id} - Launches a profile - Some(("launch", id)) => CommandPayload::LaunchProfile { - path: id.to_string(), - }, + // /launch/profile/{id} - Launches a profile + Some(("launch", rest)) if rest.starts_with("profile/") => { + CommandPayload::LaunchProfile { + path: rest.trim_start_matches("profile/").to_string(), + } + } _ => { emit_warning(&format!( "Invalid command, unrecognized path: {sublink}" From 7cb0b1bac31fa3f8e27e7dd1bd0755ec44f1c1b3 Mon Sep 17 00:00:00 2001 From: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:09:47 +0100 Subject: [PATCH 3/7] fix(ci): increase Gradle timeout and add cache to fix CI failures - Increase Gradle network timeout from 10s to 120s to prevent download timeouts when building theseus - Cache Gradle wrapper distributions and caches in CI to avoid re-downloading on every run --- .github/workflows/turbo-ci.yml | 8 ++++++++ .../app-lib/java/gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/turbo-ci.yml b/.github/workflows/turbo-ci.yml index e25448c867..4c054230fc 100644 --- a/.github/workflows/turbo-ci.yml +++ b/.github/workflows/turbo-ci.yml @@ -122,6 +122,14 @@ jobs: restore-keys: | pnpm-cache- + - name: Cache Gradle distribution + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ~/.gradle/wrapper/dists + ~/.gradle/caches + key: ${{ runner.os }}-${{ runner.arch }}-gradle-${{ hashFiles('packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties') }} + - name: Setup Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0 with: diff --git a/packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties b/packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties index 2e1113280e..e311d962e4 100644 --- a/packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties +++ b/packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip -networkTimeout=10000 +networkTimeout=120000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From d223705b14e39c1e9db0594b81160fec6ed1e511 Mon Sep 17 00:00:00 2001 From: tdgao Date: Sat, 6 Jun 2026 13:16:19 -0600 Subject: [PATCH 4/7] fix: ensure profile path is url decoded --- apps/app-frontend/src/App.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 8a65395b05..e260213e83 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -797,7 +797,7 @@ async function handleCommand(e) { }) } } else if (e.event === 'LaunchProfile') { - await run(e.path).catch(handleError) + await run(decodeURIComponent(e.path)).catch(handleError) } else if (e.event === 'InstallServer') { await router.push(`/project/${e.id}`) await playServerProject(e.id).catch(handleError) From ce7ca2e0354190ed722eccee20c5e1f21c50a1d7 Mon Sep 17 00:00:00 2001 From: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:21:37 +0100 Subject: [PATCH 5/7] fix: increase Gradle network timeout to prevent download failures --- packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties b/packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties index 2e1113280e..e311d962e4 100644 --- a/packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties +++ b/packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip -networkTimeout=10000 +networkTimeout=120000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From c849abf48354bd796636f6f546f53530b27cb6bb Mon Sep 17 00:00:00 2001 From: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:23:32 +0100 Subject: [PATCH 6/7] fix: URL-decode profile path from deep link --- packages/app-lib/src/api/handler.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/app-lib/src/api/handler.rs b/packages/app-lib/src/api/handler.rs index 5e484753e6..a1eb1e6780 100644 --- a/packages/app-lib/src/api/handler.rs +++ b/packages/app-lib/src/api/handler.rs @@ -7,6 +7,7 @@ use crate::{ }, util::io, }; +use url::percent_encoding::percent_decode_str; /// Handles external functions (such as through URL deep linkage) /// Link is extracted value (link) in somewhat URL format, such as @@ -30,8 +31,21 @@ pub async fn handle_url(sublink: &str) -> crate::Result { } // /launch/profile/{id} - Launches a profile Some(("launch", rest)) if rest.starts_with("profile/") => { - CommandPayload::LaunchProfile { - path: rest.trim_start_matches("profile/").to_string(), + let raw = rest.trim_start_matches("profile/"); + match percent_decode_str(raw).decode_utf8() { + Ok(decoded) => CommandPayload::LaunchProfile { + path: decoded.to_string(), + }, + Err(e) => { + emit_warning(&format!( + "Invalid UTF-8 in profile path: {e}" + )) + .await?; + return Err(crate::ErrorKind::InputError(format!( + "Invalid UTF-8 in profile path: {e}" + )) + .into()); + } } } _ => { From ace09d39b0f53d48654869d6adba41d783d7de69 Mon Sep 17 00:00:00 2001 From: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:35:48 +0100 Subject: [PATCH 7/7] fix: use urlencoding crate for URL decoding --- packages/app-lib/Cargo.toml | 1 + packages/app-lib/src/api/handler.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/app-lib/Cargo.toml b/packages/app-lib/Cargo.toml index 0cd7d5d9f7..2b9f075e78 100644 --- a/packages/app-lib/Cargo.toml +++ b/packages/app-lib/Cargo.toml @@ -104,6 +104,7 @@ tracing = { workspace = true } tracing-error = { workspace = true } tracing-subscriber = { workspace = true, features = ["chrono", "env-filter"] } url = { workspace = true, features = ["serde"] } +urlencoding = { workspace = true } uuid = { workspace = true, features = ["serde", "v4"] } whoami = { workspace = true } zbus = { workspace = true } diff --git a/packages/app-lib/src/api/handler.rs b/packages/app-lib/src/api/handler.rs index a1eb1e6780..ffa0ca413d 100644 --- a/packages/app-lib/src/api/handler.rs +++ b/packages/app-lib/src/api/handler.rs @@ -7,7 +7,7 @@ use crate::{ }, util::io, }; -use url::percent_encoding::percent_decode_str; +use urlencoding::decode; /// Handles external functions (such as through URL deep linkage) /// Link is extracted value (link) in somewhat URL format, such as @@ -32,7 +32,7 @@ pub async fn handle_url(sublink: &str) -> crate::Result { // /launch/profile/{id} - Launches a profile Some(("launch", rest)) if rest.starts_with("profile/") => { let raw = rest.trim_start_matches("profile/"); - match percent_decode_str(raw).decode_utf8() { + match decode(raw) { Ok(decoded) => CommandPayload::LaunchProfile { path: decoded.to_string(), },