From 9be70112dcce07b4598132b8a746d938b6688de0 Mon Sep 17 00:00:00 2001 From: dvelo <52332868+DeveloLongScript@users.noreply.github.com> Date: Thu, 22 Aug 2024 23:44:00 -0500 Subject: [PATCH] 1.0! --- .gitignore | 4 + cron/README.md | 25 +++ cron/package-lock.json | 175 +++++++++++++++++++ cron/package.json | 17 ++ cron/src/index.ts | 123 +++++++++++++ cron/tsconfig.json | 12 ++ package.json | 1 + src/app/account/settings/page.tsx | 10 +- src/app/globals.css | 6 + src/app/layout.tsx | 1 + src/components/ServerCard.tsx | 80 +++++++-- src/components/ServerList.tsx | 20 ++- src/components/clerk/LoggedInPopover.tsx | 2 +- src/components/clerk/SignInPopoverButton.tsx | 2 +- src/components/misc/InfoClaim.tsx | 2 +- src/components/misc/TextCopyComp.tsx | 4 +- src/components/ui/hover-card.tsx | 29 +++ src/lib/useClipboard.ts | 21 +++ src/version.tsx | 56 +++++- yarn.lock | 21 ++- 20 files changed, 580 insertions(+), 31 deletions(-) create mode 100644 cron/README.md create mode 100644 cron/package-lock.json create mode 100644 cron/package.json create mode 100644 cron/src/index.ts create mode 100644 cron/tsconfig.json create mode 100644 src/components/ui/hover-card.tsx create mode 100644 src/lib/useClipboard.ts diff --git a/.gitignore b/.gitignore index 3e5ffd4..83fd634 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ .yarn/install-state.gz .turbo +# cron +/cron/dist +/cron/node_modules + # testing /coverage diff --git a/cron/README.md b/cron/README.md new file mode 100644 index 0000000..aaed27d --- /dev/null +++ b/cron/README.md @@ -0,0 +1,25 @@ +# MHSF Cron Tasks + +In version 1.0, MHSF moved from using Inngest to collect statistics to a self-hosted `crontab` Node.js script. + +## Why the move? + +When running Inngest, on Vercel's servers, when doing the `/servers` Minehut API endpoint to grab the currently online servers, a Cloudflare pop-up appeared. This made it so the JSON data expected, was blocked. This appeared to only run on Vercel's servers, and the only real solution (without spending a lot of money) was to run a minimal script every 30 minutes to grab the server data. + +## How do you run this? + +If you're on a Unix based machine, just type the following: + +``` +# 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 + +crontab -e +``` + +and in `vi` go into insert mode (type `i`) and type the following: + +``` +*/30 * * * * cd "/cron/" && npm start +``` diff --git a/cron/package-lock.json b/cron/package-lock.json new file mode 100644 index 0000000..91cb478 --- /dev/null +++ b/cron/package-lock.json @@ -0,0 +1,175 @@ +{ + "name": "cron", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cron", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^5.3.0", + "dotenv": "^16.4.5", + "mongodb": "^6.8.0" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz", + "integrity": "sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/bson": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", + "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "node_modules/mongodb": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz", + "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + } + } +} diff --git a/cron/package.json b/cron/package.json new file mode 100644 index 0000000..c1f1d7e --- /dev/null +++ b/cron/package.json @@ -0,0 +1,17 @@ +{ + "name": "cron", + "version": "1.0.0", + "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", + "scripts": { + "start": "tsc --build && node ./dist/index.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "chalk": "^5.3.0", + "dotenv": "^16.4.5", + "mongodb": "^6.8.0" + }, + "type": "module" +} diff --git a/cron/src/index.ts b/cron/src/index.ts new file mode 100644 index 0000000..faaaf24 --- /dev/null +++ b/cron/src/index.ts @@ -0,0 +1,123 @@ +// MHSF crontab scripts +// by dvelo - licensed under MIT license + +import chalk from "chalk"; + +console.log(chalk.yellow(chalk.bold("MHSF crontab scripts"))); +console.log(chalk.yellow(chalk.bold("by dvelo - licensed under MIT license"))); +console.log(); + +import { MongoClient } from "mongodb"; +import { config } from "dotenv"; + +// set-up config +config({ path: "../.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); +}); +const SUCCESS = chalk.green("SUCCESS"); +const ERROR = chalk.red("ERROR"); +const WARN = chalk.red("WARN"); +const INFO = chalk.blueBright("INFO"); + +/** + * Main function that runs the script. + * + * @remarks + * Connects to the MongoDB instance, fetches the server data from the Minehut API, and inserts the total player and server count into the "mh" 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. + */ +async function main() { + await mongo.connect(); + try { + // No more mumbo jumbo + 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(); + console.log("[CRON] " + SUCCESS + " Found", mh.servers.length, "servers"); + + const mha = mongo.db("mhsf").collection("mh"); + const meta = mongo.db("mhsf").collection("meta"); + const dbl = mongo.db("mhsf").collection("history"); + + await mha.insertOne({ + total_players: mh.total_players, + total_servers: mh.total_servers, + date: new Date(), + }); + + let y = 0; + + mh.servers.forEach(async (server: any, i: number) => { + const serverFavoritesObject = await meta.findOne({ + server: server.name, + }); + let favorites = 0; + if (serverFavoritesObject != undefined) + favorites = serverFavoritesObject.favorites; + + await dbl.insertOne({ + player_count: server.playerData.playerCount, + favorites, + server: server.name, + date: new Date(), + }); + + process.stdout.clearLine(0); + process.stdout.cursorTo(0); + process.stdout.write( + "[CRON] " + + INFO + + " Remaining servers: " + + (y + "/" + mh.servers.length) + ); + y++; + if (y == mh.servers.length) { + process.stdout.clearLine(0); + process.stdout.cursorTo(0); + process.stdout.write( + "[CRON] " + SUCCESS + " Finished! Closing MongoDB connection." + ); + + // Close connection + await mongo + .close() + .catch((e) => + console.log( + "[CRON] " + WARN + " Error while closing MongoDB connection:", + e + ) + ); + + return; + } + }); + } catch (e) { + await mongo.close(); + console.log("[CRON] " + ERROR + " Error while parsing JSON:", e); + + return; + } +} diff --git a/cron/tsconfig.json b/cron/tsconfig.json new file mode 100644 index 0000000..ac1b69f --- /dev/null +++ b/cron/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "noImplicitAny": false, + "noEmitOnError": true, + "removeComments": false, + "sourceMap": true, + "target": "ES6", + "module": "NodeNext", + "outDir": "dist" + }, + "include": ["src/**/*"] +} diff --git a/package.json b/package.json index d8bdfcb..56dd0cc 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@babel/parser": "^7.24.7", "@clerk/nextjs": "^5.1.3", "@monaco-editor/react": "^4.6.0", + "@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-menubar": "^1.1.1", "@unocss/eslint-plugin": "^0.61.5", diff --git a/src/app/account/settings/page.tsx b/src/app/account/settings/page.tsx index 2e2d243..ec9392d 100644 --- a/src/app/account/settings/page.tsx +++ b/src/app/account/settings/page.tsx @@ -60,7 +60,15 @@ export default function Settings() {
Link Account
-

