World Athletics Enhanced Results

click on any athlete's performances under "Results" of their profile to see the entire heat result

// ==UserScript==
// @name        World Athletics Enhanced Results
// @namespace   https://habs.sdf.org/
// @match       https://*.worldathletics.org/athletes/*
// @grant       none
// @version     1.3
// @author      habs
// @description click on any athlete's performances under "Results" of their profile to see the entire heat result
// @license     AGPLv3
// ==/UserScript==

/* jshint esversion: 9 */

const headToHeadQuery = `
query headToHead($id: Int, $headToHeadDiscipline: String, $headToHeadOpponent: Int, $headToHeadStartDate: String, $headToHeadEndDate: String, $headToHeadFinalOnly: Boolean) {
  headToHead(id: $id, headToHeadDiscipline: $headToHeadDiscipline, headToHeadOpponent: $headToHeadOpponent, headToHeadStartDate: $headToHeadStartDate, headToHeadEndDate: $headToHeadEndDate, headToHeadFinalOnly: $headToHeadFinalOnly) {
    disciplines {
      id
      name
      __typename
    }
    opponents {
      birthDate
      country
      familyName
      gender
      givenName
      id
      __typename
    }
    parameters {
      headToHeadDiscipline
      headToHeadEndDate
      headToHeadFinalOnly
      headToHeadOpponent
      headToHeadStartDate
      __typename
    }
    results {
      athlete1Wins
      athlete2Wins
      results {
        athlete1Wins
        athlete2Wins
        competition
        date
        discipline
        place1
        place2
        race
        raceType
        result1
        result2
        __typename
      }
      __typename
    }
    __typename
  }
}`;

const id = +[...document.querySelectorAll('div[class^=profileBasicInfo_stats]')].find(stat => (stat.querySelector('div[class^=profileBasicInfo_statName]') || {}).innerText.toUpperCase() === 'ATHLETE CODE').querySelector('div[class^=profileBasicInfo_statValue]').innerText;
const athName = document.querySelector('span[class^=profileBasicInfo_firstName]').innerText + ' ' + document.querySelector('span[class^=profileBasicInfo_lastName]').innerText;
const headers = { "x-api-key": "da2-ur5cyitdenbvlpx4rbtfc23qv4" };
const GRAPHQL_ENDPOINT = 'https://ogggth7qqvc4lkshalmx3tv6rq.appsync-api.us-west-2.amazonaws.com/graphql';

const disciplines = {};

(async () => {
  (await (await fetch(GRAPHQL_ENDPOINT, {
    headers,
    method: "POST",
    body: JSON.stringify({
      operationName: "headToHead",
      variables: { id },
      query: headToHeadQuery
    }),
  })).json()).data.headToHead.disciplines.forEach(d => {
    const name = d.name.replace(' ind.', '');
    disciplines[name] = { ids: name in disciplines ? [...disciplines[name].ids, d.id] : [d.id], opponents: [] };
  });

  const contentInner = document.querySelector('div[class^=profileStatistics_contentInner]');
  new MutationObserver(list => {
    const dateResults = list.find(rec => [...rec.addedNodes].find(node => [...(node.classList || [])].find(cls => cls.startsWith('profileStatistics_table'))));
    const evtResults = list.find(rec => [...rec.addedNodes].find(node => [...(node.classList || [])].find(cls => cls.startsWith('profileStatistics_results') || cls.startsWith('profileStatistics_statsTable'))));

    if (dateResults || evtResults) {
      const trs = 
        dateResults ? document.querySelector('tbody[class^=profileStatistics_tableBody]').querySelectorAll('tr')
        : [...document.querySelector('div[class^=profileStatistics_contentInner]').querySelectorAll('tbody[class^=profileStatistics_tableBody]')].flatMap(tbody => [...tbody.querySelectorAll('tr')]);
      trs.forEach(tr => {
        let date, comp, initialEvent, cnt, cat, race, place, result;
        if (dateResults) [date, comp, initialEvent, cnt, cat, race, place, result] = [...tr.querySelectorAll('td')].map(td => td.innerText);
        else if (evtResults) {
          [date, comp, cnt, cat, race, place, result] = [...tr.querySelectorAll('td')].map(td => td.innerText);
          initialEvent = tr.parentElement.parentElement.parentElement.previousElementSibling.innerText;
        }
        const event = initialEvent.replace(' ind.', '');
        const slug = `${date}-${comp.split(',')[0]}-${race}`;
        tr.style.cursor = 'pointer';
        tr.addEventListener('mouseenter', () => tr.style.backgroundColor = 'rgb(255, 192, 203)');
        tr.addEventListener('mouseleave', () => tr.style.backgroundColor = '');
        tr.addEventListener('click', async () => {
          if (!disciplines[event]) return window.alert('Event not supported');
          if (!disciplines[event].opponents.length) {
            const loadingTr = document.createElement('tr');
            const loadingTd = document.createElement('td');
            loadingTd.setAttribute('colspan', '999');
            loadingTd.innerText = `Loading...`;
            loadingTd.style.textAlign = 'center';
            loadingTr.appendChild(loadingTd);
            tr.parentNode.replaceChild(loadingTr, tr);
            for (const eventId of disciplines[event].ids) {
              disciplines[event].opponents.push(...(await (await fetch(GRAPHQL_ENDPOINT, {
                headers, method: "POST",
                body: JSON.stringify({
                  operationName: "headToHead",
                  variables: {
                    id, headToHeadDiscipline: eventId,
                  },
                  query: headToHeadQuery,
                }),
              })).json()).data.headToHead.opponents.map(opp => ({ ...opp, eventId })));
            }
            for (const opp of disciplines[event].opponents) {
              loadingTd.innerText = `Loading... (${disciplines[event].opponents.indexOf(opp) + 1}/${disciplines[event].opponents.length})`;
              opp.results = {};
              (await (await fetch(GRAPHQL_ENDPOINT, {
                headers, method: "POST",
                body: JSON.stringify({
                  operationName: "headToHead",
                  variables: {
                    id, headToHeadDiscipline: opp.eventId, headToHeadOpponent: opp.id,
                  },
                  query: headToHeadQuery,
                }),
              })).json()).data.headToHead.results.results.forEach(res => {
                opp.results[`${res.date}-${res.competition}-${res.race}`] = res;
              });
            }
            loadingTr.parentNode.replaceChild(tr, loadingTr);
            console.log(disciplines);
          }
          const heatObjs = disciplines[event].opponents.filter(opp => opp.results[slug]).map(opp => ({
            place: +opp.results[slug].place2.split('.')[0] || Infinity,
            text: `${opp.results[slug].place2} ${opp.givenName} ${opp.familyName} (${opp.country}), ${opp.results[slug].result2}`,
          }));
          heatObjs.push({
            place: +place.split('.')[0] || Infinity,
            text: `${place} ${athName}, ${result}`,
          });
          const displayHeat = heatObjs.sort((a, b) => a.place - b.place).map(obj => obj.text);
          window.alert(displayHeat.join('\n'));
        });
      }); 
    }
  }).observe(contentInner, { subtree: true, childList: true });
})();