fix: fixing hero gap and enhancing cron

This commit is contained in:
dvelo 2024-09-07 09:17:09 -05:00
parent d541c0b6ed
commit 2effdf4c47
11 changed files with 458 additions and 26 deletions

1
cron/.gitignore vendored Normal file

@ -0,0 +1 @@
dist/

17
cron/Dockerfile Executable file

@ -0,0 +1,17 @@
FROM node:20-alpine
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
# This ensures that while in production, the .env file gets read from the current directory instead of the
# previous directory.
ENV MHC_DOCKER=true
WORKDIR /home/node/app
COPY package.json ./
COPY dist/index.js ./
COPY .env.local ./
USER node
RUN npm install
CMD [ "node", "index.js" ]

@ -8,20 +8,20 @@ When running Inngest, on Vercel's servers, when doing the `/servers` Minehut API
## How do you run this? ## How do you run this?
Make sure you have a MongoDB database set-up and ready with a file **in the previous directory (..)** named `.env.local` with the key `MONGO_DB` as the database URL. You can also just set an environment variable.
Simply run the following to test:
```
If you're on a Unix based machine, just type the following:
```bash
# Make sure you already cloned the repo and are in the /cron directory.
# This project uses NPM instead of Yarn for the website
npm install npm install
npm run dev
crontab -e
``` ```
and in `vi` go into insert mode (type `i`) and type the following: and to deploy using Docker:
``` ```
*/30 * * * * cd "<INSERT_REPO_DIR_HERE>/cron/" && npm start npm run build
docker build -t mhsf-dbref .
# run the container
docker run --name mhsf-dbref <name for container>
``` ```

12
cron/config.yaml Executable file

@ -0,0 +1,12 @@
# This is configuration a Home Assistant addon.
name: "DB refresh"
description: "MHSF Cron DB Refresh"
version: "1.0.0"
slug: "mhsf_db_ref"
init: true
arch:
- aarch64
- amd64
- armhf
- armv7
- i386

@ -4,11 +4,13 @@
"description": "In version 1.0, MHSF moved from using Inngest to collect statistics to a self-hosted `crontab` Node.js script.", "description": "In version 1.0, MHSF moved from using Inngest to collect statistics to a self-hosted `crontab` Node.js script.",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"start": "tsc --build && node ./dist/index.js" "dev": "npx tsx src/index.ts",
"build": "npx tsc -p ./tsconfig.json"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"arguments-parser": "^3.2.1",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"mongodb": "^6.8.0" "mongodb": "^6.8.0"