Link a Minecraft account to customize a server you own.

+

+ Link a Minecraft account to customize a server you own. +
{" "} + {user?.publicMetadata.player != undefined && linked && ( + <> + Currently linked to {user?.publicMetadata.player as string} + + )} +

diff --git a/src/app/globals.css b/src/app/globals.css index 18aefe9..2095da8 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -100,6 +100,12 @@ backdrop-filter: blur(8px) !important; } +body { + scrollbar-width: 10px; + scrollbar-color: #888; + scrollbar-gutter: transparent; +} + /** Cool scrollbar */ ::-webkit-scrollbar { width: 10px; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ff73674..7da5052 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -21,6 +21,7 @@ import { CommandBarer } from "@/components/CommandBar"; import ThemedToaster from "@/components/misc/ThemedToaster"; import UnofficalDialog from "@/components/misc/UnofficalDialog"; import ClientFadeIn from "@/components/ClientFadeIn"; +import toast from "react-hot-toast"; const inter = interFont({ variable: "--font-inter", subsets: ["latin"] }); export default async function RootLayout({ diff --git a/src/components/ServerCard.tsx b/src/components/ServerCard.tsx index 7f044da..6a04802 100644 --- a/src/components/ServerCard.tsx +++ b/src/components/ServerCard.tsx @@ -15,7 +15,14 @@ import { } from "./ui/card"; import IconDisplay from "./IconDisplay"; import { TagShower } from "./ServerList"; -import { Copy, EllipsisVertical, Layers, Star } from "lucide-react"; +import { + ArrowRight, + ChartArea, + Copy, + EllipsisVertical, + Layers, + Star, +} from "lucide-react"; import { Button } from "./ui/button"; import { Drawer, @@ -33,9 +40,16 @@ import { useState } from "react"; import { favoriteServer, isFavorited } from "@/lib/api"; import { useUser } from "@clerk/nextjs"; import { useTheme } from "next-themes"; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hover-card"; +import useClipboard from "@/lib/useClipboard"; export default function ServerCard({ b, motd, mini, favs }: any) { const router = useRouter(); + const clipboard = useClipboard(); const [favoriteStar, setFavoriteStar] = useState(false); const [favoriteLoading, setFavoriteLoading] = useState(true); const { isSignedIn } = useUser(); @@ -61,7 +75,51 @@ export default function ServerCard({ b, motd, mini, favs }: any) { > - {b.name}{" "} + + + + + + + + + +
+
+

{b.name}

+

+ {motd && ( + + )} +

+
+ + + Open Server Page + +
+
+ + + Running on{" "} + {b.staticInfo.serverPlan == undefined + ? "Free Plan" + : b.staticInfo.serverPlan} + +
+
+
+
+
+
+ +
  • • New hover card on server title hover
  • +
  • • Moving to self-hosted cron jobs
  • +
  • • Fixing some mobile issues
  • + +
    +
    + +
    diff --git a/yarn.lock b/yarn.lock index cbe2ed0..4f669b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -740,6 +740,21 @@ "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-use-callback-ref" "1.1.0" +"@radix-ui/react-hover-card@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.1.tgz#2982a5a91c7ae5a98e0cacd845fbdfbfdcdab355" + integrity sha512-IwzAOP97hQpDADYVKrEEHUH/b2LA+9MgB0LgdmnbFO2u/3M5hmEofjjr2M6CyzUblaAqJdFm6B7oFtU72DPXrA== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-popper" "1.2.0" + "@radix-ui/react-portal" "1.1.1" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-icons@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69" @@ -3028,9 +3043,9 @@ foreground-child@^3.1.0: signal-exit "^4.0.1" framer-motion@^11.3.8: - version "11.3.8" - resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.3.8.tgz#682df8cbac6a9667b48af427e5a8bdaea7203713" - integrity sha512-1D+RDTsIp4Rz2dq/oToqSEc9idEQwgBRQyBq4rGpFba+0Z+GCbj9z1s0+ikFbanWe3YJ0SqkNlDe08GcpFGj5A== + version "11.3.29" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.3.29.tgz#5ec10a350b89789d43ea7d9c6bde45b28470f196" + integrity sha512-uyDuUOeOElJEA3kbkbyoTNEf75Jih1EUg0ouLKYMlGDdt/LaJPmO+FyOGAGxM2HwKhHcAoKFNveR5A8peb7yhw== dependencies: tslib "^2.4.0"