Jump to content

User:Habst/WorldAthletics2Wiki.js

From Wikipedia, the free encyclopedia
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.
// Credit to User:Xymph for recent changes at [[User:Xymph/WorldAthletics2Wiki-1col.js]]
// layout in one column, empty row for ref below event, sprint wind speeds below Time/Points, add Notes column, Rank/Nation headings, simple nation links; e.g. https://en.wikipedia.org/wiki/2012_Bislett_Games (WA 7033726)
// run this script in the JavaScript console (F12, Console) while on https://www.wikidata.org/
COMPETITION_ID = '7033726'; // <-- add numeric ID here you get from searching the meet at https://worldathletics.org/competition/calendar-results and clicking "Results"
NUM_DAYS = [1]; // for multi-day meets, add the day numbers here to fetch results for all of them e.g. [1, 2] or [1, 3]

detailEvt = ``;
hideCountry = false;
doSort = false;
bdCol = false;
window.data ??= {};
window.cache ??= {};
(async () => {
for (const day of NUM_DAYS)
window.data[day] ??= await (await fetch("https://graphql-prod-4752.prod.aws.worldathletics.org/graphql", {
  "headers": {
    "x-api-key": "da2-qmxd4dl6zfbihixs5ik7uhwor4" // note API key is intentionally public
  },
  "body": JSON.stringify({
  "operationName": "getCalendarCompetitionResults",
  "variables": {
    "competitionId": COMPETITION_ID,
    "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}|qid=${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;
};
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) {
  console.log('fetching results for eventTitle:', eventTitle.eventTitle);
  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 = `\n===${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 isHeight = ['high jump', 'pole vault'].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;
    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 = 5 + !!isMulti + !!hasPts + !!bdCol;
    const numWindCols = 2 + !!hasPts;
    etOutput += `{| class="wikitable mw-datatable sortable" style="text-align:center;"
|+${evt.event.replace(' indoor', '') + (isFinal ? '' : ` ${stage[0].race}`)}
! Rank !! Athlete !!${bdCol ? ' Age !!' : ''}${hideCountry ? '' : ' Nation !!'} ${isField ? `${isHeight ? 'Height' : 'Distance'}` : 'Time'}${isMulti ? ' !! Heat' : ''}${hasPts ? ' !! Points' : ''} !! Notes\n`;
    const getResultRow = async (result, isBestWindLegal = false) => {
      const hasAbbr = ['DNS', 'DNF', 'DQ', 'NM'].includes(result.mark);
      const pl = doSort ? ((hasAbbr ? '—' : results.indexOf(result) + 1) || '') : (result.place ? result.place.replace('.', '') : '—');
      const name = result.competitor.name;
      const nation = result.nationality === 'GBR' ? 'GBR2' : (result.nationality === 'BRN' ? 'BHR' : result.nationality);
      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| ${{1: '{{Gold1}}', 2: '{{Silver2}}', 3: '{{Bronze3}}'}[isFinal ? pl : 0] ?? pl} ||align=left| ${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 ? '' : `||align=left| {{${nation}}} `}|| ${hasAbbr ? `{{AthAbbr|${result.mark}}}` : `${isField && (pl || isBestWindLegal) ? `${result.mark} m` : result.mark}`}${hasWindCol && !hasAbbr ? ` {{wind|${result.wind}}}` : ''}${isMulti ? ` || ${result.raceNumber}` : ''}${hasPts ? ` || ${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 += '|- class="sortbottom"\n';
    if (isFinal && stage.length === 1 && stage[0].wind) {
      etOutput += `| colspan=` + (numTableCols - numWindCols) + `|\n`;
      etOutput += `| colspan=${numWindCols}| Wind: {{wind|${stage[0].wind}}}\n`;
    } else {
      etOutput += `| colspan=${numTableCols}|\n`;
    }
    etOutput += '|}\n';
  }
}
if (etOutput.split('\n').length > 2) out += etOutput; 
}
console.log(out);
return out;
})();