diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1b96d8182..6cf6a809e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,7 +2,28 @@ version: 2 updates: - package-ecosystem: npm - directory: "/" + directory: "/scraper" + schedule: + interval: daily + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + commit-message: + prefix: "npm" + include: "scope" + groups: + dependencies-patch-and-minor: + update-types: + - minor + - patch + open-pull-requests-limit: 50 + reviewers: + - fabrice404 + assignees: + - fabrice404 + +- package-ecosystem: npm + directory: "/ui" schedule: interval: daily ignore: diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..8a78ee42c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "cSpell.words": [ + "Cortina", + + "NOC", // National Olympic Committee + "NOCs", // National Olympic Committees + + // color palette + "azzurro", + "giallo", + "rosa", + "rosso", + "verde", + "viola" + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..7269c8dc1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,3 @@ +Copyright (c) 2025 Fabrice Lamant. All rights reserved. + +No part of this work may be copied, reproduced, distributed, transmitted, displayed, performed, or otherwise used, in whole or in part, by any means or in any form, without the prior written permission of the copyright holder. diff --git a/scraper/cache.ts b/scraper/cache.ts index fbd397751..aa61c4eb9 100644 --- a/scraper/cache.ts +++ b/scraper/cache.ts @@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs" const debug = Debug(`olympics-calendar:cache`); const cachePath = (key: string): string => { - return `../cache/${key}`; + return `../cache/${key}.cached`; } export const get = (key: string): string | null => { diff --git a/scraper/ics.ts b/scraper/ics.ts new file mode 100644 index 000000000..f00fd8a30 --- /dev/null +++ b/scraper/ics.ts @@ -0,0 +1,150 @@ +// BEGIN:VCALENDAR +// VERSION:2.0 +// PRODID:-//fabrice404//olympics-calendar//archery/AUS//EN +// X-WR-CALNAME:๐Ÿ‡ฆ๐Ÿ‡บ Australia Archery | Paris 2024 +// NAME:๐Ÿ‡ฆ๐Ÿ‡บ Australia Archery | Paris 2024 +// BEGIN:VEVENT +// UID:20240725T073000Z-archery-WOMENS-INDIVIDUAL-RANKING-ROUND +// DTSTAMP:20240725T073000Z +// DTSTART:20240725T073000Z +// DTEND:20240725T103000Z +// DESCRIPTION:Archery - Women's Individual Ranking Round\n๐Ÿ‡จ๐Ÿ‡ณ AN +// Qixuan\n๐Ÿ‡ฒ๐Ÿ‡ฝ Alejandra VALENCIA\n๐Ÿ‡ฒ๐Ÿ‡ฉ Alexandra MIRCA\n๐Ÿ‡ต๐Ÿ‡ท Alondra +// RIVERA\n๐Ÿ‡ซ๐Ÿ‡ท Amelie CORDEAU\n๐Ÿ‡ง๐Ÿ‡ท Ana Luiza SLIACHTICAS CAETANO\n๐Ÿ‡จ๐Ÿ‡ด Ana +// RENDON MARTINEZ\n๐Ÿ‡ฒ๐Ÿ‡ฝ Ana VAZQUEZ\n๐Ÿ‡ฒ๐Ÿ‡ฝ Angela RUIZ\n๐Ÿ‡ฎ๐Ÿ‡ณ Ankita +// BHAKAT\n๐Ÿ‡ฒ๐Ÿ‡พ Ariana Nur Dania MOHAMAD ZAIRI\n๐Ÿ‡ฎ๐Ÿ‡ณ Bhajan KAUR\n๐Ÿ‡ฌ๐Ÿ‡ง +// Bryony PITMAN\n๐Ÿ‡น๐Ÿ‡ผ CHIU Yi-Ching\n๐Ÿ‡ซ๐Ÿ‡ท Caroline LOPEZ\n๐Ÿ‡บ๐Ÿ‡ธ Casey +// KAUFHOLD\n๐Ÿ‡บ๐Ÿ‡ธ Catalina GNORIEGA\n๐Ÿ‡ฉ๐Ÿ‡ช Charline SCHWARZ\n๐Ÿ‡ฎ๐Ÿ‡น Chiara +// REBAGLIATI\n๐Ÿ‡ฎ๐Ÿ‡ณ Deepika KUMARI\n๐Ÿ‡ธ๐Ÿ‡ฐ Denisa BARANKOVA\n๐Ÿ‡ฎ๐Ÿ‡ฉ Diananda +// CHOIRUNISA\n๐Ÿ‡ช๐Ÿ‡ธ Elia CANALES\n๐Ÿ‡น๐Ÿ‡ท Elif Berra GOKKIR\n๐Ÿ‡ฆ๐Ÿ‡น Elisabeth +// STRAKA\n๐Ÿ‡ฌ๐Ÿ‡ณ Fatoumata SYLLA\n๐Ÿ‡ณ๐Ÿ‡ฑ Gaby SCHLOESSER\n๐Ÿ‡ธ๐Ÿ‡ฒ Giorgia +// CESARINI\n๐Ÿ‡ฐ๐Ÿ‡ท JEON Hunyoung\n๐Ÿ‡ช๐Ÿ‡ฌ Jana ALI\n๐Ÿ‡บ๐Ÿ‡ธ Jennifer MUCINO\n๐Ÿ‡ฉ๐Ÿ‡ช +// Katharina BAUER\n๐Ÿ‡ฉ๐Ÿ‡ฐ Kirstine DANSTRUP ANDERSEN\n๐Ÿ‡น๐Ÿ‡ผ LEI +// Chien-Ying\n๐Ÿ‡จ๐Ÿ‡ณ LI Jiaman\n๐Ÿ‡น๐Ÿ‡ผ LI Tsai-Chi\n๐Ÿ‡ฐ๐Ÿ‡ท LIM Sihyeon\n๐Ÿ‡ฆ๐Ÿ‡บ +// Laura PAEGLIS\n๐Ÿ‡ณ๐Ÿ‡ฑ Laura van der WINKEL\n๐Ÿ‡ซ๐Ÿ‡ท Lisa BARBELIN\n๐Ÿ‡ท๐Ÿ‡ด +// Madalina AMAISTROAIE\n๐Ÿ‡จ๐Ÿ‡ฟ Marie HORACKOVA\n๐Ÿ‡ฌ๐Ÿ‡ง Megs HAVERS\n๐Ÿ‡ฉ๐Ÿ‡ช +// Michelle KROPPEN\n๐Ÿ‡ฎ๐Ÿ‡ฑ Mikaella MOSHE\n๐Ÿ‡ฎ๐Ÿ‡ท Mobina FALLAH\n๐Ÿ‡ฐ๐Ÿ‡ท NAM +// Suhyeon\n๐Ÿ‡ฏ๐Ÿ‡ต NODA Satsuki\n๐Ÿ‡ฒ๐Ÿ‡พ Nurul Azreena MOHAMAD FAZIL\n๐Ÿ‡ฌ๐Ÿ‡ง Penny +// HEALEY\n๐Ÿ‡ณ๐Ÿ‡ฑ Quinty ROEFFEN\n๐Ÿ‡ช๐Ÿ‡ช Reena PARNAT\n๐Ÿ‡ฎ๐Ÿ‡ฉ Rezza OCTAVIA\n๐Ÿ‡น๐Ÿ‡ณ +// Rihab ELWALID\n๐Ÿ‡ฒ๐Ÿ‡พ Syaqiera MASHAYIKH\n๐Ÿ‡ฎ๐Ÿ‡ฉ Syifa Nurafifah KAMAL\n๐Ÿ‡ป๐Ÿ‡ณ +// Thi Anh Nguyet DO\n๐Ÿ‡บ๐Ÿ‡ฆ Veronika MARCHENKO\n๐Ÿ‡จ๐Ÿ‡ฆ Virginie CHENIER\n๐Ÿ‡ต๐Ÿ‡ฑ +// Wioleta MYSZOR\n๐Ÿ‡จ๐Ÿ‡ณ YANG Xiaolei\n๐Ÿ‡ฆ๐Ÿ‡ฟ Yaylagul RAMAZANOVA\n๐Ÿ‡ธ๐Ÿ‡ฎ Zana +// PINTARIC\n๐Ÿ‡บ๐Ÿ‡ฟ Ziyodakhon ABDUSATTOROVA +// SUMMARY:๐Ÿน Women's Individual Ranking Round +// LOCATION:Invalides +// END:VEVENT + +import { mkdirSync, writeFileSync } from "fs"; +import { Calendar } from "./types"; +import { getFlag } from "./nocs"; + + +// BEGIN:VCALENDAR +// VERSION:2.0 +// PRODID:-//fabrice404//olympics-calendar//3x3-basketball/AUS//EN +// X-WR-CALNAME:๐Ÿ‡ฆ๐Ÿ‡บ Australia 3x3 Basketball | Paris 2024 +// NAME:๐Ÿ‡ฆ๐Ÿ‡บ Australia 3x3 Basketball | Paris 2024 +// BEGIN:VEVENT +// UID:20240730T160000Z-3x3-basketball-WOMENS-POOL-ROUND-AUS-CAN +// DTSTAMP:20240730T160000Z +// DTSTART:20240730T160000Z +// DTEND:20240730T162500Z +// DESCRIPTION:3x3 Basketball - Women's Pool Round +// SUMMARY:๐Ÿ€ AUS ๐Ÿ‡ฆ๐Ÿ‡บ - ๐Ÿ‡จ๐Ÿ‡ฆ CAN +// LOCATION:La Concorde 1 +// END:VEVENT + +// END:VCALENDAR + +export const generateICSFiles = (calendar: Calendar): void => { + + generateICSFile(calendar, null, null); + + calendar.sports.forEach((sport) => { + generateICSFile(calendar, sport.key, null); + calendar.nocs.forEach((noc) => { + generateICSFile(calendar, sport.key, noc.key); + }); + }); + + calendar.nocs.forEach((noc) => { + generateICSFile(calendar, null, noc.key); + }); +}; + + +export const generateICSFile = (calendar: Calendar, sportKey: string | null, nocKey: string | null): void => { + calendar.languages.forEach((lang) => { + const pathSportKey = sportKey ? sportKey : "all-sports"; + const pathNocKey = nocKey ? nocKey : "calendar" + + const filepath = `../ui/public/data/${lang.code.toLowerCase()}/${pathSportKey.toLowerCase()}/${pathNocKey.toLowerCase()}.ics`; + mkdirSync(filepath.split('/').slice(0, -1).join('/'), { recursive: true }); + + const titleComponents = []; + if (nocKey) { + titleComponents.push(`${calendar.nocs.find(n => n.key === nocKey)!.name[lang.code]}`); + } + if (sportKey) { + titleComponents.push(calendar.sports.find(s => s.key === sportKey)!.name[lang.code]); + } + titleComponents.push("Milano Cortina 2026"); + + const title = titleComponents.join(' - '); + + const lines = []; + + lines.push("BEGIN:VCALENDAR"); + lines.push("VERSION:2.0"); + lines.push(`PRODID:-//fabrice404//olympics-calendar//${lang.code}/${pathSportKey}/${pathNocKey}`); + lines.push(`X-WR-CALNAME:${title}`); + lines.push(`NAME:${title}`); + + calendar.events + .filter((event) => { + if (sportKey && event.sport !== sportKey) return false; + if (nocKey) { + if (event.match) { + const team1Key = event.match.team1.key; + const team2Key = event.match.team2.key; + if (team1Key !== nocKey && team2Key !== nocKey) { + return false; + } + } else { + return false; + } + } + return true; + }) + .forEach((event) => { + lines.push("BEGIN:VEVENT"); + lines.push(`UID:${event.key.replace(/--/g, '-')}`); + lines.push(`DTSTAMP:${event.start.replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z')}`); + lines.push(`DTSTART:${event.start.replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z')}`); + lines.push(`DTEND:${event.end.replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z')}`); + lines.push(`LOCATION:${event.location[lang.code] || ''}`); + + const sport = calendar.sports.find(s => s.key === event.sport)!; + lines.push(`DESCRIPTION:${sport.name[lang.code]} - ${event.name[lang.code] || ''}`); + let summary = `SUMMARY:${event.name[lang.code] || ''}` + + if (event.match) { + const team1Name = event.match.team1.name[lang.code] || event.match.team1.key; + const team1Flag = getFlag(event.match.team1.key); + const team2Name = event.match.team2.name[lang.code] || event.match.team2.key; + const team2Flag = getFlag(event.match.team2.key); + if (team1Name && team2Name) { + lines.push(`SUMMARY:${team1Flag} ${team1Name} - ${team2Name} ${team2Flag}`); + } + } + + lines.push(summary); + lines.push(`END:VEVENT`); + + }) + + lines.push("END:VCALENDAR"); + + writeFileSync(filepath, lines.join('\n')); + }); +}; diff --git a/scraper/index.ts b/scraper/index.ts index 7bfe11afd..a2b55044e 100644 --- a/scraper/index.ts +++ b/scraper/index.ts @@ -1,12 +1,16 @@ import Debug from "debug"; import * as cache from "./cache"; +import { mkdirSync, writeFileSync } from "fs"; +import { Calendar, Event, Sport, Team } from "./types"; +import { generateICSFiles } from "./ics"; const baseUrl = "https://www.olympics.com"; const basePath = "/milano-cortina-2026/schedule/overview"; const debug = Debug(`olympics-calendar:index`); + const getScheduleOverview = async (language: string) => { debug(`getScheduleOverview: language=${language}`); @@ -48,7 +52,7 @@ const getScheduleSport = async (language: string, sportCode: string) => { const scheduleSport = JSON.parse(cache.get(scheduleSportKey)!); return scheduleSport; -} +}; const main = async () => { const overview = await getScheduleOverview("en"); @@ -59,9 +63,9 @@ const main = async () => { name: lang.label, })) - const sports: any = []; - const events: any[] = []; - let nocs: any[] = []; + const sports: Sport[] = []; + const events: Event[] = []; + let nocs: Team[] = []; for (const lang of languages) { const scheduleOverview = await getScheduleOverview(lang.code); @@ -76,7 +80,7 @@ const main = async () => { if (sports.find((s: any) => s.key === key) == null) { sports.push({ key, name: {}, order: -1 }) } - const sport = sports.find((s: any) => s.key === key) + const sport = sports.find((s: any) => s.key === key)!; sport.name[lang.code] = discipline.description; sport.order = discipline.order; @@ -93,10 +97,12 @@ const main = async () => { isTraining: scheduleListElement.isTraining, medal: scheduleListElement.medal, name: {}, + location: {}, }) } - const event = events.find(e => e.key === scheduleListElement.unitCode); + const event = events.find(e => e.key === scheduleListElement.unitCode)!; event.name[lang.code] = scheduleListElement.description; + event.location[lang.code] = scheduleListElement.venue?.description || '' if (scheduleListElement.match) { if (event.match == null) { @@ -114,7 +120,7 @@ const main = async () => { if (nocs.find(n => n.key === nocKey) == null) { nocs.push({ key: nocKey, name: {} }); } - const noc = nocs.find(n => n.key === nocKey); + const noc = nocs.find(n => n.key === nocKey)!; noc.name[lang.code] = (team.description || '').replace(/\,/gi, ''); } } @@ -125,7 +131,11 @@ const main = async () => { nocs = nocs.filter((noc) => noc.key !== noc.name.en); - cache.set('calendar.json', JSON.stringify({ languages, sports, nocs, events })); + const dataFolder = "../ui/public/data"; + mkdirSync(dataFolder, { recursive: true }); + const calendar: Calendar = { languages, sports, nocs, events }; + writeFileSync(`${dataFolder}/calendar.json`, JSON.stringify(calendar)); + generateICSFiles(calendar); }; main(); diff --git a/scraper/nocs.ts b/scraper/nocs.ts new file mode 100644 index 000000000..36fbddab8 --- /dev/null +++ b/scraper/nocs.ts @@ -0,0 +1,212 @@ +export const flags: { [key: string]: string } = { + AFG: "๐Ÿ‡ฆ๐Ÿ‡ซ", + ALB: "๐Ÿ‡ฆ๐Ÿ‡ฑ", + ALG: "๐Ÿ‡ฉ๐Ÿ‡ฟ", + AIN: "๐Ÿ‡ฆ๐Ÿ‡ธ", + AND: "๐Ÿ‡ฆ๐Ÿ‡ฉ", + ANG: "๐Ÿ‡ฆ๐Ÿ‡ด", + ANT: "๐Ÿ‡ฆ๐Ÿ‡ฌ", + ARG: "๐Ÿ‡ฆ๐Ÿ‡ท", + ARM: "๐Ÿ‡ฆ๐Ÿ‡ฒ", + ARU: "๐Ÿ‡ฆ๐Ÿ‡ผ", + ASA: "๐Ÿ‡ฆ๐Ÿ‡ธ", + AUS: "๐Ÿ‡ฆ๐Ÿ‡บ", + AUT: "๐Ÿ‡ฆ๐Ÿ‡น", + AZE: "๐Ÿ‡ฆ๐Ÿ‡ฟ", + BAH: "๐Ÿ‡ง๐Ÿ‡ธ", + BAN: "๐Ÿ‡ง๐Ÿ‡ฉ", + BAR: "๐Ÿ‡ง๐Ÿ‡ง", + BDI: "๐Ÿ‡ง๐Ÿ‡ฎ", + BEL: "๐Ÿ‡ง๐Ÿ‡ช", + BEN: "๐Ÿ‡ง๐Ÿ‡ฏ", + BER: "๐Ÿ‡ง๐Ÿ‡ฒ", + BHU: "๐Ÿ‡ง๐Ÿ‡น", + BIH: "๐Ÿ‡ง๐Ÿ‡ฆ", + BIZ: "๐Ÿ‡ง๐Ÿ‡ฟ", + BOL: "๐Ÿ‡ง๐Ÿ‡ด", + BOT: "๐Ÿ‡ง๐Ÿ‡ผ", + BRA: "๐Ÿ‡ง๐Ÿ‡ท", + BRN: "๐Ÿ‡ง๐Ÿ‡ญ", + BRU: "๐Ÿ‡ง๐Ÿ‡ณ", + BUL: "๐Ÿ‡ง๐Ÿ‡ฌ", + BUR: "๐Ÿ‡ง๐Ÿ‡ซ", + CAF: "๐Ÿ‡จ๐Ÿ‡ซ", + CAM: "๐Ÿ‡ฐ๐Ÿ‡ญ", + CAN: "๐Ÿ‡จ๐Ÿ‡ฆ", + CAY: "๐Ÿ‡ฐ๐Ÿ‡พ", + CGO: "๐Ÿ‡จ๐Ÿ‡ฌ", + CHA: "๐Ÿ‡น๐Ÿ‡ฉ", + CHI: "๐Ÿ‡จ๐Ÿ‡ฑ", + CHN: "๐Ÿ‡จ๐Ÿ‡ณ", + CIV: "๐Ÿ‡จ๐Ÿ‡ฎ", + CMR: "๐Ÿ‡จ๐Ÿ‡ฒ", + COD: "๐Ÿ‡จ๐Ÿ‡ฉ", + COK: "๐Ÿ‡จ๐Ÿ‡ฐ", + COL: "๐Ÿ‡จ๐Ÿ‡ด", + COM: "๐Ÿ‡ฐ๐Ÿ‡ฒ", + CPV: "๐Ÿ‡จ๐Ÿ‡ป", + CRC: "๐Ÿ‡จ๐Ÿ‡ท", + CRO: "๐Ÿ‡ญ๐Ÿ‡ท", + CUB: "๐Ÿ‡จ๐Ÿ‡บ", + CYP: "๐Ÿ‡จ๐Ÿ‡พ", + CZE: "๐Ÿ‡จ๐Ÿ‡ฟ", + DEN: "๐Ÿ‡ฉ๐Ÿ‡ฐ", + DJI: "๐Ÿ‡ฉ๐Ÿ‡ฏ", + DMA: "๐Ÿ‡ฉ๐Ÿ‡ฒ", + DOM: "๐Ÿ‡ฉ๐Ÿ‡ด", + ECU: "๐Ÿ‡ช๐Ÿ‡จ", + EGY: "๐Ÿ‡ช๐Ÿ‡ฌ", + EOR: "๐Ÿณ๏ธ", + ERI: "๐Ÿ‡ช๐Ÿ‡ท", + ESA: "๐Ÿ‡ธ๐Ÿ‡ป", + ESP: "๐Ÿ‡ช๐Ÿ‡ธ", + EST: "๐Ÿ‡ช๐Ÿ‡ช", + ETH: "๐Ÿ‡ช๐Ÿ‡น", + FIJ: "๐Ÿ‡ซ๐Ÿ‡ฏ", + FIN: "๐Ÿ‡ซ๐Ÿ‡ฎ", + FRA: "๐Ÿ‡ซ๐Ÿ‡ท", + FSM: "๐Ÿ‡ซ๐Ÿ‡ฒ", + GAB: "๐Ÿ‡ฌ๐Ÿ‡ฆ", + GAM: "๐Ÿ‡ฌ๐Ÿ‡ฒ", + GBR: "๐Ÿ‡ฌ๐Ÿ‡ง", + GBS: "๐Ÿ‡ฌ๐Ÿ‡ผ", + GEO: "๐Ÿ‡ฌ๐Ÿ‡ช", + GEQ: "๐Ÿ‡ฌ๐Ÿ‡ถ", + GER: "๐Ÿ‡ฉ๐Ÿ‡ช", + GHA: "๐Ÿ‡ฌ๐Ÿ‡ญ", + GRE: "๐Ÿ‡ฌ๐Ÿ‡ท", + GRN: "๐Ÿ‡ฌ๐Ÿ‡ฉ", + GUA: "๐Ÿ‡ฌ๐Ÿ‡น", + GUI: "๐Ÿ‡ฌ๐Ÿ‡ณ", + GUM: "๐Ÿ‡ฌ๐Ÿ‡บ", + GUY: "๐Ÿ‡ฌ๐Ÿ‡พ", + HAI: "๐Ÿ‡ญ๐Ÿ‡น", + HKG: "๐Ÿ‡ญ๐Ÿ‡ฐ", + HON: "๐Ÿ‡ญ๐Ÿ‡ณ", + HUN: "๐Ÿ‡ญ๐Ÿ‡บ", + INA: "๐Ÿ‡ฎ๐Ÿ‡ฉ", + IND: "๐Ÿ‡ฎ๐Ÿ‡ณ", + IRI: "๐Ÿ‡ฎ๐Ÿ‡ท", + IRL: "๐Ÿ‡ฎ๐Ÿ‡ช", + IRQ: "๐Ÿ‡ฎ๐Ÿ‡ถ", + ISL: "๐Ÿ‡ฎ๐Ÿ‡ธ", + ISR: "๐Ÿ‡ฎ๐Ÿ‡ฑ", + ISV: "๐Ÿ‡ป๐Ÿ‡ฎ", + ITA: "๐Ÿ‡ฎ๐Ÿ‡น", + IVB: "๐Ÿ‡ป๐Ÿ‡ฌ", + JAM: "๐Ÿ‡ฏ๐Ÿ‡ฒ", + JOR: "๐Ÿ‡ฏ๐Ÿ‡ด", + JPN: "๐Ÿ‡ฏ๐Ÿ‡ต", + KAZ: "๐Ÿ‡ฐ๐Ÿ‡ฟ", + KEN: "๐Ÿ‡ฐ๐Ÿ‡ช", + KGZ: "๐Ÿ‡ฐ๐Ÿ‡ฌ", + KIR: "๐Ÿ‡ฐ๐Ÿ‡ฎ", + KOR: "๐Ÿ‡ฐ๐Ÿ‡ท", + KOS: "๐Ÿ‡ฝ๐Ÿ‡ฐ", + KSA: "๐Ÿ‡ธ๐Ÿ‡ฆ", + KUW: "๐Ÿ‡ฐ๐Ÿ‡ผ", + LAO: "๐Ÿ‡ฑ๐Ÿ‡ฆ", + LAT: "๐Ÿ‡ฑ๐Ÿ‡ป", + LBA: "๐Ÿ‡ฑ๐Ÿ‡พ", + LBN: "๐Ÿ‡ฑ๐Ÿ‡ง", + LBR: "๐Ÿ‡ฑ๐Ÿ‡ท", + LCA: "๐Ÿ‡ฑ๐Ÿ‡จ", + LES: "๐Ÿ‡ฑ๐Ÿ‡ธ", + LIE: "๐Ÿ‡ฑ๐Ÿ‡ฎ", + LTU: "๐Ÿ‡ฑ๐Ÿ‡น", + LUX: "๐Ÿ‡ฑ๐Ÿ‡บ", + MAD: "๐Ÿ‡ฒ๐Ÿ‡ฌ", + MAR: "๐Ÿ‡ฒ๐Ÿ‡ฆ", + MAS: "๐Ÿ‡ฒ๐Ÿ‡พ", + MAW: "๐Ÿ‡ฒ๐Ÿ‡ผ", + MDA: "๐Ÿ‡ฒ๐Ÿ‡ฉ", + MDV: "๐Ÿ‡ฒ๐Ÿ‡ป", + MEX: "๐Ÿ‡ฒ๐Ÿ‡ฝ", + MGL: "๐Ÿ‡ฒ๐Ÿ‡ณ", + MHL: "๐Ÿ‡ฒ๐Ÿ‡ญ", + MKD: "๐Ÿ‡ฒ๐Ÿ‡ฐ", + MLI: "๐Ÿ‡ฒ๐Ÿ‡ฑ", + MLT: "๐Ÿ‡ฒ๐Ÿ‡น", + MNE: "๐Ÿ‡ฒ๐Ÿ‡ช", + MON: "๐Ÿ‡ฒ๐Ÿ‡จ", + MOZ: "๐Ÿ‡ฒ๐Ÿ‡ฟ", + MRI: "๐Ÿ‡ฒ๐Ÿ‡บ", + MTN: "๐Ÿ‡ฒ๐Ÿ‡ท", + MYA: "๐Ÿ‡ฒ๐Ÿ‡ฒ", + NAM: "๐Ÿ‡ณ๐Ÿ‡ฆ", + NCA: "๐Ÿ‡ณ๐Ÿ‡ฎ", + NED: "๐Ÿ‡ณ๐Ÿ‡ฑ", + NEP: "๐Ÿ‡ณ๐Ÿ‡ต", + NGR: "๐Ÿ‡ณ๐Ÿ‡ฌ", + NIG: "๐Ÿ‡ณ๐Ÿ‡ช", + NOR: "๐Ÿ‡ณ๐Ÿ‡ด", + NRU: "๐Ÿ‡ณ๐Ÿ‡ท", + NZL: "๐Ÿ‡ณ๐Ÿ‡ฟ", + OMA: "๐Ÿ‡ด๐Ÿ‡ฒ", + PAK: "๐Ÿ‡ต๐Ÿ‡ฐ", + PAN: "๐Ÿ‡ต๐Ÿ‡ฆ", + PAR: "๐Ÿ‡ต๐Ÿ‡พ", + PER: "๐Ÿ‡ต๐Ÿ‡ช", + PHI: "๐Ÿ‡ต๐Ÿ‡ญ", + PLE: "๐Ÿ‡ต๐Ÿ‡ธ", + PLW: "๐Ÿ‡ต๐Ÿ‡ผ", + PNG: "๐Ÿ‡ต๐Ÿ‡ฌ", + POL: "๐Ÿ‡ต๐Ÿ‡ฑ", + POR: "๐Ÿ‡ต๐Ÿ‡น", + PRK: "๐Ÿ‡ฐ๐Ÿ‡ต", + PUR: "๐Ÿ‡ต๐Ÿ‡ท", + QAT: "๐Ÿ‡ถ๐Ÿ‡ฆ", + ROU: "๐Ÿ‡ท๐Ÿ‡ด", + RSA: "๐Ÿ‡ฟ๐Ÿ‡ฆ", + RWA: "๐Ÿ‡ท๐Ÿ‡ผ", + SAM: "๐Ÿ‡ผ๐Ÿ‡ธ", + SEN: "๐Ÿ‡ธ๐Ÿ‡ณ", + SEY: "๐Ÿ‡ธ๐Ÿ‡จ", + SGP: "๐Ÿ‡ธ๐Ÿ‡ฌ", + SKN: "๐Ÿ‡ฐ๐Ÿ‡ณ", + SLE: "๐Ÿ‡ธ๐Ÿ‡ฑ", + SLO: "๐Ÿ‡ธ๐Ÿ‡ฎ", + SMR: "๐Ÿ‡ธ๐Ÿ‡ฒ", + SOL: "๐Ÿ‡ธ๐Ÿ‡ง", + SOM: "๐Ÿ‡ธ๐Ÿ‡ด", + SRB: "๐Ÿ‡ท๐Ÿ‡ธ", + SRI: "๐Ÿ‡ฑ๐Ÿ‡ฐ", + SSD: "๐Ÿ‡ธ๐Ÿ‡ธ", + STP: "๐Ÿ‡ธ๐Ÿ‡น", + SUD: "๐Ÿ‡ธ๐Ÿ‡ฉ", + SUI: "๐Ÿ‡จ๐Ÿ‡ญ", + SUR: "๐Ÿ‡ธ๐Ÿ‡ท", + SVK: "๐Ÿ‡ธ๐Ÿ‡ฐ", + SWE: "๐Ÿ‡ธ๐Ÿ‡ช", + SWZ: "๐Ÿ‡ธ๐Ÿ‡ฟ", + SYR: "๐Ÿ‡ธ๐Ÿ‡พ", + TAN: "๐Ÿ‡น๐Ÿ‡ฟ", + TGA: "๐Ÿ‡น๐Ÿ‡ด", + THA: "๐Ÿ‡น๐Ÿ‡ญ", + TJK: "๐Ÿ‡น๐Ÿ‡ฏ", + TKM: "๐Ÿ‡น๐Ÿ‡ฒ", + TLS: "๐Ÿ‡น๐Ÿ‡ฑ", + TOG: "๐Ÿ‡น๐Ÿ‡ฌ", + TPE: "๐Ÿ‡น๐Ÿ‡ผ", + TTO: "๐Ÿ‡น๐Ÿ‡น", + TUN: "๐Ÿ‡น๐Ÿ‡ณ", + TUR: "๐Ÿ‡น๐Ÿ‡ท", + TUV: "๐Ÿ‡น๐Ÿ‡ป", + UAE: "๐Ÿ‡ฆ๐Ÿ‡ช", + UGA: "๐Ÿ‡บ๐Ÿ‡ฌ", + UKR: "๐Ÿ‡บ๐Ÿ‡ฆ", + URU: "๐Ÿ‡บ๐Ÿ‡พ", + USA: "๐Ÿ‡บ๐Ÿ‡ธ", + UZB: "๐Ÿ‡บ๐Ÿ‡ฟ", + VAN: "๐Ÿ‡ป๐Ÿ‡บ", + VEN: "๐Ÿ‡ป๐Ÿ‡ช", + VIE: "๐Ÿ‡ป๐Ÿ‡ณ", + VIN: "๐Ÿ‡ป๐Ÿ‡จ", + YEM: "๐Ÿ‡พ๐Ÿ‡ช", + ZAM: "๐Ÿ‡ฟ๐Ÿ‡ฒ", + ZIM: "๐Ÿ‡ฟ๐Ÿ‡ผ", +}; + +export const getFlag = (nocKey: string): string => { + return flags[nocKey.toUpperCase()] || "๐Ÿณ๏ธ"; +} diff --git a/scraper/package.json b/scraper/package.json index 1b714d65d..2835cfb33 100644 --- a/scraper/package.json +++ b/scraper/package.json @@ -7,7 +7,9 @@ "type": "commonjs", "main": "index.js", "scripts": { - "dev": "DEBUG=olympics-calendar* nodemon index.ts" + "start": "find ./cache/**/*.cached -mmin +10 -exec rm -f {} \\; | DEBUG=olympics-calendar* ts-node index.ts", + "dev": "DEBUG=olympics-calendar* nodemon index.ts", + "lint": "eslint . --ext .ts" }, "dependencies": { "debug": "^4.4.3", diff --git a/scraper/types.d.ts b/scraper/types.d.ts new file mode 100644 index 000000000..c432b4e70 --- /dev/null +++ b/scraper/types.d.ts @@ -0,0 +1,44 @@ + +export interface MultilingualString { + [key: string]: string; +} + +export interface Language { + code: string; + name: string; +} + +export interface Sport { + key: string; + name: MultilingualString; + order: number; +} + +export interface Team { + key: string; + name: MultilingualString; +} + +export interface Match { + team1: Team; + team2: Team; +} + +export interface Event { + key: string; + start: string; + end: string; + sport: string; + isTraining: boolean; + medal: '0' | '1' | '3'; + name: MultilingualString; + location: MultilingualString; + match?: Match; +} + +export interface Calendar { + languages: Language[]; + sports: Sport[]; + events: Event[]; + nocs: Team[]; +} diff --git a/test.html b/test.html new file mode 100644 index 000000000..df5e1f966 --- /dev/null +++ b/test.html @@ -0,0 +1,215 @@ + + + + + Add to Calendar - All Sports Calendar + + + + +
+
Add โ€œAll Sports Calendarโ€
+
+ Subscribe to keep all sports events up to date in your favorite calendar app. +
+ +
+ + + + + Google Calendar + + + + + + Outlook.com + + + + + + Office 365 + + + + + + Apple Calendar + + + + + + Yahoo Calendar + + + + + + Download .ics + +
+ + +
+ + +