User:Habst/WorldAthletics2Wiki.js

Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/* handle ties e.g. in high jump, split points -- simply bail if sorted order is different than prescribed order? */
detailEvt = ``;
hideCountry = false;
doSort = false;
bdCol = true;
window.data ??= {};
window.cache ??= {};
(async () => {
for (const day of [1])
window.data[day]??=await (await fetch("https://sgb3b5eejbghhntdslcglp2whu.appsync-api.eu-west-1.amazonaws.com/graphql", {
  "headers": {
    "x-api-key": "da2-syx6uqc6jbhirjpqjy5suoy4ty" // note API key is intentionally public
  },
  "body": JSON.stringify({
  "operationName": "getCalendarCompetitionResults",
  "variables": {
    "competitionId": 7174050,
    "day": day,
    "eventId": null
  },
  "query": `query getCalendarCompetitionResults($competitionId: Int, $day: Int, $eventId: Int) {
  getCalendarCompetitionResults(competitionId: $competitionId, day: $day, eventId: $eventId) {
    competition {
      dateRange
      endDate
      name
      rankingCategory
      startDate
      venue
      __typename
    }
    eventTitles {
      rankingCategory
      eventTitle
      events {
        event
        eventId
        gender
        isRelay
        perResultWind
        withWind
        summary {
          competitor {
            teamMembers {
              id
              name
              iaafId
              urlSlug
              __typename
            }
            id
            name
            iaafId
            urlSlug
            birthDate
            __typename
          }
          mark
          nationality
          placeInRace
          placeInRound
          points
          raceNumber
          records
          wind
          __typename
        }
        races {
          date
          day
          race
          raceId
          raceNumber
          results {
            competitor {
              teamMembers {
                id
                name
                iaafId
                urlSlug
                __typename
              }
              id
              name
              iaafId
              urlSlug
              birthDate
              hasProfile
              __typename
            }
            mark
            nationality
            place
            points
            qualified
            records
            wind
            remark
            details {
              event
              eventId
              raceNumber
              mark
              wind
              placeInRound
              placeInRace
              points
              overallPoints
              placeInRoundByPoints
              overallPlaceByPoints
              __typename
            }
            __typename
          }
          startList {
            competitor {
              birthDate
              country
              id
              name
              urlSlug
              __typename
            }
            order
            pb
            sb
            bib
            __typename
          }
          wind
          __typename
        }
        __typename
      }
      __typename
    }
    options {
      days {
        date
        day
        __typename
      }
      events {
        gender
        id
        name
        combined
        __typename
      }
      __typename
    }
    parameters {
      competitionId
      day
      eventId
      __typename
    }
    __typename
  }
}`
}),
  "method": "POST",
})).json();

if (typeof nameFixer === 'undefined') {
  const script = Object.assign(document.createElement('script'), { src: 'https://unpkg.com/name-fixer@1.0.0' });
  document.body.appendChild(script);
  await new Promise(res => script.addEventListener('load', res));
}
titleExists=async (name)=>{
  const enLabelTitleMatch = await fetch(`https://xtools.wmcloud.org/api/page/articleinfo/en.wikipedia.org/${name.replace('|', '')}?format=json&uselang=en`);
  return enLabelTitleMatch.status === 200;
}
getSuffix=async (name, evt, year) => {
  const el = evt?.toLowerCase() ?? '';
  const parens = evt?.includes('mH') || el.includes('metres hurdles') ? 'hurdler' : el.includes('high jump') ? 'high jumper' : el.includes('long jump') ? 'long jumper' : el.includes('pole vault') ? 'pole vaulter' : el.includes('triple jump') ? 'triple jumper' : el.includes('shot put') ? 'shot putter' : el.includes('discus') ? 'discus thrower' : el.includes('hammer') ? 'hammer thrower' : el.includes('javelin') ? 'javelin thrower' : el.includes('steeplchase') ? 'steeplechase runner' : ['60m', '60 m', '100m', '100 m', '200m', '200 m', '400m', '400 m'].some(d => el.includes(d)) ? 'sprinter' : 'runner';
  name += ` (${parens})`;
  if (await titleExists(name)) name = name.replace(`(${parens})`, `(${parens}, born ${year})`);
  return name;
}
getTitle=async (id,name,evt,year)=>{
  const words = name.split(' ');
  const lnameStart = words.findIndex(w => w.toUpperCase() === w);
  const fname = words.slice(0, lnameStart).join(' ');
  const lname = words.slice(lnameStart).join(' ');
  name = fname + ' ' + nameFixer.nameFixer(lname);
  name = name.replace('LI', 'Li').replace('XI', 'Xi');
  if (cache[id]) return cache[id];
  const pages = await (await fetch('https://www.wikidata.org/w/api.php?' + new URLSearchParams({
    action: 'query',
    format: 'json',
    list: 'search',
    srsearch: `haswbstatement:P1146=${id}`,
  }))).json();
  const qid = pages.query.search[0]?.title;
  if (qid) {
    const entity = await (await fetch('https://www.wikidata.org/w/api.php?' + new URLSearchParams({
      action: 'wbgetentities',
      format: 'json',
      ids: qid,
    }))).json();
    const sitelinks = entity.entities[qid].sitelinks;
    const enTitle = sitelinks.enwiki?.title;
    if (enTitle) {
      cache[id] = `[[${enTitle}${enTitle.includes('(') ? '|' : ''}]]`;
      return cache[id];
    }
    let enLabel = entity.entities[qid].labels.en?.value ?? name;
    const enLabelNoParens = enLabel;
    if (await titleExists(enLabel)) enLabel = await getSuffix(enLabel, evt, year);
    const otherWikis = Object.keys(sitelinks).filter(key => !key.startsWith('commons') && key.endsWith('wiki'));
    if (otherWikis.length) {
      const positionals = otherWikis.map(ow => `|${ow.replace('wiki', '')}|${sitelinks[ow].title}`).join('');
      cache[id] = `{{ill|${enLabel}${positionals}${enLabel.includes('(') ? `|lt=${enLabelNoParens}` : ''}}}`;
      return cache[id];
    }
    cache[id] = `{{ill|${enLabel}|wd=${qid}|s=1${enLabel.includes('(') ? `|lt=${enLabelNoParens}` : ''}}}`;
    return cache[id];
  }
  if (await titleExists(name)) name = await getSuffix(name, evt, year);
  cache[id] = `[[${name}${name.includes('(') ? '|' : ''}]]`;
  return cache[id];
}
mark2secs=(mark, isField = false)=>{
  const parts = mark.split(':');
  let ret;
  if (parts.length === 1) ret = +mark;
  else if (parts.length === 2) ret = +parts[0] * 60 + +parts[1];
  else ret = +parts[0] * 60 * 60 + +parts[1] * 60 + +parts[2];
  if (Number.isNaN(ret)) return isField ? -Infinity : Infinity;
  return ret;
}
const COLS = 2;
let out = '';
const combinedData = Object.values(data).reduce((acc, dayData) => {
  for (let eventTitle of dayData.data.getCalendarCompetitionResults.eventTitles) {
    eventTitle = structuredClone(eventTitle);
    const foundEventTitle = acc.data.getCalendarCompetitionResults.eventTitles.find(et => et.eventTitle === eventTitle.eventTitle);
    if (foundEventTitle) {
      const oldEvts = [...eventTitle.events];
      for (const evt of oldEvts) {
        const foundEvt = foundEventTitle.events.find(e2 => e2.event === evt.event);
        if (foundEvt) foundEvt.races.push(...evt.races);
        else foundEventTitle.events.push(evt);
      }
    }
    else acc.data.getCalendarCompetitionResults.eventTitles.push(eventTitle);
  }
  return acc;
}, { data: { getCalendarCompetitionResults: { eventTitles: [], competition: Object.values(data)[0].data.getCalendarCompetitionResults.competition } } });
startDate = new Date(combinedData.data.getCalendarCompetitionResults.competition.startDate);
startDateOrig = combinedData.data.getCalendarCompetitionResults.competition.startDate;
for (const eventTitle of combinedData.data.getCalendarCompetitionResults.eventTitles) {
  eventTitle.pointsTitle = eventTitle.eventTitle;
  if (eventTitle.eventTitle?.startsWith('XC ')) eventTitle.pointsTitle = eventTitle.eventTitle.replace(/XC [0-9\.]+km/, 'XC');
  console.log(eventTitle.pointsTitle)
  if (!['World Athletics Indoor Tour', 'Indoor Meeting', 'U20 Events', 'Masters Events', 'Diamond Discipline', 'Promotional Events', 'National Events', 'U18 Events', 'Regional Races', 'Additional Events', 'XC', null].includes(eventTitle.pointsTitle)) continue;
let evtIdx = -1;
let etOutput = `===${eventTitle.eventTitle ?? eventTitle.events.find(evt => evt.event === detailEvt)?.races[0].race}===\n`
for (const evt of eventTitle.events) {
  if (detailEvt && evt.event !== detailEvt) continue;
  let etHasEvents = true;
  const isLastEvt = eventTitle.events.indexOf(evt) === eventTitle.events.length - 1;
  const isField = ['jump', 'throw', 'vault', 'discus', 'put'].some(s => evt.event.toLowerCase().includes(s));
  const stages = Object.values(evt.races.reduce((acc, r) => {
    acc[r.race] ??= [];
    acc[r.race].push(r);
    return acc;
  }, {}));
  for (const stage of stages) {
    const isLastStage = stages.indexOf(stage) === stages.length - 1;
    evtIdx++;
    const isFinal = stage[0].race === 'Final';
    const finalQualIds = isFinal ? [] : (stages.find(st => st[0].race === 'Final') ?? []).flatMap(race => race.results).map(res => res.competitor.urlSlug?.split('-').at(-1).replace(/^0/, ''));
    // Object.assign({}, [...document.querySelector('.records-table').querySelectorAll('tr')].map(tr => tr.querySelectorAll('td')[2]?.innerText).filter(x => x?.trim()))
    const hasPts = isFinal && eventTitle.pointsTitle === 'World Athletics Indoor Tour' ? {1: 10, 2: 7, 3: 5, 4: 3} : isFinal && eventTitle.pointsTitle === 'Diamond Discipline' ? {1:8,2:7,3:6,4:5,5:4,6:3,7:2,8:1} : eventTitle.pointsTitle === 'XC' ? {"1":"1240","2":"1220","3":"1200","4":"1180","5":"1160","6":"1145","7":"1130","8":"1120","9":"1110","10":"1100","11":"1090","12":"1080","13":"1070","14":"1060","15":"1055","16":"1050","17":"1045","18":"1040","19":"1035","20":"1030","21":"1025","22":"1020","23":"1015","24":"1010","25":"1005","26":"1000","27":"995","28":"990","29":"985","30":"980","31":"975","32":"970","33":"965","34":"960","35":"955","36":"950","37":"945","38":"940","39":"935","40":"930","41":"927","42":"924","43":"921","44":"918","45":"915","46":"912","47":"909","48":"906","49":"903","50":"900","51":"898","52":"896","53":"894","54":"892","55":"890","56":"888","57":"886","58":"884","59":"882","60":"880","61":"879","62":"878","63":"877","64":"876","65":"875","66":"874","67":"873","68":"872","69":"871","70":"870","71":"869","72":"868","73":"887","74":"866","75":"865","76":"864","77":"863","78":"862","79":"861","80":"860"} : null;
    const isMulti = stage.length > 1;
    if (evtIdx % COLS === 0) etOutput += '{{col-begin}}\n';
    etOutput += `{{col-${COLS}}}\n`;
    const unfilteredResults = stage.flatMap(race => race.results.map(res => ({...res, raceNumber: race.raceNumber}))).map((r, idx, arr) => ({...r, bestWindLegal: !r.competitor.teamMembers && arr.findIndex(r2 => r2.competitor.urlSlug === r.competitor.urlSlug) !== idx}));
    const bestWindLegals = unfilteredResults.filter(r => r.bestWindLegal);
    let results = unfilteredResults.filter(r => !r.bestWindLegal)
    if (doSort) results = results.sort((a, b) => isField ? mark2secs(b.mark, true) - mark2secs(a.mark, true) : mark2secs(a.mark) - mark2secs(b.mark));
    const hasWindCol = results.some(res => res.wind);
    const numTableCols = 4 + hasWindCol + isMulti + hasPts + bdCol;
    etOutput += `{| class="wikitable mw-datatable sortable"
|+${evt.event.replace(' indoor', '') + (isFinal ? (stage.length === 1 && stage[0].wind ? ` <small>{{nowrap|(${stage[0].wind} m/s)}}</small>`  : '') : ` ${stage[0].race}`)}
! Place !! Athlete !!${bdCol ? ' Age !!' : ''}${hideCountry ? '' : ' Country !!'} ${isField ? 'Mark' : 'Time'}${hasWindCol ? ' !! Wind' : ''}${isMulti ? ' !! Heat' : ''}${hasPts ? ' !! Points' : ''}\n`;
    const getResultRow = async (result, isBestWindLegal = false) => {
      const pl = doSort ? ((['DNS', 'DNF', 'DQ', 'NM'].includes(result.mark) ? '' : results.indexOf(result) + 1) || '') : result.place?.replace('.', '');
      const name = result.competitor.name;
      const dob = new Date(result.competitor.birthDate);
      const isYearOnly = result.competitor.birthDate?.split(' ').length === 1;
      const id = result.competitor.urlSlug?.split('-').at(-1).replace(/^0/, '');
      return `|-${!isFinal && finalQualIds.includes(id) ? 'bgcolor=#bbf3bb' : ''}\n|align=center| ${{1: '{{Gold1}}', 2: '{{Silver2}}', 3: '{{Bronze3}}'}[isFinal ? pl : 0] ?? pl} || ${id ? await getTitle(id, name, evt.event, dob.getFullYear()) : (await Promise.all(result.competitor.teamMembers.map(async tm => await getTitle(tm.id, tm.name, evt.event)))).join('<br>')} ${bdCol ? `|| ${result.competitor.birthDate ? `{{age|${result.competitor.birthDate}|${startDateOrig}}} ` : ''}` : ''}${hideCountry ? '' : `|| {{flagg|cncie|${result.nationality}}} `}|| ${isField && (pl || isBestWindLegal) ? `{{nowrap|${result.mark} m}}` : result.mark}${hasWindCol ? ` ||align=right| {{nowrap|${result.wind} m/s}}` : ''}${isMulti ? ` ||align=center| ${result.raceNumber}` : ''}${hasPts ? ` ||align=center| ${hasPts[pl] ?? ''}` : ''}\n`;
    }
    for (const result of results) etOutput += await getResultRow(result);
    if (bestWindLegals.length) {
      etOutput += `|-\n!align=center colspan=${numTableCols}| Best wind-legal performances\n`;
      for (const bwl of bestWindLegals) etOutput += await getResultRow(bwl, true);
    }
    etOutput += '|}\n';
    if (evtIdx % COLS === COLS - 1 || (isLastEvt && isLastStage)) etOutput += '{{col-end}}\n';
  }
}
if (etOutput.split('\n').length > 2) out += etOutput; 
}
if (!out.endsWith('{{col-end}}\n')) out += '{{col-end}}\n';
console.log(out);
return out;
})();