User:Habst/WorldAthletics2Wiki.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:Habst/WorldAthletics2Wiki. |
/* 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://graphql-prod-4646.prod.aws.worldathletics.org/graphql", {
"headers": {
"x-api-key": "da2-hmvn5poyr5ch5guhvq2nl4v6v4" // note API key is intentionally public
},
"body": JSON.stringify({
"operationName": "getCalendarCompetitionResults",
"variables": {
"competitionId": 7174052,
"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;
})();