mirror of
https://github.com/clarencechaan/ufc-cal.git
synced 2026-10-19 18:10:58 -07:00
Merge branch 'main' into ics
This commit is contained in:
commit
d8965af0de
14 files changed
+916
-321
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[Subscribe to this calendar to keep track of UFC events](https://clarencechaan.github.io/ufc-cal/)
|
[Subscribe to this calendar to keep track of UFC events](https://clarencechaan.github.io/ufc-cal/)
|
||||||
|
|
||||||
Or subscribe manually from your calendar app:
|
Or subscribe manually from your calendar app using this URL:
|
||||||
|
|
||||||
`webcal://raw.githubusercontent.com/clarencechaan/ufc-cal/ics/UFC.ics`
|
`webcal://raw.githubusercontent.com/clarencechaan/ufc-cal/ics/UFC.ics`
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ The three biggest points that this aims to address, which I found lacking in oth
|
|||||||
|
|
||||||
## Example event
|
## Example event
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Info for nerds
|
## Info for nerds
|
||||||
|
|
||||||
@@ -32,4 +32,4 @@ The three biggest points that this aims to address, which I found lacking in oth
|
|||||||
|
|
||||||
**To run locally:**
|
**To run locally:**
|
||||||
|
|
||||||
- Clone this repo, run the command `npm start`, and out spits your `UFC.ics` file with all the relevant UFC events
|
- Clone this repo, run the commands `npm install`, then `npm start`, and out spits your `UFC.ics` file with all the relevant UFC events
|
||||||
+4
-4
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>UFC Calendar</title>
|
<title>UFC Calendar</title>
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="./src/assets/favicon.ico" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
@@ -69,18 +69,18 @@
|
|||||||
<a
|
<a
|
||||||
class="sub-link"
|
class="sub-link"
|
||||||
href="webcal://raw.githubusercontent.com/clarencechaan/ufc-cal/ics/UFC.ics"
|
href="webcal://raw.githubusercontent.com/clarencechaan/ufc-cal/ics/UFC.ics"
|
||||||
><img src="apple.svg" alt="apple" />
|
><img src="./src/assets/apple.svg" alt="apple" />
|
||||||
<div>Apple</div>
|
<div>Apple</div>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="sub-link"
|
class="sub-link"
|
||||||
href="https://calendar.google.com/calendar/u/0/r?cid=webcal://raw.githubusercontent.com/clarencechaan/ufc-cal/ics/UFC.ics"
|
href="https://calendar.google.com/calendar/u/0/r?cid=webcal://raw.githubusercontent.com/clarencechaan/ufc-cal/ics/UFC.ics"
|
||||||
><img src="google.svg" alt="google" />
|
><img src="./src/assets/google.svg" alt="google" />
|
||||||
<div>Google</div>
|
<div>Google</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="sub-manual">
|
<div class="sub-manual">
|
||||||
<p>Or subscribe manually from your calendar app:</p>
|
<p>Or subscribe manually from your calendar app using this URL:</p>
|
||||||
<div class="code-wrapper">
|
<div class="code-wrapper">
|
||||||
<code
|
<code
|
||||||
>webcal://raw.githubusercontent.com/clarencechaan/ufc-cal/ics/UFC.ics</code
|
>webcal://raw.githubusercontent.com/clarencechaan/ufc-cal/ics/UFC.ics</code
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
import { getAllDetailedEvents } from "./scrape.js";
|
|
||||||
import { writeFileSync } from "fs";
|
|
||||||
import { createEvents } from "ics";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the details of recent and near-future UFC events, then creates an
|
|
||||||
* ICS file named "UFC.ics" in the current directory containing these events
|
|
||||||
*
|
|
||||||
* @returns {Promise<void>} - A Promise that resolves when the ICS file is created
|
|
||||||
*/
|
|
||||||
async function createICS() {
|
|
||||||
try {
|
|
||||||
let events = await getAllDetailedEvents();
|
|
||||||
if (!events.length) throw new Error("No events retrieved");
|
|
||||||
|
|
||||||
// Get current date and time to communicate to the user how up-to-date
|
|
||||||
// the event details are
|
|
||||||
let currentDateTime = new Date();
|
|
||||||
let dateTimestr = currentDateTime.toLocaleString("en-US", {
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "numeric",
|
|
||||||
timeZone: "America/Toronto",
|
|
||||||
timeZoneName: "short",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert event details in the format in accordance with the ICS generator
|
|
||||||
events = events.map((event) => {
|
|
||||||
let date = new Date(parseInt(event.date) * 1000);
|
|
||||||
let start = [
|
|
||||||
date.getFullYear(),
|
|
||||||
date.getMonth() + 1,
|
|
||||||
date.getDate(),
|
|
||||||
date.getHours(),
|
|
||||||
date.getMinutes(),
|
|
||||||
];
|
|
||||||
let duration = { hours: 3 };
|
|
||||||
let title = event.name;
|
|
||||||
let description = "";
|
|
||||||
|
|
||||||
// Distinguish between main card and prelims if this information has been
|
|
||||||
// announced by the UFC, otherwise list all fights without categorizing
|
|
||||||
if (event.fightCard.length)
|
|
||||||
description = `${event.fightCard.join("\n")}\n`;
|
|
||||||
if (event.mainCard.length)
|
|
||||||
description += `Main Card\n--------------------\n${event.mainCard.join(
|
|
||||||
"\n"
|
|
||||||
)}\n`;
|
|
||||||
if (event.prelims.length) {
|
|
||||||
description += "\nPrelims";
|
|
||||||
if (event.prelimsTime) {
|
|
||||||
let prelimsTime = new Date(parseInt(event.prelimsTime) * 1000);
|
|
||||||
let hoursAgo = (date - prelimsTime) / 1000 / 60 / 60;
|
|
||||||
if (hoursAgo > 0) description += ` (${hoursAgo} hrs before Main)`;
|
|
||||||
}
|
|
||||||
description += `\n--------------------\n${event.prelims.join("\n")}\n`;
|
|
||||||
}
|
|
||||||
if (event.earlyPrelims.length) {
|
|
||||||
description += "\nEarly Prelims";
|
|
||||||
if (event.earlyPrelimsTime) {
|
|
||||||
let earlyPrelimsTime = new Date(
|
|
||||||
parseInt(event.earlyPrelimsTime) * 1000
|
|
||||||
);
|
|
||||||
let hoursAgo = (date - earlyPrelimsTime) / 1000 / 60 / 60;
|
|
||||||
if (hoursAgo > 0) description += ` (${hoursAgo} hrs before Main)`;
|
|
||||||
}
|
|
||||||
description += `\n--------------------\n${event.earlyPrelims.join(
|
|
||||||
"\n"
|
|
||||||
)}\n`;
|
|
||||||
}
|
|
||||||
description += `\n${event.url}`;
|
|
||||||
description += `\n\nAccurate as of ${dateTimestr}`;
|
|
||||||
|
|
||||||
let location = event.location;
|
|
||||||
let uid = event.url;
|
|
||||||
let calName = "UFC";
|
|
||||||
return {
|
|
||||||
start,
|
|
||||||
duration,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
location,
|
|
||||||
uid,
|
|
||||||
calName,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("\nDetailed events:");
|
|
||||||
console.log(events);
|
|
||||||
|
|
||||||
// Create UFC.ics
|
|
||||||
writeFileSync("UFC.ics", createEvents(events).value);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createICS();
|
|
||||||
Generated
+541
-1
@@ -9,9 +9,430 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.12.0",
|
||||||
"html-entities": "^2.4.0",
|
"html-entities": "^2.4.0",
|
||||||
"ics": "^3.4.0",
|
"ics": "^3.4.0",
|
||||||
"node-html-parser": "^6.1.9"
|
"node-html-parser": "^6.1.9",
|
||||||
|
"tsx": "^4.19.2",
|
||||||
|
"typescript": "^5.7.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "22.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz",
|
||||||
|
"integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.20.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
@@ -115,6 +536,74 @@
|
|||||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
||||||
|
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.23.1",
|
||||||
|
"@esbuild/android-arm": "0.23.1",
|
||||||
|
"@esbuild/android-arm64": "0.23.1",
|
||||||
|
"@esbuild/android-x64": "0.23.1",
|
||||||
|
"@esbuild/darwin-arm64": "0.23.1",
|
||||||
|
"@esbuild/darwin-x64": "0.23.1",
|
||||||
|
"@esbuild/freebsd-arm64": "0.23.1",
|
||||||
|
"@esbuild/freebsd-x64": "0.23.1",
|
||||||
|
"@esbuild/linux-arm": "0.23.1",
|
||||||
|
"@esbuild/linux-arm64": "0.23.1",
|
||||||
|
"@esbuild/linux-ia32": "0.23.1",
|
||||||
|
"@esbuild/linux-loong64": "0.23.1",
|
||||||
|
"@esbuild/linux-mips64el": "0.23.1",
|
||||||
|
"@esbuild/linux-ppc64": "0.23.1",
|
||||||
|
"@esbuild/linux-riscv64": "0.23.1",
|
||||||
|
"@esbuild/linux-s390x": "0.23.1",
|
||||||
|
"@esbuild/linux-x64": "0.23.1",
|
||||||
|
"@esbuild/netbsd-x64": "0.23.1",
|
||||||
|
"@esbuild/openbsd-arm64": "0.23.1",
|
||||||
|
"@esbuild/openbsd-x64": "0.23.1",
|
||||||
|
"@esbuild/sunos-x64": "0.23.1",
|
||||||
|
"@esbuild/win32-arm64": "0.23.1",
|
||||||
|
"@esbuild/win32-ia32": "0.23.1",
|
||||||
|
"@esbuild/win32-x64": "0.23.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-tsconfig": {
|
||||||
|
"version": "4.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
|
||||||
|
"integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"resolve-pkg-maps": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/he": {
|
"node_modules/he": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
@@ -196,6 +685,16 @@
|
|||||||
"integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==",
|
"integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/resolve-pkg-maps": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tiny-case": {
|
"node_modules/tiny-case": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
|
||||||
@@ -208,6 +707,26 @@
|
|||||||
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
|
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/tsx": {
|
||||||
|
"version": "4.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
|
||||||
|
"integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild": "~0.23.0",
|
||||||
|
"get-tsconfig": "^4.7.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tsx": "dist/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/type-fest": {
|
"node_modules/type-fest": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
|
||||||
@@ -220,6 +739,27 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||||
|
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||||
|
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/yup": {
|
"node_modules/yup": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/yup/-/yup-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/yup/-/yup-1.2.0.tgz",
|
||||||
|
|||||||
+6
-3
@@ -2,17 +2,20 @@
|
|||||||
"name": "ufc-cal",
|
"name": "ufc-cal",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "node index.js"
|
"start": "npx tsx src/index.ts"
|
||||||
},
|
},
|
||||||
"author": "Clarence Chan",
|
"author": "Clarence Chan",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.12.0",
|
||||||
"html-entities": "^2.4.0",
|
"html-entities": "^2.4.0",
|
||||||
"ics": "^3.4.0",
|
"ics": "^3.4.0",
|
||||||
"node-html-parser": "^6.1.9"
|
"node-html-parser": "^6.1.9",
|
||||||
|
"tsx": "^4.19.2",
|
||||||
|
"typescript": "^5.7.3"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
import { parse } from "node-html-parser";
|
|
||||||
import { decode } from "html-entities";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of URL strings of recent and near-future UFC events
|
|
||||||
*
|
|
||||||
* @returns {Promise<Array<string>>} - A Promise that resolves with the array
|
|
||||||
* of URL strings of UFC events
|
|
||||||
*/
|
|
||||||
async function getEventLinks() {
|
|
||||||
// Get first two pages of event links
|
|
||||||
let urls = [
|
|
||||||
"https://www.ufc.com/events?page=0",
|
|
||||||
"https://www.ufc.com/events?page=1",
|
|
||||||
];
|
|
||||||
|
|
||||||
async function getEventLinksFromUrl(url) {
|
|
||||||
const response = await fetch(url);
|
|
||||||
const text = await response.text();
|
|
||||||
const root = parse(text);
|
|
||||||
|
|
||||||
// Extract the URLs of the relevant events from the DOM object
|
|
||||||
let links = root.querySelectorAll(".c-card-event--result__headline");
|
|
||||||
links = links.map(
|
|
||||||
(html) => `https://www.ufc.com${html.firstChild.getAttribute("href")}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
urls = urls.map(getEventLinksFromUrl);
|
|
||||||
|
|
||||||
let links = await Promise.all(urls);
|
|
||||||
links = links.flat();
|
|
||||||
|
|
||||||
console.log("\nEvent links found:");
|
|
||||||
console.log(links);
|
|
||||||
return links;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the fight card details of a UFC event given its URL
|
|
||||||
*
|
|
||||||
* @param {string} url - A URL string of a UFC event
|
|
||||||
* @returns {Promise<object>} - A Promise that resolves with the fight card details of
|
|
||||||
* the UFC event
|
|
||||||
*/
|
|
||||||
async function getDetailsFromEventLink(url) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(url);
|
|
||||||
const text = await response.text();
|
|
||||||
const root = parse(text);
|
|
||||||
|
|
||||||
// Extract basic details of the event from the DOM object
|
|
||||||
let headlinePrefix = root
|
|
||||||
.querySelector(".c-hero__headline-prefix")
|
|
||||||
.innerText.trim();
|
|
||||||
let headline = root
|
|
||||||
.querySelector(".c-hero__headline")
|
|
||||||
.innerText.replace(/\s\s+/g, " ")
|
|
||||||
.trim();
|
|
||||||
let name = `${headlinePrefix}: ${headline}`;
|
|
||||||
let date = root
|
|
||||||
.querySelector(".c-hero__headline-suffix")
|
|
||||||
.getAttribute("data-timestamp");
|
|
||||||
let prelimsTime = root
|
|
||||||
.querySelector("#prelims-card .c-event-fight-card-broadcaster__time")
|
|
||||||
?.getAttribute("data-timestamp");
|
|
||||||
let earlyPrelimsTime = root
|
|
||||||
.querySelector("#early-prelims .c-event-fight-card-broadcaster__time")
|
|
||||||
?.getAttribute("data-timestamp");
|
|
||||||
|
|
||||||
let location =
|
|
||||||
root
|
|
||||||
.querySelector(".field--name-venue")
|
|
||||||
?.innerText.replaceAll("\n", "")
|
|
||||||
.replaceAll(" ", " ") || "";
|
|
||||||
let fightCard = [];
|
|
||||||
let mainCard = root.querySelectorAll("#main-card .l-listing__item");
|
|
||||||
let prelims = [];
|
|
||||||
let earlyPrelims = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to return the string of a fight given its DOM list item
|
|
||||||
*
|
|
||||||
* @param {object} li - The DOM list item of a fight
|
|
||||||
* @returns {string} - The string of a fight
|
|
||||||
*/
|
|
||||||
function convertLiToStr(li) {
|
|
||||||
let bout = li.querySelector(".c-listing-fight__class-text")?.innerText;
|
|
||||||
|
|
||||||
// Return only fight names without bout weightclass if formatting for
|
|
||||||
// an event is broken on the UFC event page
|
|
||||||
if (!bout)
|
|
||||||
return (
|
|
||||||
"• " +
|
|
||||||
li
|
|
||||||
.querySelector(".field--name-node-title")
|
|
||||||
.innerText.trim()
|
|
||||||
.replace(" vs ", " vs. ")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Convert weightclass to lbs
|
|
||||||
if (bout.includes("Strawweight")) bout = "115";
|
|
||||||
else if (bout.includes("Flyweight")) bout = "125";
|
|
||||||
else if (bout.includes("Bantamweight")) bout = "135";
|
|
||||||
else if (bout.includes("Featherweight")) bout = "145";
|
|
||||||
else if (bout.includes("Lightweight")) bout = "155";
|
|
||||||
else if (bout.includes("Welterweight")) bout = "170";
|
|
||||||
else if (bout.includes("Middleweight")) bout = "185";
|
|
||||||
else if (bout.includes("Light Heavyweight")) bout = "205";
|
|
||||||
else if (bout.includes("Heavyweight")) bout = "265";
|
|
||||||
else if (bout.includes("Catchweight")) bout = "CW";
|
|
||||||
|
|
||||||
// Extract and format fighter names with weightclass
|
|
||||||
let red = li
|
|
||||||
.querySelector(".c-listing-fight__corner-name--red")
|
|
||||||
.innerText.replaceAll("\n", "")
|
|
||||||
.replace(/\s+/g, " ")
|
|
||||||
.trim();
|
|
||||||
let blue = li
|
|
||||||
.querySelector(".c-listing-fight__corner-name--blue")
|
|
||||||
.innerText.replaceAll("\n", "")
|
|
||||||
.replace(/\s+/g, " ")
|
|
||||||
.trim();
|
|
||||||
let ranks = li.querySelectorAll(
|
|
||||||
".c-listing-fight__ranks-row .js-listing-fight__corner-rank span"
|
|
||||||
);
|
|
||||||
ranks = ranks.map((rank) => rank?.innerText);
|
|
||||||
let redRankStr = ranks[0] ? ` (${ranks[0]})` : "";
|
|
||||||
let blueRankStr = ranks[1] ? ` (${ranks[1]})` : "";
|
|
||||||
let fightStr = `• ${red}${redRankStr} vs. ${blue}${blueRankStr} @${bout}`;
|
|
||||||
|
|
||||||
// Deentitize HTML entities
|
|
||||||
fightStr = decode(fightStr);
|
|
||||||
|
|
||||||
return fightStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("\nGetting details from url:", url);
|
|
||||||
|
|
||||||
// Check if main card & prelims have been announced
|
|
||||||
if (mainCard.length) {
|
|
||||||
// Main card has been announced, extract prelims
|
|
||||||
prelims = root.querySelectorAll("#prelims-card .l-listing__item");
|
|
||||||
earlyPrelims = root.querySelectorAll("#early-prelims .l-listing__item");
|
|
||||||
|
|
||||||
console.log("Main card length:", mainCard.length);
|
|
||||||
console.log("Prelims length:", prelims.length);
|
|
||||||
|
|
||||||
mainCard = mainCard.map(convertLiToStr);
|
|
||||||
prelims = prelims.map(convertLiToStr);
|
|
||||||
earlyPrelims = earlyPrelims.map(convertLiToStr);
|
|
||||||
} else {
|
|
||||||
// Main card has not been announced, extract entire fight card
|
|
||||||
fightCard = root.querySelectorAll(
|
|
||||||
".l-listing__group--bordered .l-listing__item"
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("Fight card length:", fightCard.length);
|
|
||||||
|
|
||||||
fightCard = fightCard.map(convertLiToStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deentitize HTML entities
|
|
||||||
[name, location] = [decode(name), decode(location)];
|
|
||||||
|
|
||||||
let details = {
|
|
||||||
name,
|
|
||||||
url,
|
|
||||||
date,
|
|
||||||
location,
|
|
||||||
fightCard,
|
|
||||||
mainCard,
|
|
||||||
prelims,
|
|
||||||
earlyPrelims,
|
|
||||||
prelimsTime,
|
|
||||||
earlyPrelimsTime,
|
|
||||||
};
|
|
||||||
return details;
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to retrieve event: ${url}\n${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of details of recent and near-future UFC events
|
|
||||||
*
|
|
||||||
* @returns {Promise<Array<object>>} - A Promise that resolves with the array
|
|
||||||
* of details of recent and near-future UFC events
|
|
||||||
*/
|
|
||||||
async function getAllDetailedEvents() {
|
|
||||||
let events = [];
|
|
||||||
try {
|
|
||||||
// Get the URLs of recent and near-future UFC events
|
|
||||||
events = await getEventLinks();
|
|
||||||
|
|
||||||
// Convert each URL to the details of the corresponding UFC event
|
|
||||||
events = events.map(getDetailsFromEventLink);
|
|
||||||
events = await Promise.all(events);
|
|
||||||
return events;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getAllDetailedEvents };
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+100
@@ -0,0 +1,100 @@
|
|||||||
|
import { getAllDetailedEvents } from "./scrape.js";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { createEvents, type DateArray, type EventAttributes } from "ics";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the details of recent and upcoming UFC events, then creates an
|
||||||
|
* ICS file named "UFC.ics" in the current directory containing these events
|
||||||
|
*/
|
||||||
|
async function createICS() {
|
||||||
|
try {
|
||||||
|
const events = await getAllDetailedEvents();
|
||||||
|
if (!events?.length) throw new Error("No events retrieved");
|
||||||
|
|
||||||
|
// Convert event details in the format in accordance with the ICS generator
|
||||||
|
const formattedEvents = events.map(formatEventForCalendar);
|
||||||
|
|
||||||
|
console.log("\nDetailed events:");
|
||||||
|
console.log(formattedEvents);
|
||||||
|
|
||||||
|
// Create UFC.ics
|
||||||
|
const eventsData = createEvents(formattedEvents).value;
|
||||||
|
if (eventsData) fs.writeFileSync("UFC.ics", eventsData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatEventForCalendar(event: UFCEvent): EventAttributes {
|
||||||
|
const date = new Date(parseInt(event.date) * 1000);
|
||||||
|
const start: DateArray = [
|
||||||
|
date.getFullYear(),
|
||||||
|
date.getMonth() + 1,
|
||||||
|
date.getDate(),
|
||||||
|
date.getHours(),
|
||||||
|
date.getMinutes(),
|
||||||
|
];
|
||||||
|
const duration: { hours: number } = { hours: 3 };
|
||||||
|
const title = event.name;
|
||||||
|
let description = "";
|
||||||
|
|
||||||
|
// Distinguish between main card and prelims if this information has been
|
||||||
|
// announced by the UFC, otherwise list all fights without categorizing
|
||||||
|
if (event.fightCard.length) description = `${event.fightCard.join("\n")}\n`;
|
||||||
|
if (event.mainCard.length)
|
||||||
|
description += `Main Card\n--------------------\n${event.mainCard.join(
|
||||||
|
"\n"
|
||||||
|
)}\n`;
|
||||||
|
if (event.prelims.length) {
|
||||||
|
description += "\nPrelims";
|
||||||
|
if (event.prelimsTime) {
|
||||||
|
const prelimsTime = new Date(parseInt(event.prelimsTime) * 1000);
|
||||||
|
const hoursAgo = (+date - +prelimsTime) / 1000 / 60 / 60;
|
||||||
|
if (hoursAgo > 0) description += ` (${hoursAgo} hrs before Main)`;
|
||||||
|
}
|
||||||
|
description += `\n--------------------\n${event.prelims.join("\n")}\n`;
|
||||||
|
}
|
||||||
|
if (event.earlyPrelims.length) {
|
||||||
|
description += "\nEarly Prelims";
|
||||||
|
if (event.earlyPrelimsTime) {
|
||||||
|
const earlyPrelimsTime = new Date(
|
||||||
|
parseInt(event.earlyPrelimsTime) * 1000
|
||||||
|
);
|
||||||
|
const hoursAgo = (+date - +earlyPrelimsTime) / 1000 / 60 / 60;
|
||||||
|
if (hoursAgo > 0) description += ` (${hoursAgo} hrs before Main)`;
|
||||||
|
}
|
||||||
|
description += `\n--------------------\n${event.earlyPrelims.join("\n")}\n`;
|
||||||
|
}
|
||||||
|
if (description.length) description += "\n";
|
||||||
|
description += `${event.url}`;
|
||||||
|
|
||||||
|
// Get current date and time to communicate to the user how up-to-date
|
||||||
|
// the event details are
|
||||||
|
const dateTimestr = new Date().toLocaleString("en-US", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
timeZone: "America/Toronto",
|
||||||
|
timeZoneName: "short",
|
||||||
|
});
|
||||||
|
description += `\n\nAccurate as of ${dateTimestr}`;
|
||||||
|
|
||||||
|
const location = event.location;
|
||||||
|
const uid = event.url.href;
|
||||||
|
const calName = "UFC";
|
||||||
|
|
||||||
|
const calendarEvent = {
|
||||||
|
start,
|
||||||
|
duration,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
location,
|
||||||
|
uid,
|
||||||
|
calName,
|
||||||
|
};
|
||||||
|
|
||||||
|
return calendarEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
createICS();
|
||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
import { parse, HTMLElement } from "node-html-parser";
|
||||||
|
import { decode } from "html-entities";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of URLs of recent and upcoming UFC events
|
||||||
|
*/
|
||||||
|
async function getEventURLs() {
|
||||||
|
// Get first two pages of event urls
|
||||||
|
const pageURLs = [
|
||||||
|
new URL("https://www.ufc.com/events?page=0"),
|
||||||
|
new URL("https://www.ufc.com/events?page=1"),
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const eventURLs = (
|
||||||
|
await Promise.all(pageURLs.map(getEventURLsFromPageURL))
|
||||||
|
).flat();
|
||||||
|
|
||||||
|
console.log("\nEvent URLs found:");
|
||||||
|
console.log(eventURLs);
|
||||||
|
return eventURLs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw new Error("Failed to retrieve event URLs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEventURLsFromPageURL(url: URL) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const text = await response.text();
|
||||||
|
const root = parse(text);
|
||||||
|
|
||||||
|
// Extract the URLs of the relevant events from the HTML element
|
||||||
|
const eventURLElements = root.querySelectorAll(
|
||||||
|
".c-card-event--result__headline"
|
||||||
|
);
|
||||||
|
const eventURLs = eventURLElements.map(
|
||||||
|
(html) =>
|
||||||
|
new URL(
|
||||||
|
`https://www.ufc.com${(html.firstChild as HTMLElement).getAttribute(
|
||||||
|
"href"
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return eventURLs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw new Error("Failed to retrieve event URLs from page URL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the output string of a fight given its HTML element list item
|
||||||
|
*/
|
||||||
|
function convertLiToStr(li: HTMLElement) {
|
||||||
|
const bout = li.querySelector(".c-listing-fight__class-text")?.textContent;
|
||||||
|
|
||||||
|
let fightStr = "";
|
||||||
|
|
||||||
|
// Return only fight names without bout weightclass if formatting for
|
||||||
|
// an event is broken on the UFC event page
|
||||||
|
if (!bout) {
|
||||||
|
const textContent = li
|
||||||
|
.querySelector(".field--name-node-title")
|
||||||
|
?.textContent?.trim()
|
||||||
|
.replace(" vs ", " vs. ");
|
||||||
|
if (!textContent) return "";
|
||||||
|
fightStr += `• ${textContent}`;
|
||||||
|
return fightStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let weightClass = "";
|
||||||
|
|
||||||
|
// Get weightclass in short form
|
||||||
|
if (bout.includes("Strawweight")) weightClass = "115";
|
||||||
|
else if (bout.includes("Flyweight")) weightClass = "125";
|
||||||
|
else if (bout.includes("Bantamweight")) weightClass = "135";
|
||||||
|
else if (bout.includes("Featherweight")) weightClass = "145";
|
||||||
|
else if (bout.includes("Lightweight")) weightClass = "155";
|
||||||
|
else if (bout.includes("Welterweight")) weightClass = "170";
|
||||||
|
else if (bout.includes("Middleweight")) weightClass = "185";
|
||||||
|
else if (bout.includes("Light Heavyweight")) weightClass = "205";
|
||||||
|
else if (bout.includes("Heavyweight")) weightClass = "265";
|
||||||
|
else if (bout.includes("Catchweight")) weightClass = "CW";
|
||||||
|
|
||||||
|
// Extract and format fighter names with weightclass
|
||||||
|
const red = li
|
||||||
|
.querySelector(".c-listing-fight__corner-name--red")
|
||||||
|
?.textContent?.replaceAll("\n", "")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
const blue = li
|
||||||
|
.querySelector(".c-listing-fight__corner-name--blue")
|
||||||
|
?.textContent?.replaceAll("\n", "")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
if (!red || !blue) throw new Error("Failed to retrieve fighter names");
|
||||||
|
|
||||||
|
const ranks = li
|
||||||
|
.querySelectorAll(
|
||||||
|
".c-listing-fight__ranks-row .js-listing-fight__corner-rank span"
|
||||||
|
)
|
||||||
|
.map((rank) => rank?.textContent);
|
||||||
|
|
||||||
|
const redRankStr = ranks[0] ? ` (${ranks[0]})` : "";
|
||||||
|
const blueRankStr = ranks[1] ? ` (${ranks[1]})` : "";
|
||||||
|
fightStr += `• ${red}${redRankStr} vs. ${blue}${blueRankStr} @${weightClass}`;
|
||||||
|
|
||||||
|
// Deentitize HTML entities
|
||||||
|
fightStr = decode(fightStr);
|
||||||
|
|
||||||
|
return fightStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fight card details of a UFC event given its URL
|
||||||
|
*/
|
||||||
|
async function getDetailsFromEventURL(url: URL) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const text = await response.text();
|
||||||
|
const root = parse(text);
|
||||||
|
|
||||||
|
// Extract basic details of the event from the HTML
|
||||||
|
const headlinePrefix = root
|
||||||
|
.querySelector(".c-hero__headline-prefix")
|
||||||
|
?.innerText.trim();
|
||||||
|
const headline = root
|
||||||
|
.querySelector(".c-hero__headline")
|
||||||
|
?.innerText.replace(/\s\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
const date = root
|
||||||
|
.querySelector(".c-hero__headline-suffix")
|
||||||
|
?.getAttribute("data-timestamp");
|
||||||
|
|
||||||
|
const prelimsTime = root
|
||||||
|
.querySelector("#prelims-card .c-event-fight-card-broadcaster__time")
|
||||||
|
?.getAttribute("data-timestamp");
|
||||||
|
const earlyPrelimsTime = root
|
||||||
|
.querySelector("#early-prelims .c-event-fight-card-broadcaster__time")
|
||||||
|
?.getAttribute("data-timestamp");
|
||||||
|
|
||||||
|
let name = `${headlinePrefix}: ${headline}`;
|
||||||
|
let location =
|
||||||
|
root
|
||||||
|
.querySelector(".field--name-venue")
|
||||||
|
?.innerText.replaceAll("\n", "")
|
||||||
|
.replaceAll(" ", " ") || "";
|
||||||
|
let fightCard: string[] = [];
|
||||||
|
let mainCard: string[] = [];
|
||||||
|
let prelims: string[] = [];
|
||||||
|
let earlyPrelims: string[] = [];
|
||||||
|
|
||||||
|
console.log(`\nGetting details from url: ${url.href}`);
|
||||||
|
|
||||||
|
const mainCardElements = root.querySelectorAll(
|
||||||
|
"#main-card .l-listing__item"
|
||||||
|
);
|
||||||
|
// Check if main card and prelims have been announced
|
||||||
|
if (mainCardElements.length) {
|
||||||
|
// Main card has been announced, extract prelims
|
||||||
|
const prelimsElements = root.querySelectorAll(
|
||||||
|
"#prelims-card .l-listing__item"
|
||||||
|
);
|
||||||
|
const earlyPrelimsElements = root.querySelectorAll(
|
||||||
|
"#early-prelims .l-listing__item"
|
||||||
|
);
|
||||||
|
|
||||||
|
mainCard = mainCardElements.map(convertLiToStr);
|
||||||
|
prelims = prelimsElements.map(convertLiToStr);
|
||||||
|
earlyPrelims = earlyPrelimsElements.map(convertLiToStr);
|
||||||
|
|
||||||
|
console.log(`Main card length: ${mainCard.length}`);
|
||||||
|
console.log(`Prelims length: ${prelims.length}`);
|
||||||
|
} else {
|
||||||
|
// Main card has not been announced, extract entire fight card
|
||||||
|
const fightCardElements = root.querySelectorAll(
|
||||||
|
".l-listing__group--bordered .l-listing__item"
|
||||||
|
);
|
||||||
|
|
||||||
|
fightCard = fightCardElements.map(convertLiToStr);
|
||||||
|
|
||||||
|
console.log(`Fight card length: ${fightCard.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deentitize HTML entities
|
||||||
|
[name, location] = [decode(name), decode(location)];
|
||||||
|
|
||||||
|
if (!name || !date) {
|
||||||
|
throw new Error("Failed to retrieve event details");
|
||||||
|
}
|
||||||
|
|
||||||
|
const details: UFCEvent = {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
date,
|
||||||
|
location,
|
||||||
|
fightCard,
|
||||||
|
mainCard,
|
||||||
|
prelims,
|
||||||
|
earlyPrelims,
|
||||||
|
prelimsTime,
|
||||||
|
earlyPrelimsTime,
|
||||||
|
};
|
||||||
|
return details;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw new Error(`Failed to retrieve event: ${url.href}\n${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of details of recent and upcoming UFC events
|
||||||
|
*/
|
||||||
|
async function getAllDetailedEvents() {
|
||||||
|
try {
|
||||||
|
const eventURLs = await getEventURLs();
|
||||||
|
|
||||||
|
// Get UFC events from URLs
|
||||||
|
const detailedEvents = await Promise.all(
|
||||||
|
eventURLs?.map(getDetailsFromEventURL)
|
||||||
|
);
|
||||||
|
return detailedEvents;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw new Error("Failed to retrieve all events");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getAllDetailedEvents };
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
type UFCEvent = {
|
||||||
|
name: string;
|
||||||
|
url: URL;
|
||||||
|
date: string;
|
||||||
|
location: string;
|
||||||
|
fightCard: string[];
|
||||||
|
mainCard: string[];
|
||||||
|
prelims: string[];
|
||||||
|
earlyPrelims: string[];
|
||||||
|
prelimsTime: string | undefined;
|
||||||
|
earlyPrelimsTime: string | undefined;
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"target": "es2022",
|
||||||
|
"allowJs": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"module": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"lib": ["es2022"]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user