feat: support avatar pack links pointing at any host, not just GitHub repos
Some checks are pending
CI / test (push) Waiting to run
CI / clippy (push) Waiting to run
CI / bench-check (push) Waiting to run
CI / wasm-check (push) Waiting to run
CI / fmt (push) Waiting to run

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.
This commit is contained in:
KhooLy 2026-07-31 00:43:46 +03:00
parent 897db9aa6b
commit 91bc0f5d57
2 changed files with 67 additions and 3 deletions

View file

@ -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))
}

View file

@ -37,6 +37,12 @@ struct GitHubRepository {
path: Option<String>,
}
pub(crate) fn profile_avatar_pack_manifest_plan_json(request_json: &str) -> Option<String> {
let request = serde_json::from_str::<RepositoryPlanRequest>(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<String> {
@ -195,6 +201,21 @@ pub(crate) fn profile_avatar_pack_json(request_json: &str) -> Option<String> {
.ok()
}
fn direct_manifest_url(input: &str) -> Option<String> {
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<GitHubRepository> {
let input = input.trim().trim_end_matches('/');
let path = input
@ -216,9 +237,6 @@ fn parse_repository_url(input: &str) -> Option<GitHubRepository> {
})
}
/// Extracts the directory a `/blob/<ref>/<path>` or `/tree/<ref>/<path>` 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<String> {
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(