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. |
// 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;
})();