From 91bc0f5d576c643554a0e998cad097e8868d0922 Mon Sep 17 00:00:00 2001 From: KhooLy <73142442+KhooLy@users.noreply.github.com> Date: Fri, 31 Jul 2026 00:43:46 +0300 Subject: [PATCH] feat: support avatar pack links pointing at any host, not just GitHub repos Adds profileAvatarPackManifestPlan: recognizes a URL that already points directly at a pack.json/json.pack file (GitHub blob link or any other HTTPS host) and fetches it as a single pack, instead of requiring a bare GitHub repository URL and walking its whole tree. --- src/ffi/core_addon_store_routes.rs | 3 ++ src/profile_avatar_pack.rs | 67 ++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/ffi/core_addon_store_routes.rs b/src/ffi/core_addon_store_routes.rs index c2a8ad5..bc26539 100644 --- a/src/ffi/core_addon_store_routes.rs +++ b/src/ffi/core_addon_store_routes.rs @@ -80,6 +80,9 @@ pub(super) fn route_profile_avatar_pack(method: &str, args_json: &str) -> Outcom // args_json IS the request object for all of these. The platform owns // the HTTP calls between plans; this crate only validates and maps the // GitHub responses into the stable UI contract. + "profileAvatarPackManifestPlan" => { + opt_json(profile_avatar_pack::profile_avatar_pack_manifest_plan_json(args_json)) + } "profileAvatarPackRepositoryPlan" => { opt_json(profile_avatar_pack::profile_avatar_pack_repository_plan_json(args_json)) } diff --git a/src/profile_avatar_pack.rs b/src/profile_avatar_pack.rs index b40bb0c..c5358c0 100644 --- a/src/profile_avatar_pack.rs +++ b/src/profile_avatar_pack.rs @@ -37,6 +37,12 @@ struct GitHubRepository { path: Option, } +pub(crate) fn profile_avatar_pack_manifest_plan_json(request_json: &str) -> Option { + let request = serde_json::from_str::(request_json).ok()?; + let manifest_url = direct_manifest_url(&request.repository_url)?; + serde_json::to_string(&json!({ "manifestUrl": manifest_url })).ok() +} + /// Normalizes a GitHub repository pasted by a user and returns the first /// platform-owned HTTP request needed to discover its default branch. pub(crate) fn profile_avatar_pack_repository_plan_json(request_json: &str) -> Option { @@ -195,6 +201,21 @@ pub(crate) fn profile_avatar_pack_json(request_json: &str) -> Option { .ok() } +fn direct_manifest_url(input: &str) -> Option { + let trimmed = input.trim(); + if !is_https_url(trimmed) { + return None; + } + let normalized = normalize_avatar_url(trimmed); + is_manifest_filename(&normalized).then_some(normalized) +} + +fn is_manifest_filename(url: &str) -> bool { + let path = url.split('?').next().unwrap_or(url); + let filename = path.rsplit('/').next().unwrap_or(""); + matches!(filename.to_ascii_lowercase().as_str(), "pack.json" | "json.pack") +} + fn parse_repository_url(input: &str) -> Option { let input = input.trim().trim_end_matches('/'); let path = input @@ -216,9 +237,6 @@ fn parse_repository_url(input: &str) -> Option { }) } -/// Extracts the directory a `/blob//` or `/tree//` URL -/// points at, so pasting a link to one specific pack scopes discovery to it -/// instead of importing every pack in the repository. fn extract_target_path(rest: &str) -> Option { let mut segments = rest.splitn(2, '/'); let kind = segments.next()?; @@ -423,6 +441,49 @@ mod tests { assert_eq!(output["repository"], "Fusion-Profile-Avatars"); } + #[test] + fn manifest_plan_rewrites_a_github_blob_url_to_raw() { + let output: Value = serde_json::from_str( + &profile_avatar_pack_manifest_plan_json( + r#"{"repositoryUrl":"https://github.com/eueueue292/Fusion-Profile-Avatars/blob/main/Solo%20Leveling%20S2/pack.json"}"#, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!( + output["manifestUrl"], + "https://raw.githubusercontent.com/eueueue292/Fusion-Profile-Avatars/main/Solo%20Leveling%20S2/pack.json" + ); + } + + #[test] + fn manifest_plan_accepts_any_https_host_serving_a_manifest() { + let output: Value = serde_json::from_str( + &profile_avatar_pack_manifest_plan_json( + r#"{"repositoryUrl":"https://example.com/packs/solo-leveling/pack.json"}"#, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(output["manifestUrl"], "https://example.com/packs/solo-leveling/pack.json"); + } + + #[test] + fn manifest_plan_rejects_urls_not_pointing_at_a_manifest_file() { + assert!( + profile_avatar_pack_manifest_plan_json( + r#"{"repositoryUrl":"https://github.com/eueueue292/Fusion-Profile-Avatars"}"# + ) + .is_none() + ); + assert!( + profile_avatar_pack_manifest_plan_json( + r#"{"repositoryUrl":"https://example.com/packs/solo-leveling/avatar.png"}"# + ) + .is_none() + ); + } + #[test] fn catalog_scopes_to_the_pack_a_blob_url_points_at() { let output: Value = serde_json::from_str(