mirror of
https://github.com/ssyyhhrr/stewardle.git
synced 2025-11-08 02:23:46 -05:00
506 lines
15 KiB
JavaScript
506 lines
15 KiB
JavaScript
const axios = require("axios")
|
|
const _ = require("lodash")
|
|
const fs = require("fs")
|
|
const schedule = require("node-schedule")
|
|
const express = require("express")
|
|
const favicon = require("serve-favicon")
|
|
const morgan = require("morgan")
|
|
const dayjs = require("dayjs")
|
|
const process = require("process")
|
|
|
|
const driversPath = "./assets/drivers.json"
|
|
const statsPath = "./assets/stats.json"
|
|
|
|
const flag = {
|
|
"British": "gb",
|
|
"Spanish": "es",
|
|
"Polish": "pl",
|
|
"Japanese": "jp",
|
|
"Mexican": "mx",
|
|
"Australian": "au",
|
|
"Russian": "ru",
|
|
"Dutch": "nl",
|
|
"Belgian": "be",
|
|
"Canadian": "ca",
|
|
"New Zealander": "nz",
|
|
"Thai": "th",
|
|
"Finnish": "fi",
|
|
"Brazilian": "br",
|
|
"German": "de",
|
|
"French": "fr",
|
|
"Venezuelan": "ve",
|
|
"Danish": "dk",
|
|
"Swedish": "se",
|
|
"American": "us",
|
|
"Indonesian": "id",
|
|
"Italian": "it",
|
|
"Monegasque": "mc",
|
|
"Chinese": "cn",
|
|
"Argentine": "ar",
|
|
"Andorran": "ad",
|
|
"Emirati": "ae",
|
|
"Afghan": "af",
|
|
"Antiguan": "ag",
|
|
"Anguillan": "ai",
|
|
"Albanian": "al",
|
|
"Armenian": "am",
|
|
"Angolan": "ao",
|
|
"Azerbaijani": "az",
|
|
"Austrian": "at",
|
|
"Bahamian": "bs",
|
|
"Bangladeshi": "bd",
|
|
"Barbadian": "bb",
|
|
"Beninese": "bj",
|
|
"Bhutanese": "bt",
|
|
"Botswanan": "bw",
|
|
"Bulgarian": "bg",
|
|
"Burkinabé": "bf",
|
|
"Burundian": "bi",
|
|
"Cambodian": "kh",
|
|
"Cameroonian": "cm",
|
|
"Cape Verdean": "cv",
|
|
"Chadian": "td",
|
|
"Chilean": "cl",
|
|
"Colombian": "co",
|
|
"Costa Rican": "cr",
|
|
"Croatian": "hr",
|
|
"Cuban": "cu",
|
|
"Cypriot": "cy",
|
|
"Czech": "cz",
|
|
"Dominican": "do",
|
|
"Ecuadorian": "ec",
|
|
"Egyptian": "eg",
|
|
"Salvadoran": "sv",
|
|
"Equatorial Guinean": "gq",
|
|
"Eritrean": "er",
|
|
"Estonian": "ee",
|
|
"Ethiopian": "et",
|
|
"Fijian": "fj",
|
|
"Gabonese": "ga",
|
|
"Gambian": "gm",
|
|
"Georgian": "ge",
|
|
"Ghanaian": "gh",
|
|
"Gibraltarian": "gi",
|
|
"Greek": "gr",
|
|
"Grenadian": "gd",
|
|
"Guatemalan": "gt",
|
|
"Guinean": "gn",
|
|
"Guyanese": "gy",
|
|
"Haitian": "ht",
|
|
"Honduran": "hn",
|
|
"Hong Konger": "hk",
|
|
"Hungarian": "hu",
|
|
"Icelandic": "is",
|
|
"Indian": "in",
|
|
"Iranian": "ir",
|
|
"Iraqi": "iq",
|
|
"Israeli": "il",
|
|
"Jamaican": "jm",
|
|
"Jordanian": "jo",
|
|
"Kazakh": "kz",
|
|
"Kenyan": "ke",
|
|
"North Korean": "kp",
|
|
"South Korean": "kr",
|
|
"Kuwaiti": "kw",
|
|
"Kyrgyz": "kg",
|
|
"Laotian": "la",
|
|
"Latvian": "lv",
|
|
"Lebanese": "lb",
|
|
"Liberian": "lr",
|
|
"Libyan": "ly",
|
|
"Liechtensteiner": "li",
|
|
"Lithuanian": "lt",
|
|
"Luxembourgish": "lu",
|
|
"Macedonian": "mk",
|
|
"Malagasy": "mg",
|
|
"Malawian": "mw",
|
|
"Malaysian": "my",
|
|
"Malian": "ml",
|
|
"Maltese": "mt",
|
|
"Marshallese": "mh",
|
|
"Mauritanian": "mr",
|
|
"Mauritian": "mu",
|
|
"Micronesian": "fm",
|
|
"Moldovan": "md",
|
|
"Mongolian": "mn",
|
|
"Montenegrin": "me",
|
|
"Moroccan": "ma",
|
|
"Mozambican": "mz",
|
|
"Namibian": "na",
|
|
"Nepalese": "np",
|
|
"Nicaraguan": "ni",
|
|
"Nigerien": "ne",
|
|
"Nigerian": "ng",
|
|
"Norwegian": "no",
|
|
"Omani": "om",
|
|
"Pakistani": "pk",
|
|
"Palauan": "pw",
|
|
"Panamanian": "pa",
|
|
"Papua New Guinean": "pg",
|
|
"Paraguayan": "py",
|
|
"Peruvian": "pe",
|
|
"Filipino": "ph",
|
|
"Portuguese": "pt",
|
|
"Qatari": "qa",
|
|
"Romanian": "ro",
|
|
"Rwandan": "rw",
|
|
"Saint Lucian": "lc",
|
|
"Saint Vincentian": "vc",
|
|
"Samoan": "ws",
|
|
"San Marinese": "sm",
|
|
"Saudi": "sa",
|
|
"Senegalese": "sn",
|
|
"Serbian": "rs",
|
|
"Seychellois": "sc",
|
|
"Sierra Leonean": "sl",
|
|
"Singaporean": "sg",
|
|
"Slovak": "sk",
|
|
"Slovenian": "si",
|
|
"Solomon Islander": "sb",
|
|
"Somali": "so",
|
|
"South African": "za",
|
|
"South Sudanese": "ss",
|
|
"Sri Lankan": "lk",
|
|
"Sudanese": "sd",
|
|
"Surinamese": "sr",
|
|
"Syrian": "sy",
|
|
"Taiwanese": "tw",
|
|
"Tajikistani": "tj",
|
|
"Tanzanian": "tz",
|
|
"Togolese": "tg",
|
|
"Tongan": "to",
|
|
"Trinidadian": "tt",
|
|
"Tunisian": "tn",
|
|
"Turkish": "tr",
|
|
"Turkmen": "tm",
|
|
"Tuvaluan": "tv",
|
|
"Ugandan": "ug",
|
|
"Ukrainian": "ua",
|
|
"Uruguayan": "uy",
|
|
"Uzbekistani": "uz",
|
|
"Vanuatuan": "vu",
|
|
"Vietnamese": "vn",
|
|
"Yemeni": "ye",
|
|
"Zambian": "zm",
|
|
"Zimbabwean": "zw"
|
|
}
|
|
|
|
function team(teamName, year) {
|
|
switch(teamName) {
|
|
case "McLaren":
|
|
return "mclaren"
|
|
break
|
|
case "Alpine F1 Team":
|
|
return "alpine"
|
|
break
|
|
case "Mercedes":
|
|
return "mercedes"
|
|
break
|
|
case "Sauber":
|
|
if (year < 2024) {
|
|
return "sauber"
|
|
} else {
|
|
return "kick"
|
|
}
|
|
break
|
|
case "Haas F1 Team":
|
|
return "haas"
|
|
break
|
|
case "Lotus F1":
|
|
return "lotus"
|
|
break
|
|
case "Marussia":
|
|
return "marussia"
|
|
break
|
|
case "Manor Marussia":
|
|
return "marussia"
|
|
break
|
|
case "Renault":
|
|
return "renault"
|
|
break
|
|
case "Alfa Romeo":
|
|
return "alfa"
|
|
break
|
|
case "Williams":
|
|
return "williams"
|
|
break
|
|
case "Aston Martin":
|
|
return "aston"
|
|
break
|
|
case "Caterham":
|
|
return "caterham"
|
|
break
|
|
case "Red Bull":
|
|
return "red"
|
|
break
|
|
case "Toro Rosso":
|
|
return "toro"
|
|
break
|
|
case "AlphaTauri":
|
|
return "alpha"
|
|
break
|
|
case "Ferrari":
|
|
return "ferrari"
|
|
break
|
|
case "RB F1 Team":
|
|
return "rb"
|
|
break
|
|
}
|
|
}
|
|
|
|
let stats = {
|
|
"visits": 0,
|
|
"guesses": 0
|
|
}
|
|
|
|
let drivers = {}
|
|
let driver
|
|
|
|
let year = new Date().getFullYear()
|
|
|
|
function sleep(ms) {
|
|
return new Promise((resolve) => {
|
|
setTimeout(resolve, ms);
|
|
});
|
|
}
|
|
|
|
axios.get("https://api.jolpi.ca/ergast/f1/1950/driverStandings.json?limit=1000").then(async () => {
|
|
await updateDrivers()
|
|
}).catch(() => {
|
|
console.log("API is unreachable! Not updating drivers...")
|
|
if (fs.existsSync(driversPath)) {
|
|
let data = fs.readFileSync(driversPath)
|
|
drivers = JSON.parse(data)
|
|
} else {
|
|
throw "Jolpica API is unreachable and the drivers.json cache has not been built. Please try again when the Jolpica API is online."
|
|
}
|
|
}).catch(err => {
|
|
console.log(err)
|
|
return process.exit(1)
|
|
}).then(() => {
|
|
dotd()
|
|
server()
|
|
})
|
|
|
|
schedule.scheduleJob("59 23 * * *", async () => {
|
|
axios.get("https://api.jolpi.ca/ergast/f1/1950/driverStandings.json?limit=1000").then(async () => {
|
|
await updateDrivers()
|
|
}).catch(() => {
|
|
console.log("API is unreachable! Not updating drivers...")
|
|
drivers = JSON.parse(fs.readFileSync(driversPath))
|
|
})
|
|
})
|
|
|
|
schedule.scheduleJob("0 0 * * *", () => {
|
|
dotd()
|
|
})
|
|
|
|
schedule.scheduleJob("* * * * *", () => {
|
|
processStats()
|
|
})
|
|
|
|
async function updateDrivers() {
|
|
let newDrivers = {}
|
|
for (let i = 2000; i <= year; i++) {
|
|
console.log(`Scraping F1 ${i} Season...`)
|
|
try {
|
|
await axios.get(`https://api.jolpi.ca/ergast/f1/${i}/driverStandings.json?limit=1000`).then(res => {
|
|
res.data.MRData.StandingsTable.StandingsLists[0].DriverStandings.forEach(driver => {
|
|
if (driver.Driver.driverId in newDrivers) {
|
|
newDrivers[driver.Driver.driverId].wins += parseInt(driver.wins)
|
|
if (newDrivers[driver.Driver.driverId].constructors[newDrivers[driver.Driver.driverId].constructors.length - 1] !== team(driver.Constructors[0].name, i) || newDrivers[driver.Driver.driverId].constructors.length === 0) newDrivers[driver.Driver.driverId].constructors.push(team(driver.Constructors[0].name, i))
|
|
} else if (driver.Driver.hasOwnProperty("permanentNumber")) {
|
|
newDrivers[driver.Driver.driverId] = {
|
|
"firstName": driver.Driver.givenName,
|
|
"lastName": driver.Driver.familyName,
|
|
"code": driver.Driver.code,
|
|
"nationality": flag[driver.Driver.nationality.trim()],
|
|
"constructors": [team(driver.Constructors[0].name, i)],
|
|
"permanentNumber": driver.Driver.permanentNumber,
|
|
"age": getAge(driver.Driver.dateOfBirth),
|
|
"firstYear": i,
|
|
"wins": parseInt(driver.wins),
|
|
}
|
|
}
|
|
})
|
|
})
|
|
} catch (e) {
|
|
if (i !== year) throw ""
|
|
}
|
|
await sleep(300)
|
|
}
|
|
drivers = newDrivers
|
|
|
|
if (fs.existsSync("assets/drivers.json")) {
|
|
console.log("Deleting drivers.json...")
|
|
fs.unlinkSync("assets/drivers.json")
|
|
}
|
|
|
|
console.log(`Writing ${_.keys(drivers).length} Drivers to drivers.json...`)
|
|
fs.writeFileSync("assets/drivers.json", JSON.stringify(drivers), (error) => {
|
|
if (error) throw error
|
|
})
|
|
}
|
|
|
|
function processStats(dotd = false) {
|
|
const date = dayjs().format("YYYY-MM-DD");
|
|
|
|
let statsFile = {};
|
|
if (fs.existsSync(statsPath)) {
|
|
statsFile = JSON.parse(fs.readFileSync(statsPath));
|
|
}
|
|
|
|
if (statsFile.hasOwnProperty(date)) {
|
|
if (statsFile[date].visits > stats.visits) {
|
|
statsFile[date].visits += stats.visits
|
|
stats.visits = statsFile[date].visits
|
|
}
|
|
else statsFile[date].visits = stats.visits
|
|
if (statsFile[date].guesses > stats.guesses) {
|
|
statsFile[date].guesses += stats.guesses
|
|
stats.guesses = statsFile[date].guesses
|
|
}
|
|
else statsFile[date].guesses = stats.guesses
|
|
} else if (dotd) {
|
|
statsFile[date] = {
|
|
"driver": stats.driver
|
|
}
|
|
} else return
|
|
|
|
fs.writeFileSync(statsPath, JSON.stringify(statsFile));
|
|
}
|
|
|
|
function dotd() {
|
|
console.log("Selecting Driver of the Day...")
|
|
let date = dayjs().format("YYYY-MM-DD")
|
|
let pastDrivers = []
|
|
let pastDates = []
|
|
if (fs.existsSync(statsPath)) {
|
|
let statsFile = JSON.parse(fs.readFileSync(statsPath))
|
|
pastDates = Object.keys(statsFile)
|
|
pastDrivers = Object.values(statsFile).map(x => x.driver).filter((x) => { return typeof x === "string"})
|
|
}
|
|
if (pastDrivers.length > 0 && pastDates.length > 0 && pastDates[pastDates.length - 1] === date) {
|
|
driver = pastDrivers[pastDrivers.length - 1]
|
|
} else {
|
|
let newDriver = getRandomProperty(drivers)
|
|
while (pastDrivers.slice(-14).includes(newDriver)) {
|
|
newDriver = getRandomProperty(drivers)
|
|
}
|
|
driver = newDriver
|
|
}
|
|
stats = {
|
|
"visits": 0,
|
|
"guesses": 0,
|
|
"driver": driver
|
|
}
|
|
processStats(true)
|
|
console.log(`Driver of the Day is ${driver}!`)
|
|
console.log(drivers[driver])
|
|
}
|
|
|
|
function getRandomProperty(obj) {
|
|
let keys = Object.keys(obj)
|
|
return keys[Math.floor(Math.random() * keys.length)]
|
|
}
|
|
|
|
function getAge(dateString) {
|
|
var today = new Date();
|
|
var birthDate = new Date(dateString);
|
|
var age = today.getFullYear() - birthDate.getFullYear();
|
|
var m = today.getMonth() - birthDate.getMonth();
|
|
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
|
|
age--;
|
|
}
|
|
return age;
|
|
}
|
|
|
|
function server() {
|
|
var app = express()
|
|
|
|
app.enable("trust proxy")
|
|
|
|
app.use(express.urlencoded({ extended: true }))
|
|
app.use(express.static("assets", {
|
|
setHeaders: function(res, path, stat) {
|
|
// Set cache control headers
|
|
res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
res.set('Pragma', 'no-cache');
|
|
res.set('Expires', '0');
|
|
}
|
|
}))
|
|
app.use(favicon('assets/favicon.ico'))
|
|
app.use(morgan("combined"))
|
|
|
|
app.set("views", "views")
|
|
app.set("view engine", "ejs")
|
|
|
|
app.get("/", (req, res) => {
|
|
res.render("index")
|
|
stats.visits++
|
|
})
|
|
|
|
app.get("/winner", (req, res) => {
|
|
if (req.headers.authorization !== "Bearer kRyX3RYMRY$&yEc8") return res.end()
|
|
res.json({
|
|
"winner": drivers[driver].firstName + " " + drivers[driver].lastName,
|
|
})
|
|
})
|
|
|
|
app.get("/driver", (req, res) => {
|
|
if (!req.query.driver) return res.statusSend(400)
|
|
let search = false
|
|
let response = []
|
|
for (let query in drivers) {
|
|
if (req.query.driver === drivers[query].firstName + " " + drivers[query].lastName) {
|
|
search = true
|
|
let guess = drivers[query]
|
|
let actual = drivers[driver]
|
|
|
|
// nationality
|
|
if (guess.nationality === actual.nationality) response.push(1) // correct nationality
|
|
else response.push(3) // incorrect nationality
|
|
|
|
// constructors
|
|
if (guess.constructors[guess.constructors.length - 1] === actual.constructors[actual.constructors.length - 1]) response.push(1) // correct constructor
|
|
else if (actual.constructors.includes(guess.constructors[guess.constructors.length - 1])) response.push(4) // previous constructor
|
|
else response.push(3) // incorrect constructor
|
|
|
|
// permanent number
|
|
if (parseInt(guess.permanentNumber) > parseInt(actual.permanentNumber)) response.push(0) // go down
|
|
else if (parseInt(guess.permanentNumber) === parseInt(actual.permanentNumber)) response.push(1) // stay the same
|
|
else if (parseInt(guess.permanentNumber) < parseInt(actual.permanentNumber)) response.push(2) // go up
|
|
|
|
// age
|
|
if (parseInt(guess.age) > parseInt(actual.age)) response.push(0) // go down
|
|
else if (parseInt(guess.age) === parseInt(actual.age)) response.push(1) // stay the same
|
|
else if (parseInt(guess.age) < parseInt(actual.age)) response.push(2) // go up
|
|
|
|
// first year
|
|
if (parseInt(guess.firstYear) > parseInt(actual.firstYear)) response.push(0) // go down
|
|
else if (parseInt(guess.firstYear) === parseInt(actual.firstYear)) response.push(1) // stay the same
|
|
else if (parseInt(guess.firstYear) < parseInt(actual.firstYear)) response.push(2) // go up
|
|
|
|
// wins
|
|
if (parseInt(guess.wins) > parseInt(actual.wins)) response.push(0) // go down
|
|
else if (parseInt(guess.wins) === parseInt(actual.wins)) response.push(1) // stay the same
|
|
else if (parseInt(guess.wins) < parseInt(actual.wins)) response.push(2) // go up
|
|
}
|
|
}
|
|
if (!search) return res.sendStatus(400)
|
|
res.json({
|
|
"nationality": response[0],
|
|
"constructor": response[1],
|
|
"permanentNumber": response[2],
|
|
"age": response[3],
|
|
"firstYear": response[4],
|
|
"wins": response[5]
|
|
})
|
|
stats.guesses++
|
|
})
|
|
|
|
let port = 3000
|
|
app.listen(port, () => {
|
|
console.log(`Listening on port ${port}!`)
|
|
})
|
|
} |