diff --git a/src/app/(stats)/player/[ign]/_components/Sidebar.tsx b/src/app/(stats)/player/[ign]/_components/Sidebar.tsx
index d51fb5c..bb452ff 100644
--- a/src/app/(stats)/player/[ign]/_components/Sidebar.tsx
+++ b/src/app/(stats)/player/[ign]/_components/Sidebar.tsx
@@ -4,9 +4,11 @@ import { Separator } from "@/components/ui/separator"
import { getColor } from "@/lib/colors"
import { formatDate, formatNumber } from "@/lib/formatters"
import { getCoinMultiplier, getTotalChallenges, getTotalCoins, getTotalQuests, rewardClaimed } from "@/lib/hypixel/general/stats"
+import { getGame, getGameMode } from "@/lib/hypixel/general/status"
import { getGuildMember, getGuildRankTag, getMemberGEXP, getMemberWeeklyGEXP } from "@/lib/hypixel/guild/guild"
import { Guild } from "@/lib/schema/guild"
import { Player } from "@/lib/schema/player"
+import { Session } from "@/lib/schema/status"
import Link from "next/link"
import SocialIcons from "./SocialIcons"
@@ -18,9 +20,10 @@ type SidebarProps = {
rank: string | undefined
specialRank: string | undefined
eulaCoins: boolean | undefined
+ session: Session["session"] | null
}
-export default function Sidebar({ level, ign, player, guild, rank, specialRank, eulaCoins }: SidebarProps) {
+export default function Sidebar({ level, ign, player, guild, rank, specialRank, eulaCoins, session }: SidebarProps) {
const levelMultiplier = getCoinMultiplier(level, rank, specialRank, eulaCoins)
const levelMultiplierVal = levelMultiplier.value
@@ -200,6 +203,62 @@ export default function Sidebar({ level, ign, player, guild, rank, specialRank,
)
}
+ function OnlineStatus() {
+ if (!session?.online) return null
+
+ const noMapGameTypes = ["BUILD_BATTLE", "HOUSING", "REPLAY"]
+ const noMapArcadeModes = [
+ "DAYONE",
+ "DEFENDER",
+ "DRAGONWARS2",
+ "DROPPER",
+ "SOCCER",
+ "STARWARS",
+ "SIMON_SAYS",
+ "PARTY",
+ "DRAW_THEIR_THING",
+ "PIXEL_PARTY",
+ "THROW_OUT"
+ ]
+
+ const showMode = !(session.gameType === "REPLAY")
+ const showMap = session.map !== undefined &&
+ session.gameType !== undefined &&
+ !noMapGameTypes.includes(session.gameType) &&
+ session.mode !== undefined &&
+ !(session.gameType === "ARCADE" && noMapArcadeModes.includes(session.mode))
+ const mode = session.mode === "LOBBY" ? "Lobby" : getGameMode(session.gameType, session.mode)
+
+ return (
+ <>
+
+
Online Status
+
+ {session.gameType && (
+
+ {"Game: "}
+ {getGame(session.gameType)?.name || "Unknown"}
+
+ )}
+ {showMode && (
+
+ {"Mode: "}
+ {mode || "Unknown"}
+
+ )}
+ {showMap && (
+
+ {"Map: "}
+ {session.map}
+
+ )}
+
+
+
+ >
+ )
+ }
+
function SoicalLinks() {
return (
@@ -235,6 +294,7 @@ export default function Sidebar({ level, ign, player, guild, rank, specialRank,
+
diff --git a/src/app/(stats)/player/[ign]/page.tsx b/src/app/(stats)/player/[ign]/page.tsx
index 0498823..a9d1871 100644
--- a/src/app/(stats)/player/[ign]/page.tsx
+++ b/src/app/(stats)/player/[ign]/page.tsx
@@ -4,6 +4,7 @@ import { Card, CardContent } from "@/components/ui/card"
import { env } from "@/lib/env/server"
import { getGuild } from "@/lib/hypixel/api/guild"
import { getUuid } from "@/lib/hypixel/api/mojang"
+import { getSession } from "@/lib/hypixel/api/online"
import { getPlayer } from "@/lib/hypixel/api/player"
import { getExactLevel } from "@/lib/hypixel/general/level"
import { Loader2Icon, ShieldAlert } from "lucide-react"
@@ -78,6 +79,7 @@ async function SuspendedPage({ params }: Pick
, "param
)
}
+ const session = await getSession(mc.id)
const guild = await getGuild(mc.id)
const level = getExactLevel(player.networkExp)
@@ -109,6 +111,7 @@ async function SuspendedPage({ params }: Pick, "param
rank={player.newPackageRank}
specialRank={player.rank}
eulaCoins={player.eulaCoins}
+ session={session}
/>
{player.stats !== undefined ?
(
diff --git a/src/data/hypixel/hypixel.ts b/src/data/hypixel/hypixel.ts
new file mode 100644
index 0000000..50e1d72
--- /dev/null
+++ b/src/data/hypixel/hypixel.ts
@@ -0,0 +1,249 @@
+export const GAMES = [
+ { id: "ARCADE", name: "Arcade Games" },
+ { id: "ARENA", name: "Arena Brawl" },
+ { id: "BATTLEGROUND", name: "Warlords" },
+ { id: "BEDWARS", name: "Bed Wars" },
+ { id: "BUILD_BATTLE", name: "Build Battle" },
+ { id: "DUELS", name: "Duels" },
+ { id: "GINGERBREAD", name: "Turbo Kart Racers" },
+ { id: "HOUSING", name: "Housing" },
+ { id: "LEGACY", name: "Classic Games" },
+ { id: "MAIN", name: "Main" },
+ { id: "MCGO", name: "Cops and Crims" },
+ { id: "MURDER_MYSTERY", name: "Murder Mystery" },
+ { id: "PAINTBALL", name: "Paintball" },
+ { id: "PIT", name: "Pit" },
+ { id: "PROTOTYPE", name: "Prototype" },
+ { id: "QUAKECRAFT", name: "Quakecraft" },
+ { id: "REPLAY", name: "Replay" },
+ { id: "SKYBLOCK", name: "SkyBlock" },
+ { id: "SKYWARS", name: "SkyWars" },
+ { id: "SMP", name: "SMP" },
+ { id: "SPEED_UHC", name: "Speed UHC" },
+ { id: "SUPER_SMASH", name: "Smash Heroes" },
+ { id: "SURVIVAL_GAMES", name: "Blitz SG" },
+ { id: "TNTGAMES", name: "TNT Games" },
+ { id: "TOURNAMENT", name: "Tournament Hall" },
+ { id: "UHC", name: "UHC Champions" },
+ { id: "VAMPIREZ", name: "VampireZ" },
+ { id: "WALLS", name: "Walls" },
+ { id: "WALLS3", name: "Mega Walls" },
+ { id: "WOOL_GAMES", name: "Wool Games" }
+]
+
+export const MODES = {
+ ARCADE: {
+ DAYONE: "Blocking Dead",
+ ONEINTHEQUIVER: "Bounty Hunters",
+ PVP_CTW: "Capture the Wool",
+ DEFENDER: "Creeper Attack",
+ DRAGONWARS2: "Dragon Wars",
+ DROPPER: "Dropper",
+ ENDER: "Ender Spleef",
+ FARM_HUNT: "Farm Hunt",
+ SOCCER: "Football",
+ STARWARS: "Galaxy Wars",
+ HALLOWEEN_SIMULATOR: "Halloween Simulator",
+ HIDE_AND_SEEK_PROP_HUNT: "Hide and Seek (Prop Hunt)",
+ HIDE_AND_SEEK_PARTY_POOPER: "Hide and Seek (Party Pooper)",
+ HOLE_IN_THE_WALL: "Hole in the Wall",
+ SIMON_SAYS: "Hypixel Says",
+ MINI_WALLS: "Mini Walls",
+ PARTY: "Party Games",
+ DRAW_THEIR_THING: "Pixel Painters",
+ PIXEL_PARTY: "Pixel Party",
+ THROW_OUT: "Throw Out",
+ ZOMBIES_DEAD_END: "Zombies",
+ ZOMBIES_BAD_BLOOD: "Zombies",
+ ZOMBIES_ALIEN_ARCADIUM: "Zombies",
+ ZOMBIES_PRISON: "Zombies"
+ },
+ ARENA: {
+ "1v1": "1v1",
+ "2v2": "2v2",
+ "4v4": "4v4"
+ },
+ BATTLEGROUND: {
+ mini_ctf: "Capture the Flag",
+ domination: "Domination",
+ team_deathmatch: "Team Deathmatch"
+ },
+ BEDWARS: {
+ BEDWARS_EIGHT_ONE: "Solo",
+ BEDWARS_EIGHT_TWO: "Doubles",
+ BEDWARS_EIGHT_TWO_TOURNEY: "Doubles (Tournament)",
+ BEDWARS_FOUR_THREE: "3v3v3v3",
+ BEDWARS_FOUR_FOUR: "4v4v4v4",
+ BEDWARS_FOUR_FOUR_TOURNEY: "4v4v4v4 (Tournament)",
+ BEDWARS_TWO_FOUR: "4v4",
+ BEDWARS_TWO_FOUR_TOURNEY: "4v4 (Tournament)",
+ BEDWARS_EIGHT_ONE_RUSH: "Rush Solo",
+ BEDWARS_EIGHT_TWO_RUSH: "Rush Doubles",
+ BEDWARS_FOUR_FOUR_RUSH: "Rush 4v4v4v4",
+ BEDWARS_EIGHT_ONE_ULTIMATE: "Ultimate Solo",
+ BEDWARS_EIGHT_TWO_ULTIMATE: "Ultimate Doubles",
+ BEDWARS_FOUR_FOUR_ULTIMATE: "Ultimate 4v4v4v4",
+ BEDWARS_EIGHT_TWO_VOIDLESS: "Voidless Doubles",
+ BEDWARS_FOUR_FOUR_VOIDLESS: "Voidless 4v4v4v4",
+ BEDWARS_EIGHT_TWO_ARMED: "Armed Doubles",
+ BEDWARS_FOUR_FOUR_ARMED: "Armed 4v4v4v4",
+ BEDWARS_EIGHT_TWO_SWAP: "Swappage Doubles",
+ BEDWARS_FOUR_FOUR_SWAP: "Swappage 4v4v4v4",
+ BEDWARS_EIGHT_TWO_UNDERWORLD: "Underworld Doubles",
+ BEDWARS_FOUR_FOUR_UNDERWORLD: "Underworld 4v4v4v4",
+ BEDWARS_CASTLE: "Castle",
+ BEDWARS_PRACTICE: "Practice"
+ },
+ BUILD_BATTLE: {
+ BUILD_BATTLE_SOLO_PRO: "Pro",
+ BUILD_BATTLE_SOLO_NORMAL: "Solo",
+ BUILD_BATTLE_TEAMS_NORMAL: "Teams",
+ BUILD_BATTLE_GUESS_THE_BUILD: "Guess the Build",
+ BUILD_BATTLE_HALLOWEEN: "Halloween Hyper",
+ BUILD_BATTLE_CHRISTMAS_NEW_TEAMS: "Holiday Teams",
+ BUILD_BATTLE_SOLO_NORMAL_LATEST: "Solo (1.14+)",
+ BUILD_BATTLE_CHRISTMAS_NEW_SOLO: "Holiday Solo",
+ BUILD_BATTLE_CHRISTMAS: "Christmas"
+ },
+ DUELS: {
+ DUELS_UHC_DUEL: "UHC 1v1",
+ DUELS_UHC_DOUBLES: "UHC 2v2",
+ DUELS_UHC_TOURNAMENT: "UHC Championship",
+ DUELS_UHC_FOUR: "UHC 4v4",
+ DUELS_UHC_MEETUP: "UHC Deathmatch",
+ DUELS_OP_DUEL: "OP 1v1",
+ DUELS_OP_DOUBLES: "OP 2v2",
+ DUELS_SW_DUEL: "SkyWars 1v1",
+ DUELS_SW_DOUBLES: "SkyWars 2v2",
+ DUELS_SW_TOURNAMENT: "SkyWars Championship",
+ DUELS_BOW_DUEL: "Bow 1v1",
+ DUELS_BLITZ_DUEL: "Blitz 1v1",
+ DUELS_MW_DUEL: "MegaWalls 1v1",
+ DUELS_MW_DOUBLES: "MegaWalls 2v2",
+ DUELS_SUMO_DUEL: "Sumo 1v1",
+ DUELS_SUMO_TOURNAMENT: "Sumo Championship",
+ DUELS_BOWSPLEEF_DUEL: "Bow Spleef 1v1",
+ DUELS_PARKOUR_EIGHT: "Parkour",
+ DUELS_BOXING_DUEL: "Boxing 1v1",
+ DUELS_CLASSIC_DUEL: "Classic 1v1",
+ DUELS_POTION_DUEL: "NoDebuff 1v1",
+ DUELS_COMBO_DUEL: "Combo 1v1",
+ DUELS_BRIDGE_DUEL: "Bridge 1v1",
+ DUELS_BRIDGE_DOUBLES: "Bridge 2v2",
+ DUELS_BRIDGE_TOURNAMENT: "The Bridge Championship",
+ DUELS_BRIDGE_THREES: "Bridge 3v3",
+ DUELS_BRIDGE_FOUR: "Bridge 4v4",
+ DUELS_BRIDGE_2V2V2V2: "Bridge 2v2v2v2",
+ DUELS_BRIDGE_3V3V3V3: "Bridge 3v3v3v3",
+ DUELS_CAPTURE_THREES: "Bridge CTF 3v3",
+ DUELS_DUEL_ARENA: "Duel Arena"
+ },
+ GINGERBREAD: {},
+ HOUSING: {},
+ LEGACY: {},
+ MAIN: {},
+ MCGO: {
+ normal: "Defusal",
+ gungame: "Gun Game",
+ deathmatch: "Team Deathmatch",
+ DEFUSAL_TOURNEY: "Defusal (Tournament)"
+ },
+ MURDER_MYSTERY: {
+ MURDER_CLASSIC: "Classic",
+ MURDER_ASSASSINS: "Assassins",
+ MURDER_INFECTION: "Infection",
+ MURDER_DOUBLE_UP: "Double Up",
+ MURDER_HARDCORE: "Hardcore",
+ MURDER_SHOWDOWN: "Showdown"
+ },
+ PAINTBALL: {},
+ PIT: {},
+ PROTOTYPE: {},
+ QUAKECRAFT: {
+ teams: "Teams",
+ solo: "Solo",
+ solo_tourney: "Solo (Tournament)"
+ },
+ SKYBLOCK: {
+ farming_1: "The Farming Islands",
+ crystal_hollows: "Crystal Hollows",
+ winter: "Jerry's Workshop",
+ foraging_1: "The Park",
+ dark_auction: "Dark Auction",
+ dungeon: "Dungeons",
+ combat_3: "The End",
+ crismon_isle: "Crimson Isle",
+ hub: "Hub",
+ instanced: "Kuudra's Hollow",
+ dynamic: "Private Island",
+ mining_3: "Dwarven Mines",
+ garden: "The Garden",
+ mining_1: "Gold Mine",
+ combat_2: "Blazing Fortress",
+ mining_2: "Deep Caverns",
+ combat_1: "Spider's Den"
+ },
+ SKYWARS: {
+ ranked_normal: "Ranked",
+ solo_normal: "Solo Normal",
+ solo_insane: "Solo Insane",
+ team_normal: "Teams Normal",
+ team_insane: "Teams Insane",
+ mega_normal: "Mega",
+ mega_doubles: "Mega Doubles",
+ solo_insane_lucky: "Solo Lucky Block",
+ teams_normal_tourney: "Doubles Normal (Tournament)",
+ solo_insane_slime: "Solo Slime",
+ teams_insane_slime: "Teams Slime",
+ teams_insane_rush: "Teams Rush",
+ teams_insane_lucky: "Teams Lucky Block",
+ solo_crazyinsane: "Solo Crazy Insane (Tournament)",
+ teams_insane_tourney: "Doubles Insane (Tournament)",
+ solo_insane_hunters_vs_beasts: "Solo Hunters vs Beasts",
+ solo_insane_tnt_madness: "Solo TNT Madness",
+ solo_insane_rush: "Solo Rush",
+ teams_insane_tnt_madness: "Teams TNT Madness"
+ },
+ SMP: {},
+ SPEED_UHC: {
+ solo_normal: "Solo Normal",
+ solo_insane: "Solo Insane",
+ team_normal: "Teams Normal",
+ team_insane: "Teams Insane"
+ },
+ SUPER_SMASH: {
+ solo_normal: "Solo",
+ teams_normal: "Teams",
+ friends_normal: "Friends"
+ },
+ SURVIVAL_GAMES: {
+ solo_normal: "Solo",
+ teams_normal: "Teams"
+ },
+ TNTGAMES: {
+ BOWSPLEEF: "Bow Spleef",
+ PVPRUN: "PVP Run",
+ TNTRUN: "TNT Run",
+ TNTAG: "TNT Tag",
+ CAPTURE: "Wizards",
+ TNTRUN_TOURNEY: "TNT Run Tourney",
+ TEAMS_NORMAL: "Teams Mode"
+ },
+ TOURNAMENT: {},
+ UHC: {
+ SOLO: "Solo",
+ TEAMS: "Teams"
+ },
+ VAMPIREZ: {},
+ WALLS: {},
+ WALLS3: {
+ face_off: "Face Off",
+ standard: "Standard"
+ },
+ WOOL_GAMES: {
+ wool_wars_two_four_tourney: "4v4 (Tournament)",
+ wool_wars_two_four: "4v4",
+ capture_the_wool_two_twenty: "Capture the Wool",
+ sheep_wars_two_six: "Sheep Wars"
+ }
+}
diff --git a/src/lib/hypixel/api/online.ts b/src/lib/hypixel/api/online.ts
new file mode 100644
index 0000000..0b404ca
--- /dev/null
+++ b/src/lib/hypixel/api/online.ts
@@ -0,0 +1,31 @@
+import { statusSchema } from "@/lib/schema/status"
+import { cacheLife } from "next/dist/server/use-cache/cache-life"
+import { env } from "../../env/server"
+
+const playerApi = "https://api.hypixel.net/v2/status"
+
+export async function getSession(uuid: string) {
+ "use cache"
+
+ if (process.env.NODE_ENV === "production") {
+ cacheLife({
+ stale: 1000 * 60,
+ revalidate: 1000 * 60 * 5,
+ expire: 1000 * 60 * 5
+ })
+ }
+
+ const res = await fetch(`${playerApi}?uuid=${uuid}`, {
+ headers: {
+ "API-Key": env.HYPIXEL_API_KEY
+ }
+ })
+
+ if (!res.ok) return null
+
+ const { success, data } = statusSchema.safeParse(await res.json())
+
+ if (!success) return null
+
+ return data.session
+}
diff --git a/src/lib/hypixel/general/status.ts b/src/lib/hypixel/general/status.ts
new file mode 100644
index 0000000..82a297f
--- /dev/null
+++ b/src/lib/hypixel/general/status.ts
@@ -0,0 +1,20 @@
+import { GAMES, MODES } from "@/data/hypixel/hypixel"
+
+export function getGame(val: string) {
+ const game = GAMES.find(g => g.id === val)
+ return game || null
+}
+
+export function getGameMode(gameType?: string, mode?: string) {
+ if (!gameType || !mode) return null
+ const game = getGame(gameType)
+ if (!game) return null
+
+ const modes = MODES as Record | Record>
+
+ const gameModes = modes[gameType]
+ if (!gameModes) return null
+
+ const modeName = gameModes[mode]
+ return modeName || null
+}
diff --git a/src/lib/schema/status.ts b/src/lib/schema/status.ts
new file mode 100644
index 0000000..c823621
--- /dev/null
+++ b/src/lib/schema/status.ts
@@ -0,0 +1,17 @@
+import z from "zod"
+
+export const statusSchema = z.object({
+ session: z.discriminatedUnion("online", [
+ z.object({
+ online: z.literal(false)
+ }),
+ z.object({
+ online: z.literal(true),
+ gameType: z.string().optional(),
+ mode: z.string().optional(),
+ map: z.string().optional()
+ })
+ ])
+})
+
+export type Session = z.infer
diff --git a/đ b/đ
new file mode 100644
index 0000000..cdcfa6d
--- /dev/null
+++ b/đ
@@ -0,0 +1,18 @@
+import { GAMES, MODES } from "@/data/hypixel/hypixel"
+
+export function getGame(val: string) {
+ const game = GAMES.find(g => g.id === val)
+ return game || null
+}
+
+export function getGameMode(gameType?: string, mode?: string) {
+ if (!gameType || !mode) return null
+ const game = getGame(gameType)
+ if (!game) return null
+
+ const gameModes = MODES[gameType as keyof typeof MODES]
+ if (!gameModes) return null
+
+ const modeValue = gameModes[mode as keyof typeof gameModes]
+ return modeValue || null
+}