223 lines
5.8 KiB
JavaScript
223 lines
5.8 KiB
JavaScript
/**
|
||
* 从 zhibo 导出的 world-cup-group-stage-matches.json 生成 seed 用裁剪 JSON 与球队映射 TS。
|
||
* 用法: node apps/api/scripts/build-wc2026-seed-json.mjs <源JSON路径>
|
||
*/
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||
const apiRoot = path.resolve(__dirname, '..');
|
||
const seedDataDir = path.join(apiRoot, 'src/infrastructure/database/seed-data');
|
||
const outrightTeamsPath = path.join(apiRoot, 'src/domains/catalog/wc2026-outright-teams.ts');
|
||
|
||
const sourcePath = process.argv[2];
|
||
if (!sourcePath) {
|
||
console.error('用法: node build-wc2026-seed-json.mjs <源JSON路径>');
|
||
process.exit(1);
|
||
}
|
||
|
||
const raw = JSON.parse(fs.readFileSync(path.resolve(sourcePath), 'utf8'));
|
||
const outrightSrc = fs.readFileSync(outrightTeamsPath, 'utf8');
|
||
const outrightTeams = [...outrightSrc.matchAll(/code: '([^']+)', names: \{ 'zh-CN': '([^']+)', 'en-US': '([^']+)'/g)].map((m) => ({
|
||
code: m[1],
|
||
zh: m[2],
|
||
en: m[3],
|
||
}));
|
||
|
||
const CODE_TO_FLAG_ISO = {
|
||
ALG: 'dz',
|
||
ARG: 'ar',
|
||
AUS: 'au',
|
||
AUT: 'at',
|
||
BEL: 'be',
|
||
BIH: 'ba',
|
||
BRA: 'br',
|
||
CAN: 'ca',
|
||
CIV: 'ci',
|
||
COD: 'cd',
|
||
COL: 'co',
|
||
CPV: 'cv',
|
||
CRO: 'hr',
|
||
CUW: 'cw',
|
||
CZE: 'cz',
|
||
ECU: 'ec',
|
||
EGY: 'eg',
|
||
ENG: 'gb-eng',
|
||
ESP: 'es',
|
||
FRA: 'fr',
|
||
GER: 'de',
|
||
GHA: 'gh',
|
||
HAI: 'ht',
|
||
IRN: 'ir',
|
||
IRQ: 'iq',
|
||
JOR: 'jo',
|
||
JPN: 'jp',
|
||
KOR: 'kr',
|
||
KSA: 'sa',
|
||
MAR: 'ma',
|
||
MEX: 'mx',
|
||
NED: 'nl',
|
||
NOR: 'no',
|
||
NZL: 'nz',
|
||
PAN: 'pa',
|
||
PAR: 'py',
|
||
POR: 'pt',
|
||
QAT: 'qa',
|
||
RSA: 'za',
|
||
SCO: 'gb-sct',
|
||
SEN: 'sn',
|
||
SUI: 'ch',
|
||
SWE: 'se',
|
||
TUN: 'tn',
|
||
TUR: 'tr',
|
||
URU: 'uy',
|
||
USA: 'us',
|
||
UZB: 'uz',
|
||
};
|
||
|
||
function flagUrlForCode(code) {
|
||
const iso = CODE_TO_FLAG_ISO[code];
|
||
return iso ? `https://flagcdn.com/${iso}.svg` : '';
|
||
}
|
||
|
||
function pickNames(names) {
|
||
return {
|
||
zh: names?.zh ?? null,
|
||
en: names?.en ?? null,
|
||
zhTw: names?.zhTw ?? null,
|
||
vi: names?.vi ?? null,
|
||
km: names?.km ?? null,
|
||
ms: names?.ms ?? null,
|
||
};
|
||
}
|
||
|
||
function pickTeam(team) {
|
||
const code = resolveCanonicalCode(team);
|
||
return {
|
||
id: team.id,
|
||
name: team.name,
|
||
names: pickNames(team.names),
|
||
image: code ? flagUrlForCode(code) : '',
|
||
};
|
||
}
|
||
|
||
function slimMatch(m) {
|
||
return {
|
||
officialMatchNo: m.officialMatchNo,
|
||
stage: m.stage,
|
||
groupName: m.groupName,
|
||
liveMatchId: m.liveMatchId,
|
||
additionMatchId: m.additionMatchId,
|
||
channelId: m.channelId,
|
||
matchName: m.matchName,
|
||
league: { type: m.league.type, en: m.league.en, zh: m.league.zh },
|
||
kickoff: {
|
||
utcTimeStart: m.kickoff.utcTimeStart,
|
||
utcTimeStop: m.kickoff.utcTimeStop,
|
||
utcIso: m.kickoff.utcIso,
|
||
chinaTime: m.kickoff.chinaTime,
|
||
venueTime: m.kickoff.venueTime,
|
||
venueTimezone: m.kickoff.venueTimezone,
|
||
},
|
||
homeTeam: pickTeam(m.homeTeam),
|
||
awayTeam: pickTeam(m.awayTeam),
|
||
status: { state: m.status.state, isHot: 0 },
|
||
venue: {
|
||
names: pickNames(m.venue?.names),
|
||
city: pickNames(m.venue?.city),
|
||
},
|
||
sortOrder: m.sortOrder,
|
||
isPublished: m.isPublished,
|
||
};
|
||
}
|
||
|
||
function resolveCanonicalCode(team) {
|
||
const en = (team.name || team.names?.en || '').toLowerCase();
|
||
const zh = team.names?.zh || '';
|
||
const hit = outrightTeams.find(
|
||
(o) =>
|
||
o.en.toLowerCase() === en ||
|
||
o.zh === zh ||
|
||
(o.en === 'Turkey' && en.includes('türkiye')) ||
|
||
(o.en === 'Czech' && en === 'czechia') ||
|
||
(o.en === 'Bosnia' && en.includes('bosnia')) ||
|
||
(o.en === 'Ivory Coast' && en.includes('côte')) ||
|
||
(o.en === 'DR Congo' && en.includes('congo')) ||
|
||
(o.en === 'Curacao' && en.includes('cura')),
|
||
);
|
||
return hit?.code ?? null;
|
||
}
|
||
|
||
const matches = (raw.matches || []).map(slimMatch);
|
||
const bundle = { count: matches.length, matches };
|
||
|
||
const teamById = new Map();
|
||
for (const m of raw.matches || []) {
|
||
for (const t of [m.homeTeam, m.awayTeam]) {
|
||
if (t?.id != null) teamById.set(t.id, t);
|
||
}
|
||
}
|
||
|
||
const zhiboToCode = {};
|
||
const unmatched = [];
|
||
const missingFlagCodes = new Set();
|
||
for (const [id, team] of teamById) {
|
||
const code = resolveCanonicalCode(team);
|
||
if (code) {
|
||
zhiboToCode[id] = code;
|
||
const flagUrl = flagUrlForCode(code);
|
||
if (!flagUrl) {
|
||
missingFlagCodes.add(code);
|
||
}
|
||
} else {
|
||
unmatched.push({ id, name: team.name });
|
||
}
|
||
}
|
||
|
||
fs.mkdirSync(seedDataDir, { recursive: true });
|
||
const jsonOut = path.join(seedDataDir, 'wc2026-group-stage.json');
|
||
fs.writeFileSync(jsonOut, JSON.stringify(bundle, null, 2) + '\n', 'utf8');
|
||
|
||
const mapLines = Object.entries(zhiboToCode)
|
||
.sort(([a], [b]) => Number(a) - Number(b))
|
||
.map(([id, code]) => ` ${id}: '${code}',`)
|
||
.join('\n');
|
||
|
||
const flagIsoLines = Object.entries(CODE_TO_FLAG_ISO)
|
||
.sort(([a], [b]) => a.localeCompare(b))
|
||
.map(([code, iso]) => ` ${code}: '${iso}',`)
|
||
.join('\n');
|
||
|
||
const mapTs = `/** 由 build-wc2026-seed-json.mjs 生成 — zhibo externalId → WC2026 canonical code */
|
||
export const WC2026_ZIBO_ID_TO_CODE: Record<number, string> = {
|
||
${mapLines}
|
||
};
|
||
|
||
/** WC2026 默认国家/地区国旗(FlagCDN,seed 时写入 teams.logo_url) */
|
||
export const WC2026_FLAG_ISO_BY_CODE: Record<string, string> = {
|
||
${flagIsoLines}
|
||
};
|
||
|
||
export const WC2026_TEAM_LOGO_BY_CODE: Record<string, string> = Object.fromEntries(
|
||
Object.entries(WC2026_FLAG_ISO_BY_CODE).map(([code, iso]) => [
|
||
code,
|
||
\`https://flagcdn.com/\${iso}.svg\`,
|
||
]),
|
||
);
|
||
`;
|
||
|
||
const mapOut = path.join(seedDataDir, 'wc2026-zhibo-team-map.ts');
|
||
fs.writeFileSync(mapOut, mapTs, 'utf8');
|
||
|
||
console.log(`Wrote ${jsonOut} (${matches.length} matches)`);
|
||
console.log(`Wrote ${mapOut} (${Object.keys(zhiboToCode).length} team mappings)`);
|
||
if (unmatched.length) {
|
||
console.warn('Unmatched teams:', unmatched);
|
||
process.exit(1);
|
||
}
|
||
if (missingFlagCodes.size) {
|
||
console.warn('Missing FlagCDN ISO mapping:', [...missingFlagCodes].sort());
|
||
process.exit(1);
|
||
}
|