@ -7,23 +7,55 @@ console.log(chalk.yellow(chalk.bold("MHSF crontab scripts")));
console.log(chalk.yellow(chalk.bold("by dvelo - licensed under MIT license"))); console.log(chalk.yellow(chalk.bold("by dvelo - licensed under MIT license")));
console.log(); console.log();
import { MongoClient } from "mongodb"; import { MongoClient, WithId } from "mongodb";
import { config } from "dotenv"; import { config } from "dotenv";
import {
OnlineServer,
OnlineServerExtended,
ServerResponse,
} from "./types/mh-server.js";
import { CronJob } from "cron";
import { Achievement } from "./types/achievement.js";
// set-up config // set-up config
config({ path: "../.env.local" }); config({
path: process.env.MHC_DOCKER != "true" ? "../.env.local" : "./.env.local",
const mongo = new MongoClient(process.env.MONGO_DB as string);
main().catch((e) => {
console.log(chalk.red("[CRON] " + ERROR + " Error while running: "));
console.error(e);
}); });
let mongo = new MongoClient(process.env.MONGO_DB as string);
const SUCCESS = chalk.green("SUCCESS"); const SUCCESS = chalk.green("SUCCESS");
const ERROR = chalk.red("ERROR"); const ERROR = chalk.red("ERROR");
const WARN = chalk.red("WARN"); const WARN = chalk.red("WARN");
const INFO = chalk.blueBright("INFO"); const INFO = chalk.blueBright("INFO");
console.log(INFO, "Starting cron job #1");
CronJob.from({
cronTime: "*/30 * * * *",
onTick: function () {
periodicCronJob().catch((e) => {
console.log(chalk.red("[CRON] " + ERROR + " Error while running: "));
console.error(e);
});
},
start: true,
timeZone: "America/Los_Angeles",
});
console.log(INFO, "Starting cron job #2");
CronJob.from({
cronTime: "0 */12 * * *",
onTick: function () {
achievementTask().catch((e) => {
console.log(chalk.red("[CRON] " + ERROR + " Error while running: "));
console.error(e);
});
},
start: true,
timeZone: "America/Los_Angeles",
});
/** /**
* Main function that runs the script. * Main function that runs the script.
* *
@ -32,8 +64,7 @@ const INFO = chalk.blueBright("INFO");
* Then, it iterates over each server and inserts the player count, server name, and date into the "history" collection. * Then, it iterates over each server and inserts the player count, server name, and date into the "history" collection.
* If an error occurs, it logs the error and closes the MongoDB connection. * If an error occurs, it logs the error and closes the MongoDB connection.
*/ */
async function main() { async function periodicCronJob() {
await mongo.connect();
try { try {
// No more mumbo jumbo // No more mumbo jumbo
const mh = await ( const mh = await (
@ -70,7 +101,7 @@ async function main() {
let y = 0; let y = 0;
mh.servers.forEach(async (server: any, i: number) => { mh.servers.forEach(async (server: OnlineServer, i: number) => {
const serverFavoritesObject = await meta.findOne({ const serverFavoritesObject = await meta.findOne({
server: server.name, server: server.name,
}); });
@ -115,9 +146,142 @@ async function main() {
} }
}); });
} catch (e) { } catch (e) {
await mongo.close();
console.log("[CRON] " + ERROR + " Error while parsing JSON:", e); console.log("[CRON] " + ERROR + " Error while parsing JSON:", e);
return; return;
} }
} }
async function achievementTask() {
try {
const mh = await (
await fetch("https://api.minehut.com/servers", {
headers: {
accept: "application/json",
"accept-language": Math.random().toString(),
priority: "u=1, i",
"sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="126"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"macOS"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site",
"Content-Type": "application/json",
Referer: "http://localhost:3000/",
"Referrer-Policy": "strict-origin-when-cross-origin",
},
body: null,
method: "GET",
})
).json();
const meta = mongo.db("mhsf").collection("meta");
const achievements = mongo.db("mhsf").collection("achievements");
console.log("adding achievements");
mh.servers.forEach(async (server: OnlineServer, i: number) => {
const serverFavoritesObject = await meta.findOne({
server: server.name,
});
let favorites = 0;
if (serverFavoritesObject != undefined)
favorites = serverFavoritesObject.favorites;
const srvExt: OnlineServerExtended = {
...server,
favorites,
position: { joins: i + 1 },
};
const prevAchievements = ((await achievements.findOne({
name: server.name,
})) || { _id: "", name: server.name, achievements: [] }) as WithId<{
name: string;
achievements: Achievement[];
}>;
const achievementsTsk = await achievementEngine(
srvExt,
prevAchievements.achievements
);
await achievements.insertOne({
name: server.name,
achievements: achievementsTsk,
});
});
} catch (e) {
console.log("[CRON] " + ERROR + " Error while parsing JSON:", e);
return;
}
}
async function achievementEngine(
server: OnlineServerExtended,
currentAchievements: Achievement[]
): Promise<Achievement[]> {
const achievements: Array<Achievement> = [];
if (
server.favorites >= 1000 &&
currentAchievements.find((c) => c.type == "has1kFavorites") === undefined
) {
achievements.push({
type: "has1kFavorites",
date: new Date().toISOString(),
});
}
if (
server.favorites >= 100000 &&
currentAchievements.find((c) => c.type == "has100kFavorites") === undefined
) {
achievements.push({
type: "has100kFavorites",
date: new Date().toISOString(),
});
}
if (
server.playerData.playerCount >= 2 &&
currentAchievements.find((c) => c.type == "has1kTotalJoins") === undefined
) {
const v: { server: ServerResponse } = await (
await fetch(
"https://api.minehut.com/server/" + server.name + "?byName=true"
)
).json();
if (v.server.joins >= 1000) {
achievements.push({
type: "has1kTotalJoins",
date: new Date().toISOString(),
});
}
}
if (
server.playerData.playerCount >= 10 &&
currentAchievements.find((c) => c.type == "has100kTotalJoins") === undefined
) {
const v: { server: ServerResponse } = await (
await fetch(
"https://api.minehut.com/server/" + server.name + "?byName=true"
)
).json();
if (v.server.joins >= 100000) {
achievements.push({
type: "has100kTotalJoins",
date: new Date().toISOString(),
});
}
}
if (server.position.joins === 1) {
achievements.push({
type: "mostJoined",
date: new Date().toISOString(),
});
}
return achievements;
}

@ -0,0 +1,10 @@
export type Achievement = {
type:
| "mostJoined"
| "has1kFavorites"
| "has1kTotalJoins"
| "has100kFavorites"
| "has100kTotalJoins";
/** The ISO time of the gaining of the achievement. */
date: string;
};

@ -0,0 +1,89 @@
export interface ServerResponse {
__unix?: string;
deletion?: Deletion;
_id: string;
categories: string[];
inheritedCategories: any[];
purchased_icons: string[];
backup_slots: number;
suspended: boolean;
server_version_type: string;
proxy: boolean;
connectedServers: any[];
motd: string;
visibility: boolean;
server_plan: string;
storage_node: string;
default_banner_image: string;
default_banner_tint: string;
owner: string;
name: string;
name_lower: string;
creation: number;
platform: string;
credits_per_day: number;
in_game: boolean;
using_cosmetics: boolean;
__v: number;
port: number;
last_online: number;
joins: number;
active_icon: string;
expired: boolean;
icon: string;
online: boolean;
maxPlayers: number;
playerCount: number;
rawPlan: string;
activeServerPlan: string;
}
export interface Deletion {
started: boolean;
started_at: number;
reason: string;
completed: boolean;
completed_at: number;
storage_completed: boolean;
storage_completed_at: number;
}
export interface OnlineServer {
staticInfo: StaticInfo;
maxPlayers: number;
name: string;
motd: string;
icon: string;
playerData: PlayerData;
connectable: boolean;
visibility: boolean;
allCategories: string[];
usingCosmetics: boolean;
author?: string;
authorRank: string;
}
export interface StaticInfo {
_id: string;
serverPlan: string;
serviceStartDate: number;
platform: string;
planMaxPlayers: number;
planRam: number;
alwaysOnline: boolean;
rawPlan: string;
connectedServers: any[];
}
export interface PlayerData {
playerCount: number;
timeNoPlayers: number;
}
export interface OnlineServerExtended extends OnlineServer {
favorites: number;
position: {
joins: number;
favorites?: number;
};
}

@ -4,8 +4,8 @@
"noEmitOnError": true, "noEmitOnError": true,
"removeComments": false, "removeComments": false,
"sourceMap": true, "sourceMap": true,
"target": "ES6", "moduleResolution": "Node",
"module": "NodeNext", "target": "es6",
"outDir": "dist" "outDir": "dist"
}, },
"include": ["src/**/*"] "include": ["src/**/*"]

