From bbec84c7def47d80ad29eb51f2c16b2207eaa6d7 Mon Sep 17 00:00:00 2001 From: dvelo <52332868+DeveloLongScript@users.noreply.github.com> Date: Sun, 9 Mar 2025 20:17:47 -0500 Subject: [PATCH] feat(www): added statistical banner --- apps/www/src/app/api/og/fonts/index.ts | 20 +- .../app/api/og/server/[id]/players/route.tsx | 643 ++++++++++++++++++ apps/www/src/config/version.ts | 32 + apps/www/src/config/version.tsx | 527 -------------- 4 files changed, 685 insertions(+), 537 deletions(-) create mode 100644 apps/www/src/app/api/og/server/[id]/players/route.tsx create mode 100644 apps/www/src/config/version.ts delete mode 100644 apps/www/src/config/version.tsx diff --git a/apps/www/src/app/api/og/fonts/index.ts b/apps/www/src/app/api/og/fonts/index.ts index 9491ffd..5ae18ef 100644 --- a/apps/www/src/app/api/og/fonts/index.ts +++ b/apps/www/src/app/api/og/fonts/index.ts @@ -1,16 +1,16 @@ // For Edge runtime, we need to use fetch instead of fs export async function loadFonts() { - const interRegularFontP = fetch( - new URL("./Inter-Regular.ttf", import.meta.url), - ).then((res) => res.arrayBuffer()); + const interRegularFontP = fetch( + new URL("./Inter-Regular.ttf", import.meta.url) + ).then((res) => res.arrayBuffer()); - const interMediumFontP = fetch( - new URL("./Inter-Medium.ttf", import.meta.url), - ).then((res) => res.arrayBuffer()); + const interMediumFontP = fetch( + new URL("./Inter-Medium.ttf", import.meta.url) + ).then((res) => res.arrayBuffer()); - const interBoldFontP = fetch( - new URL("./Inter-Bold.ttf", import.meta.url), - ).then((res) => res.arrayBuffer()); + const interBoldFontP = fetch( + new URL("./Inter-Bold.ttf", import.meta.url) + ).then((res) => res.arrayBuffer()); - return Promise.all([interRegularFontP, interMediumFontP, interBoldFontP]); + return Promise.all([interRegularFontP, interMediumFontP, interBoldFontP]); } diff --git a/apps/www/src/app/api/og/server/[id]/players/route.tsx b/apps/www/src/app/api/og/server/[id]/players/route.tsx new file mode 100644 index 0000000..80df2fc --- /dev/null +++ b/apps/www/src/app/api/og/server/[id]/players/route.tsx @@ -0,0 +1,643 @@ +import { ImageResponse } from "@vercel/og"; +import { NextRequest } from "next/server"; +import { MongoClient } from "mongodb"; +import fs from "node:fs"; +import path from "node:path"; + +// Helper function to format dates +function formatDate(date: Date): string { + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }); +} + +// Helper function to format times +function formatTime(date: Date): string { + return date.toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + }); +} + +// Load fonts directly from the filesystem +async function loadLocalFonts() { + const fontPath = path.join(process.cwd(), "src", "app", "api", "og", "fonts"); + + return [ + fs.readFileSync(path.join(fontPath, "Inter-Regular.ttf")), + fs.readFileSync(path.join(fontPath, "Inter-Medium.ttf")), + fs.readFileSync(path.join(fontPath, "Inter-Bold.ttf")), + ]; +} + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + // Load banner image from filesystem + const bannerPath = path.join( + process.cwd(), + "public", + "branding", + "bg-banner.png" + ); + const bannerImageData = fs.readFileSync(bannerPath); + + const id = (await params).id; + + // Load fonts + const [interRegular, interMedium, interBold] = await loadLocalFonts(); + + // Verify server exists + const serverResponse = await fetch(`https://api.minehut.com/server/${id}`); + const serverData = await serverResponse.json(); + + if (!serverData.server) { + // Return a default banner for server not found + return new ImageResponse( + ( +
+
+ Server not found +
+
+ ), + { + width: 1200, + height: 630, + fonts: [ + { + name: "Inter", + data: interRegular, + style: "normal", + weight: 400, + }, + ], + } + ); + } + + const serverName = serverData.server.name; + + // Connect to MongoDB + const mongo = new MongoClient(process.env.MONGO_DB as string); + await mongo.connect(); + const db = mongo.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); + + // Get player data (last 60 entries) + const historyCollection = db.collection("history"); + const playerData = await historyCollection + .find({ server: serverName }) + .sort({ date: -1 }) + .limit(80) + .project({ player_count: 1, date: 1, _id: 0 }) + .toArray(); + + // Close MongoDB connection + await mongo.close(); + + // Reverse the data to show oldest to newest + const playerHistory = playerData.reverse(); + + // Calculate max player count for scaling + const maxPlayerCount = Math.max( + ...playerHistory.map((item: any) => item.player_count || 0), + 1 // Ensure we have at least 1 as max to avoid division by zero + ); + + // Calculate average player count + const averagePlayerCount = Math.round( + playerHistory.reduce( + (sum: number, item: any) => sum + (item.player_count || 0), + 0 + ) / Math.max(playerHistory.length, 1) + ); + + // Get current player count + const currentPlayerCount = + playerHistory.length > 0 + ? playerHistory[playerHistory.length - 1].player_count || 0 + : 0; + + const bars = []; + const numBars = Math.min(playerHistory.length, 30); // Limit to 30 bars for better visibility + const step = Math.max(1, Math.floor(playerHistory.length / numBars)); + + // Create Y-axis markers (player count) - Modified to avoid drawing horizontal lines that intersect with Y-axis + const yAxisMarkers = []; + const numYMarkers = 4; // Reduced to 4 for better alignment + + for (let i = 0; i <= numYMarkers; i++) { + const value = Math.round((maxPlayerCount * i) / numYMarkers); + const position = 200 - (i / numYMarkers) * 200; + + yAxisMarkers.push( +
0 ? "1px dashed rgba(255, 255, 255, 0.2)" : "none", + }} + > +
+ {value} +
+
+ ); + } + + const chartWidth = 970; // Fixed width of the chart area + // Create X-axis markers (times) + const xAxisMarkers = []; + const numXMarkers = Math.min(10, playerHistory.length); // Increased to 10 for more labels + + if (playerHistory.length > 0) { + for (let i = 0; i < numXMarkers; i++) { + const index = Math.floor( + (i / (numXMarkers - 1)) * (playerHistory.length - 1) + ); + const item = playerHistory[index]; + const position = (index / (playerHistory.length - 1)) * chartWidth; + + xAxisMarkers.push( +
+ {formatTime(new Date(item.date))} +
+ ); + } + } + + for (let i = 0; i < playerHistory.length; i += step) { + const item = playerHistory[i]; + const height = Math.max( + 5, + ((item.player_count || 0) / maxPlayerCount) * 200 + ); + const position = (i / (playerHistory.length - 1)) * chartWidth; + + bars.push( +
+
+
+ ); + } + + return new ImageResponse( + ( +
+ {/* Decorative elements */} +
+
+ + {/* Header with server name */} +
+

+ + {serverName} - Player Statistics + +

+
+ + {/* Stats summary */} +
+
+
+ Max Players +
+
+ {maxPlayerCount} +
+
+
+
+ Average Players +
+
+ {averagePlayerCount} +
+
+
+
+ Current Players +
+
+ {currentPlayerCount} +
+
+
+ + {/* Bar chart container */} +
+ {/* Chart content container */} +
+ {/* Y-axis and X-axis are now drawn as a single element to ensure continuity */} + + {/* Y-axis line */} + + {/* X-axis line */} + + {/* Y-axis tick marks */} + {Array.from({ length: numYMarkers + 1 }).map((_, i) => { + const yPosition = 200 - (i / numYMarkers) * 200; + return ( + + ); + })} + + + {/* Y-axis markers (labels and grid lines) */} +
{yAxisMarkers}
+ + {/* X-axis markers */} +
{xAxisMarkers}
+ + {/* Bars */} +
+ {bars} +
+
+
+ + {/* Chart legend */} +
+
+ + Oldest Entry:{" "} + {playerHistory.length > 0 + ? formatDate(new Date(playerHistory[0].date)) + : "N/A"} + +
+
+ + Latest Entry:{" "} + {playerHistory.length > 0 + ? formatDate( + new Date(playerHistory[playerHistory.length - 1].date) + ) + : "N/A"} + +
+
+ + {/* Footer */} +
+
+ ✨ Powered by mhsf.app +
+
+ + + Licensed under MIT license using open source technologies. + + + Using dynamic statistical data version 1. + + + Not officially affiliated with Minehut, GamerSafer or Super + League Enterprise. + + +
+
+ ), + { + width: 1200, + height: 630, + fonts: [ + { + name: "Inter", + data: interRegular, + style: "normal", + weight: 400, + }, + { + name: "Inter", + data: interBold, + style: "normal", + weight: 700, + }, + { + name: "Inter", + data: interMedium, + style: "normal", + weight: 500, + }, + ], + } + ); + } catch (error) { + console.error("Error generating player statistics image:", error); + + // Load fonts for error page + let interRegular: Buffer | null = null; + try { + interRegular = (await loadLocalFonts())[0]; + } catch (e) { + // If we can't load fonts, we'll just use a system font + console.error("Failed to load fonts for error page:", e); + } + + // Try to load the banner image + let bannerImageData: Buffer | null = null; + try { + const bannerPath = path.join( + process.cwd(), + "public", + "branding", + "dark-banner.png" + ); + bannerImageData = fs.readFileSync(bannerPath); + } catch (e) { + // If banner image fails to load, use a solid color background + console.error("Failed to load banner image for error page:", e); + } + + return new ImageResponse( + ( +
+
+ + Error generating player statistics image + + + {String(error).substring(0, 100)} + +
+
+ ), + { + width: 1200, + height: 630, + fonts: interRegular + ? [ + { + name: "Inter", + data: interRegular, + style: "normal", + weight: 400, + }, + ] + : undefined, + } + ); + } +} diff --git a/apps/www/src/config/version.ts b/apps/www/src/config/version.ts new file mode 100644 index 0000000..3eb8cf9 --- /dev/null +++ b/apps/www/src/config/version.ts @@ -0,0 +1,32 @@ +/* + * MHSF, Minehut Server List + * All external content is rather licensed under the ECA Agreement + * located here: https://mhsf.app/docs/legal/external-content-agreement + * + * All code under MHSF is licensed under the MIT License + * by open source contributors + * + * Copyright (c) 2025 dvelo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +"use client"; +export const version = "2.0"; diff --git a/apps/www/src/config/version.tsx b/apps/www/src/config/version.tsx deleted file mode 100644 index 9960e31..0000000 --- a/apps/www/src/config/version.tsx +++ /dev/null @@ -1,527 +0,0 @@ -/* - * MHSF, Minehut Server List - * All external content is rather licensed under the ECA Agreement - * located here: https://mhsf.app/docs/legal/external-content-agreement - * - * All code under MHSF is licensed under the MIT License - * by open source contributors - * - * Copyright (c) 2025 dvelo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -"use client"; -import { Link } from "@/components/util/link"; -import { Button } from "@/components/ui/button"; -import Github from "@/components/ui/github"; -import type { ReactNode } from "react"; - -const User = ({ user }: { user: string }) => ( - - {user} - -); - -const FeatureList = ({ - features, - title, - github, -}: { - features: (string | ReactNode)[]; - github?: string; - title: ReactNode; -}) => { - return ( -
    - {title} - {github && ( - - - - )} - {features.map((feature, i) => ( -
  • • {feature}
  • - ))} -
- ); -}; - -export const version = "2.0"; -export const changelog: { name: string; id: string; changelog: ReactNode }[] = [ - { - id: "tnjageringae231nfnajrekgra", - name: "v1.8.0", - changelog: ( - - Version 1.8.0 (February 14th 2025, {"<3 happy valentines"}) - - } - /> - ), - }, - { - id: "tj4ijg09aern9eargjjuauerr", - name: "v1.7.5", - changelog: ( - - Version 1.7.5 (January 29th 2025) - - } - /> - ), - }, - { - id: "38ufajf8efajwj3njdaisef", - name: "v1.7", - changelog: ( - - Version 1.7 (January 18th 2025) - - } - /> - ), - }, - { - id: "dut6hx3f2paswzjve4yg9r", - name: "v1.6.5", - changelog: ( - - Version 1.6.5 (December 20th 2024) - - } - /> - ), - }, - { - id: "h9jr2cbxn7qwfvt5uypsdg", - name: "v1.6.0", - changelog: ( - - Version 1.6.0 (November 17th 2024) - - } - /> - ), - }, - { - id: "r9swempc7kaqd2j84nutv5", - name: "v1.5.0", - changelog: ( - - Version 1.5.0 (November 16th 2024) - - } - /> - ), - }, - { - id: "ywvhtcs4k9rqjfp57x", - name: "v1.4.5", - changelog: ( - - Version 1.4.5 (November 6th 2024) - - } - /> - ), - }, - { - id: "amq4suhgcfwrb7y5j6", - name: "v1.4.0", - changelog: ( - - Version 1.4.0 (November 3rd 2024) - - } - /> - ), - }, - { - id: "jeh48p7w9bx2k3ad6f", - name: "v1.3.2", - changelog: ( -
- - Version 1.3.2 (October 4th 2024) - -
    -
  • • Minor backend changes
  • -
  • - •{" "} - - Please check on GitHub for statuses about this project. - -
  • -
-
- ), - }, - { - id: "wvg9x5dbpj76sn4yrz", - name: "v1.3.0", - changelog: ( -
- - Version 1.3.0 (September 9th 2024) - -
    -
  • - • New documentation linking -
  • -
  • - • Achievements are here! See more at{" "} - here -
  • -
  • • Finally fixed Cron actions for the final time™
  • -
  • • Overhauled account preferences
  • -
-
- ), - }, - { - name: "v1.2.0", - changelog: ( -
- - Version 1.2.0 (September 3rd 2024) - -
    -
  • • Added documentation
  • -
  • • Brand new linking of padding and server options
  • -
  • • New system to ensure automatic Cron actions!
  • -
  • • and alot more!
  • -
-
- ), - id: "e482y9k5hvjt73urfx", - }, - { - name: "v1.1.0", - changelog: ( -
- - Version 1.1.0 (August 24rd 2024) - -
    -
  • • Brand new hero page
  • -
  • • New padding option on server list
  • -
  • • New help guide
  • -
-
- ), - id: "hfn9p243765x8bwurj", - }, - { - name: "v1.0.0", - changelog: ( -
- - Version 1.0.0 (August 22nd 2024) - -
    -
  • • 1.0!
  • -
  • • New hover card on server title hover
  • -
  • • Moving to self-hosted cron jobs
  • -
  • • Fixing some mobile issues
  • -
-
- ), - id: "a8w4xvjbg3s7ynehu6", - }, - { - name: "v0.10.7", - changelog: ( -
- - Version b-0.10.7 (August 18th 2024) - -
    -
  • • New server information tab on server pages
  • -
-
- ), - id: "asbt64h9fdyu8neqmp", - }, - { - name: "v0.10.2", - changelog: ( -
- - Version b-0.10.2 (August 18th 2024) - -
    -
  • • Content fades-in on load
  • -
  • • Instead of using spinners, now we are using Skeletons
  • -
-
- ), - id: "kct29adbp6zug5r3q8", - }, - { - name: "v0.10.0", - changelog: ( -
- - Version b-0.10.0 (August 17th 2024) - -
    -
  • • Revamped server list button list
  • -
  • • Added welcome dialog when first launching
  • -
  • - • Fixed an issue where servers were still able to be favorited - client-side when logged out -
  • -
  • • Improved MOTD engine
  • -
-
- 👀 - {/** Ensure Tailwind pre-renders all grid column types */} - - - -
- ), - id: "ah6t7c8sfzyrkp3u52", - }, - { - name: "v0.9.0", - changelog: ( -
- - Version b-0.9.0 (August 15th 2024) - -
    -
  • • Adding favorites sorting option
  • -
  • • Fixed right-click context menu on the server list
  • -
  • • Fixed metadata bugs
  • -
-
- - Hey! Update on statistics. Recently, we have figured out the Minehut - API is blocked to Vercel servers (atleast the /servers{" "} - endpoint). I'm actively trying to find a loop-hole so that statistics - works correctly. Thank you {":)"} - -
-
- ), - id: "kjxnrfazc7hp9q4e82", - }, - { - name: "v0.8.0", - changelog: ( -
- - Version b-0.8.0 (August 11th 2024) - -
    -
  • • Fixing up command bar
  • -
  • • Renaming "Short Term" to "Statistics"
  • -
-
- ), - id: "f8rmhwzuxk3qyds542", - }, - { - name: "v0.7.2", - changelog: ( -
- - Version b-0.7.2 (August 7th 2024) - -
    -
  • • Adding new spinners to pages that needed it
  • -
  • • Fixed lots of bugs
  • -
  • • Moved from Inngest to Vercel Cron
  • -
-
- ), - id: "g2rhxfj6bu8wqk43n7", - }, - { - name: "v0.7.0", - changelog: ( -
- - Version b-0.7.0 (August 7th 2024) - -
    -
  • • Added customization to servers
  • -
  • • New button focus effect
  • -
  • • Lots of bugfixes
  • -
-
- ), - id: "a5xb97jv3surwmqn62", - }, - { - name: "v0.6.0", - changelog: ( -
- - Version b-0.6.0 (August 3rd 2024) - -
    -
  • • Enhanced shortcuts
  • -
  • • Added gradient beam to player count
  • -
  • • Updated loading animations
  • -
  • • Lots of bugfixes
  • -
-
- ), - id: "u83r5mkea9x4p2fjnb", - }, - { - name: "v0.4.5", - changelog: ( -
- - Version b-0.4.5 (July 26th 2024): - -
    -
  • • Made charts better
  • -
  • • Sorted API endpoints
  • -
-
- ), - id: "vu3k2daqj4y68bnwsp", - }, - { - name: "v0.4.0", - changelog: ( -
- - Version b-0.4 (July 25th 2024): - -
    -
  • • Added Info button
  • -
  • • Fixed Clerk in production
  • -
  • • Added Turbo for faster builds
  • -
  • - • Added historical data -
  • -
-
- ), - id: "psr9tx5jah74d32vq6", - }, - { - name: "v0.3.0", - changelog: ( -
- - Version b-0.3 (July 23th 2024): - -
    -
  • - • Fixed minor bugs described by -
  • -
-
- ), - id: "m2ngpd6fwtv7xh5zrk", - }, - { - name: "v0.2.0", - changelog: ( -
- - Version b-0.2 (July 23th 2024): - -
    -
  • • Inital release!
  • -
-
- ), - id: "xsfw2rcnv7m3kuhpbq", - }, -];