mirror of
https://github.com/KytyPS5/kytyps5.github.io.git
synced 2026-10-09 08:49:37 -07:00
Strip every mention of the external reference project from code comments, scripts and workflows, and rewrite the README as a standalone project doc (functionality, building, derived data, compatibility database, games database, automation, deployment) with no outside references except the KytyPS5 emulator itself.
63 lines
2.3 KiB
JavaScript
63 lines
2.3 KiB
JavaScript
// Title-list transforms: regional-ID stripping, junk filtering, concept
|
|
// deduplication, and refresh-merging that preserves enrichment + overrides.
|
|
|
|
const REGION_RANK = { UP: 0, EP: 1, JP: 2 };
|
|
export function regionRank(region) {
|
|
return REGION_RANK[region] ?? 3;
|
|
}
|
|
|
|
/** Strip the regional "_NN" suffix from a title ID (PPSA01007_00 → PPSA01007). */
|
|
export function stripSuffix(titleId) {
|
|
return titleId.replace(/_\d{2}$/, "");
|
|
}
|
|
|
|
const JUNK_PATTERNS = [/entitlement/i, /\((?:prod|dev|qa|test|internal|beta)\)/i];
|
|
export function isJunk(row) {
|
|
const name = row.name ?? "";
|
|
if (name.trim() === "") return true;
|
|
return JUNK_PATTERNS.some((re) => re.test(name));
|
|
}
|
|
|
|
/** Deduplicate title rows by PS Store concept ID, preferring US/EU/JP regions. */
|
|
export function dedupeByConcept(rows) {
|
|
const byConcept = new Map();
|
|
for (const row of rows) {
|
|
if (isJunk(row)) continue;
|
|
if (!byConcept.has(row.conceptId)) byConcept.set(row.conceptId, []);
|
|
byConcept.get(row.conceptId).push(row);
|
|
}
|
|
const games = [];
|
|
for (const [conceptId, list] of byConcept) {
|
|
list.sort(
|
|
(a, b) => regionRank(a.region) - regionRank(b.region) || a.titleId.localeCompare(b.titleId),
|
|
);
|
|
const primary = list[0];
|
|
games.push({
|
|
conceptId,
|
|
titleId: stripSuffix(primary.titleId),
|
|
allTitleIds: [...new Set(list.map((r) => stripSuffix(r.titleId)))].sort(),
|
|
name: primary.name,
|
|
region: primary.region,
|
|
});
|
|
}
|
|
return games.sort((a, b) => a.titleId.localeCompare(b.titleId));
|
|
}
|
|
|
|
/** Preserve enrichment + manual overrides across database refreshes. */
|
|
export function mergeExisting(fresh, existing) {
|
|
const prev = new Map(existing.map((g) => [g.conceptId, g]));
|
|
return fresh.map((g) => {
|
|
const old = prev.get(g.conceptId);
|
|
if (!old) return g;
|
|
// Manual flags (commentsDisabled, noStore) survive refreshes even for
|
|
// entries that aren't enriched yet — including hand-completed entries.
|
|
const withOverrides =
|
|
old.commentsDisabled === undefined && old.noStore === undefined
|
|
? g
|
|
: { ...g, commentsDisabled: old.commentsDisabled, noStore: old.noStore };
|
|
if (!old.enriched) return withOverrides;
|
|
const { name, cover, publisher, releaseDate, genres, enriched, noStore } = old;
|
|
return { ...withOverrides, name, cover, publisher, releaseDate, genres, enriched, noStore };
|
|
});
|
|
}
|