fix(catalog): Hyundai/Kia/Nissan schema images (image-board ticket URL)
These three brands rendered parts with NO schema illustration (and no hotspots):
schema_pics=0 vs 56/29/19 parts on dev. Root cause: extractPsaImageTicketUrl only
read imageViewerParamsUrl from the id="jsinitparams" data-params attribute (where
Ford/PSA/Opel/Volvo put it). Hyundai/Kia/Nissan's jsinitparams carries only a
localization dict ({"commonTxt":{"ok":"TAMAM"}}); their imageViewerParamsUrl lives
in a separate <script> JSON blob → extractor returned null → image pipeline skipped.
Fix: when the jsinitparams-attribute parse yields no URL, fall back to a whole-HTML
scan for "imageViewerParamsUrl":"…" (decoding &). Brand-agnostic and additive;
Ford/PSA/Opel/Volvo unchanged (attribute path still wins). Proven live via the de
client: Hyundai 600x820/3 hotspots, Kia 600x820/11, Nissan 1024x560/13 — all download OK.
Also: drop a pre-existing noParameterAssign in fetchP4Page (derive `account` const
from `accountParam` instead of reassigning the param). +3 extractor unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,3 +102,34 @@ describe("PL24FordLegacyService.parseFordGroupsFromHtml — nav-crumb junk filte
|
||||
expect(names).not.toContain("Geri");
|
||||
});
|
||||
});
|
||||
|
||||
describe("PL24FordLegacyService.extractPsaImageTicketUrl — image-board ticket URL", () => {
|
||||
const ex = svc as unknown as { extractPsaImageTicketUrl(html: string): string | null };
|
||||
|
||||
it("reads imageViewerParamsUrl from the id=jsinitparams attribute (Ford/PSA/Opel/Volvo)", () => {
|
||||
const html =
|
||||
'<div id="jsinitparams" data-params="{"imageViewerParamsUrl":"/ford/fordp_parts/json-image-ticket.action?x=1&y=2"}"></div>';
|
||||
expect(ex.extractPsaImageTicketUrl(html)).toBe(
|
||||
"/ford/fordp_parts/json-image-ticket.action?x=1&y=2",
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to the <script> JSON blob when jsinitparams only has localization (Hyundai/Kia/Nissan)", () => {
|
||||
// Real shape: jsinitparams carries commonTxt only; imageViewerParamsUrl lives in
|
||||
// a separate script blob with raw quotes and &-escaped ampersands.
|
||||
const html =
|
||||
'<div id="jsinitparams" data-params="{"commonTxt":{"ok":"TAMAM"}}"></div>' +
|
||||
'<script>var p = {"qty":"Miktar","imageViewerParamsUrl":"/hyundai-kia-automotive-group/hyundai_parts/json-image-ticket.action?catalog=HMT1B0PA00\\u0026illustration=1\\u0026signature=abc"};</script>';
|
||||
expect(ex.extractPsaImageTicketUrl(html)).toBe(
|
||||
"/hyundai-kia-automotive-group/hyundai_parts/json-image-ticket.action?catalog=HMT1B0PA00&illustration=1&signature=abc",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns null when no image ticket url is present", () => {
|
||||
expect(
|
||||
ex.extractPsaImageTicketUrl(
|
||||
'<div id="jsinitparams" data-params="{"commonTxt":{}}"></div>',
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2833,23 +2833,38 @@ export class PL24FordLegacyService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the json-image-ticket.action URL from image-board.action HTML jsinitparams.
|
||||
* Extract the json-image-ticket.action URL from image-board.action HTML.
|
||||
*
|
||||
* PSA / Ford / Opel / Volvo embed it in the `id="jsinitparams"` data-params
|
||||
* attribute (HTML-encoded JSON). Hyundai / Kia / Nissan instead carry only a
|
||||
* localization dict in jsinitparams and put `imageViewerParamsUrl` in a separate
|
||||
* <script> JSON blob (raw quotes, &-escaped &) — so when the attribute parse
|
||||
* yields nothing we scan the whole document as a brand-agnostic fallback.
|
||||
* Without this fallback those three brands silently render parts with no schema
|
||||
* image (and no hotspots).
|
||||
*/
|
||||
private extractPsaImageTicketUrl(html: string): string | null {
|
||||
const m = html.match(/id="jsinitparams"[^>]+data-params="([^"]+)"/);
|
||||
if (!m) return null;
|
||||
try {
|
||||
const dataParams = JSON.parse(m[1].replace(/"/g, '"').replace(/&/g, "&"));
|
||||
// PSA: top-level imageViewerParamsUrl
|
||||
// Ford/Hyundai/Opel/Volvo: nested under jsIlluData[0].imageViewerParamsUrl
|
||||
return (
|
||||
(dataParams.imageViewerParamsUrl as string) ||
|
||||
(dataParams.jsIlluData?.[0]?.imageViewerParamsUrl as string) ||
|
||||
null
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
if (m) {
|
||||
try {
|
||||
const dataParams = JSON.parse(m[1].replace(/"/g, '"').replace(/&/g, "&"));
|
||||
const url =
|
||||
(dataParams.imageViewerParamsUrl as string) ||
|
||||
(dataParams.jsIlluData?.[0]?.imageViewerParamsUrl as string);
|
||||
if (url) return url;
|
||||
} catch {
|
||||
// fall through to the script-blob scan below
|
||||
}
|
||||
}
|
||||
// Hyundai/Kia/Nissan: imageViewerParamsUrl lives in a <script> JSON blob.
|
||||
const blob = html.match(/"imageViewerParamsUrl"\s*:\s*"([^"]+)"/);
|
||||
if (blob) {
|
||||
return blob[1]
|
||||
.replace(/\\u0026/g, "&")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/&/g, "&");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3284,7 +3299,7 @@ export class PL24FordLegacyService {
|
||||
url: string,
|
||||
serviceName: string,
|
||||
isFullUrl = false,
|
||||
account: "tr" | "de" = "tr",
|
||||
accountParam: "tr" | "de" = "tr",
|
||||
retried = false,
|
||||
): Promise<string | null> {
|
||||
// Hyundai/Kia/Nissan parts are licensed ONLY on the de account — the tr
|
||||
@@ -3292,8 +3307,9 @@ export class PL24FordLegacyService {
|
||||
// fetch for these brands to de (which carries the Korean/Japanese license),
|
||||
// and ensure the de service token is authorized. Verified on dev: de yields
|
||||
// 266 Hyundai / 169 Kia models, non-demo, with real drill content.
|
||||
if (LEGACY_DE_SERVICES.has(serviceName)) {
|
||||
account = "de";
|
||||
const isDeOnly = LEGACY_DE_SERVICES.has(serviceName);
|
||||
const account = isDeOnly ? "de" : accountParam;
|
||||
if (isDeOnly) {
|
||||
await this.authService.authorizeServiceForAccount(serviceName, "de");
|
||||
}
|
||||
// Some catalogs (Volvo vin-group.action) store hrefs relative to the catalog
|
||||
|
||||
Reference in New Issue
Block a user