137
cron/yarn.lock Normal file

@ -0,0 +1,137 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@mongodb-js/saslprep@^1.1.5":
version "1.1.9"
resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz#e974bab8eca9faa88677d4ea4da8d09a52069004"
integrity sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==
dependencies:
sparse-bitfield "^3.0.3"
"@types/webidl-conversions@*":
version "7.0.3"
resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz#1306dbfa53768bcbcfc95a1c8cde367975581859"
integrity sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==
"@types/whatwg-url@^11.0.2":
version "11.0.5"
resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-11.0.5.tgz#aaa2546e60f0c99209ca13360c32c78caf2c409f"
integrity sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==
dependencies:
"@types/webidl-conversions" "*"
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
arguments-parser@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/arguments-parser/-/arguments-parser-3.2.1.tgz#c816e1db2d4e3395ea1bfd1ae65b3fe9303c8a7b"
integrity sha512-+wWBD6LKShmR8nszO6kgc+Z4pOqbADh4RH1gWWFD9Vuot1ljOf2Tc0FCn1mE6Yc/eT0GkxGn5OjV0gRxpCq2BQ==
dependencies:
chalk "4.1.2"
bson@^6.7.0:
version "6.8.0"
resolved "https://registry.yarnpkg.com/bson/-/bson-6.8.0.tgz#5063c41ba2437c2b8ff851b50d9e36cb7aaa7525"
integrity sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==
chalk@4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
dotenv@^16.4.5:
version "16.4.5"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
memory-pager@^1.0.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
mongodb-connection-string-url@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz#c13e6ac284ae401752ebafdb8cd7f16c6723b141"
integrity sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==
dependencies:
"@types/whatwg-url" "^11.0.2"
whatwg-url "^13.0.0"
mongodb@^6.8.0:
version "6.8.1"
resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.8.1.tgz#3f3a663e296446e412e26d8769315e36945a70fe"
integrity sha512-qsS+gl5EJb+VzJqUjXSZ5Y5rbuM/GZlZUEJ2OIVYP10L9rO9DQ0DGp+ceTzsmoADh6QYMWd9MSdG9IxRyYUkEA==
dependencies:
"@mongodb-js/saslprep" "^1.1.5"
bson "^6.7.0"
mongodb-connection-string-url "^3.0.0"
punycode@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
sparse-bitfield@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11"
integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==
dependencies:
memory-pager "^1.0.2"
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
tr46@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469"
integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==
dependencies:
punycode "^2.3.0"
webidl-conversions@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
whatwg-url@^13.0.0:
version "13.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-13.0.0.tgz#b7b536aca48306394a34e44bda8e99f332410f8f"
integrity sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==
dependencies:
tr46 "^4.1.1"
webidl-conversions "^7.0.0"

@ -227,7 +227,7 @@ export default function ServerList() {
<div className="p-0 branding-hero"> <div className="p-0 branding-hero">
<> <>
{(!isSignedIn || hero) && ( {(!isSignedIn || hero) && (
<div className=" py-2 max-lg:h-[370vh] lg:h-[300vh] relative mx-auto mt-20 max-w-7xl px-6 text-center md:px-8 "> <div className=" py-[300px] relative mx-auto mt-20 max-w-7xl px-6 text-center md:px-8 ">
<Particles <Particles
className="absolute inset-0 -z-10 block" className="absolute inset-0 -z-10 block"
quantity={100} quantity={100}