jackwener/OpenCLI · error · ArgumentError

match map parser requires a /stats/matches/mapstatsid/:id/:s

Error message

match map parser requires a /stats/matches/mapstatsid/:id/:slug URL

What it means

readMatchMap() parses a single map's stats page and requires the input to be a map-stats URL (/stats/matches/mapstatsid/:id/:slug). It normalizes the match argument and throws ArgumentError if the pathname does not match that pattern. This guard exists because the parser also extracts the numeric matchStatsId from the URL and navigates directly to the map page.

Source

Thrown at clis/hltv/utils.js:521

  await gotoAndWait(page, url, 'a[href*="/stats/matches/"]', 'hltv match series link page');
  const seriesUrl = await page.evaluate((payload) => {
    for (const a of document.querySelectorAll('a[href*="/stats/matches/"]')) {
      const href = new URL(a.getAttribute('href'), payload.base);
      href.search = '';
      href.hash = '';
      if (/^\/stats\/matches\/\d+\//.test(href.pathname)) return href.toString();
    }
    return null;
  }, { base: BASE });

  return seriesUrl ?? url.toString();
}

export async function readMatchMap(page, match) {
  const url = normalizeMatchUrl(match);
  if (!/^\/stats\/matches\/mapstatsid\/\d+\//.test(url.pathname)) {
    throw new ArgumentError('match map parser requires a /stats/matches/mapstatsid/:id/:slug URL');
  }
  const matchStatsId = extractIdFromUrl(url.toString(), 'matchStats');

  await gotoAndWait(page, url, '.stats-section.stats-match, .stats-table.totalstats', 'hltv match map page');

  const rows = await page.evaluate((payload) => {
    const clean = (value) => String(value ?? '').replace(/\s+/g, ' ').trim();
    const cleanLines = (value) => String(value ?? '').split('\n').map((line) => clean(line)).filter(Boolean);
    const numberFrom = (value) => {
      const match = String(value ?? '').replace(/,/g, '').match(/-?\d+(?:\.\d+)?/);
      return match ? Number(match[0]) : null;
    };
    const textOf = (root, selector) => clean(root.querySelector(selector)?.textContent);
    const splitMainParen = (value) => {
      const match = clean(value).match(/^(-?\d+(?:\.\d+)?)\s*(?:\(([-\d.]+)\))?/);
      return { main: match ? Number(match[1]) : null, paren: match?.[2] !== undefined ? Number(match[2]) : null };
    };
    const infoLines = cleanLines(document.querySelector('.match-info-box')?.innerText);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass a mapstatsid URL, e.g. from resolveMatchMapUrls()/resolveStatsSeriesUrlFromMap() output
  2. Verify the path matches /stats/matches/mapstatsid/<digits>/... including the trailing slash and slug
  3. If you only have a match ID, first navigate the match page flow to obtain the mapstatsid URLs, then call readMatchMap for each

Example fix

// before
await readMatchMap(page, 'https://www.hltv.org/stats/matches/92734/natus-vincere-vs-faze');
// after
await readMatchMap(page, 'https://www.hltv.org/stats/matches/mapstatsid/73681/natus-vincere-vs-faze-map1');
Defensive patterns

Strategy: validation

Validate before calling

function requireMapStatsUrl(match) {
  const u = new URL(String(match), 'https://www.hltv.org');
  if (!/^\/stats\/matches\/mapstatsid\/\d+\//.test(u.pathname)) {
    throw new Error(`readMatchMap needs a mapstatsid URL, got: ${u.pathname}`);
  }
  return u.toString();
}
await readMatchMap(page, requireMapStatsUrl(match));

Type guard

function isMapStatsInput(v) {
  try {
    return /^\/stats\/matches\/mapstatsid\/\d+\//.test(new URL(String(v), 'https://www.hltv.org').pathname);
  } catch { return false; }
}

Try / catch

try {
  const map = await readMatchMap(page, match);
} catch (err) {
  if (err instanceof ArgumentError && /match map parser requires/.test(err.message)) {
    console.error('Pass a /stats/matches/mapstatsid/:id/:slug URL, not:', match);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling readMatchMap(page, match) with a regular match URL (/matches/:id/:slug), a series stats URL (/stats/matches/:id/:slug), an ID-only string, or a URL with a trailing slash missing after the numeric ID.

Common situations: Confusing the public match URL with the per-map stats URL (these are different HLTV pages); reusing a URL collected from a series page instead of the mapstatsid links; hand-building the URL and forgetting the slug/trailing slash.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/455e9f8d39f1baac. Report an issue: GitHub.