{"record":{"id":"de1065cf52570a5d","repo":"jackwener/OpenCLI","slug":"match-must-be-an-hltv-match-stats-series-or-maps","errorCode":null,"errorMessage":"match must be an HLTV match, stats series, or mapstats URL","messagePattern":"match must be an HLTV match, stats series, or mapstats URL","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/hltv/utils.js","lineNumber":465,"sourceCode":"  const shortMatch = raw.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{2})$/);\n  if (shortMatch) {\n    const [, dd, mm, yy] = shortMatch;\n    return `20${yy}-${mm}-${dd}`;\n  }\n  const longMatch = raw.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n  if (longMatch) {\n    const [, dd, mm, yyyy] = longMatch;\n    return `${yyyy}-${mm}-${dd}`;\n  }\n  return raw;\n}\n\nexport function normalizeMatchUrl(value) {\n  const raw = String(value ?? '').trim();\n  if (!raw) throw new ArgumentError('match is required');\n  const url = /^https?:\\/\\//i.test(raw) ? parseHltvUserUrl(raw, 'match') : new URL(raw.replace(/^\\/+/, ''), `${BASE}/`);\n  if (!/^\\/(?:matches\\/\\d+\\/|stats\\/matches\\/(?:mapstatsid\\/)?\\d+\\/)/.test(url.pathname)) {\n    throw new ArgumentError('match must be an HLTV match, stats series, or mapstats URL');\n  }\n  return url;\n}\n\nfunction buildPerformanceUrl(mapstatsUrl) {\n  const url = new URL(mapstatsUrl, BASE);\n  url.pathname = url.pathname.replace('/stats/matches/mapstatsid/', '/stats/matches/performance/mapstatsid/');\n  return url;\n}\n\nexport async function resolveMatchMapUrls(page, match) {\n  const url = normalizeMatchUrl(match);\n  if (/^\\/stats\\/matches\\/mapstatsid\\/\\d+\\//.test(url.pathname)) return [url.toString()];\n\n  await gotoAndWait(page, url, 'a[href*=\"/stats/matches/mapstatsid/\"]', 'hltv match map link page');\n  const links = await page.evaluate((payload) => {\n    const seen = new Set();\n    const out = [];","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/hltv/utils.js#L447-L483","documentation":"This ArgumentError is thrown by normalizeMatchUrl when the value is non-empty and parses as a URL, but the pathname does not match an accepted HLTV match path: /matches/<digits>/, /stats/matches/<digits>/, or /stats/matches/mapstatsid/<digits>/. The library only supports those three match URL shapes; other HLTV pages (player, team, event, results listing) are rejected so downstream match-id extraction never sees garbage.","triggerScenarios":"normalizeMatchUrl('https://www.hltv.org/team/6667/falcons'), normalizeMatchUrl('/results'), normalizeMatchUrl('https://www.hltv.org/player/3741/niko'), or a match URL without a trailing slash/id segment pattern, e.g. '/matches' or '/matches/abc/label'.","commonSituations":"Pasting a results-list or news URL instead of a specific match page; mixing up team/player and match URLs; using a localized or redesigned HLTV path; passing stats URLs for team/player stats rather than series/mapstats.","solutions":["Use a full match URL: https://www.hltv.org/matches/<id>/<slug>, /stats/matches/<id>/<slug>, or /stats/matches/mapstatsid/<id>/<slug>.","Extract the specific match page link from a results page instead of passing the listing URL.","Verify the pathname starts with /matches/<digits>/ or /stats/matches/ and includes the numeric id.","If you have only a match id, construct the URL yourself, e.g. new URL(`/matches/${id}/x/`, BASE) form: `https://www.hltv.org/matches/${id}/` + any slug."],"exampleFix":"// before\nnormalizeMatchUrl('https://www.hltv.org/results'); // ArgumentError\n// after\nnormalizeMatchUrl('https://www.hltv.org/matches/2380210/falcons-vs-vital-iem-cologne-2025');","handlingStrategy":"type-guard","validationCode":"function isMatchUrlShape(v) {\n  const raw = String(v ?? '').trim();\n  if (!raw) return false;\n  let pathname = raw;\n  if (/^https?:\\/\\//i.test(raw)) {\n    try { pathname = new URL(raw).pathname; } catch { return false; }\n  }\n  return /^\\/(?:matches\\/\\d+\\/|stats\\/matches\\/(?:mapstatsid\\/)?\\d+\\/)/.test(pathname);\n}\nif (!isMatchUrlShape(input)) throw new TypeError('not an HLTV match/stats/mapstats URL');","typeGuard":"function isHltvMatchUrl(v) {\n  if (typeof v !== 'string') return false;\n  const p = v.replace(/^https?:\\/\\/[^/]+/i, '');\n  return /^\\/(?:matches\\/\\d+\\/|stats\\/matches\\/(?:mapstatsid\\/)?\\d+\\/)/.test(p);\n}","tryCatchPattern":"try {\n  return normalizeMatchUrl(input);\n} catch (err) {\n  if (err.name === 'ArgumentError' && err.message.startsWith('match must be')) {\n    throw new Error(`\"${input}\" is not a match/stats-series/mapstats URL`, { cause: err });\n  }\n  throw err;\n}","preventionTips":["Keep the library's pathname regex mirrored in your own input validation.","When extracting links from result pages, filter to /matches/<digits>/ hrefs only.","Distinguish match URLs from team/player/event URLs at the data-model level.","Test with all three accepted shapes (matches, stats/matches, mapstatsid) in CI."],"tags":["argument-error","validation","url-parsing","hltv"],"backgroundTag":"invalid-argument-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}