Added online status to sidebar

This commit is contained in:
2025-09-13 23:28:47 +02:00
parent d754fc0b37
commit 781338a81b
7 changed files with 399 additions and 1 deletions

View File

@@ -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 (
<>
<div className="flex flex-col gap-2">
<h2 className="text-xl font-bold">Online Status</h2>
<div>
{session.gameType && (
<p>
<span className="font-bold">{"Game: "}</span>
<span>{getGame(session.gameType)?.name || "Unknown"}</span>
</p>
)}
{showMode && (
<p>
<span className="font-bold">{"Mode: "}</span>
<span>{mode || "Unknown"}</span>
</p>
)}
{showMap && (
<p>
<span className="font-bold">{"Map: "}</span>
<span>{session.map}</span>
</p>
)}
</div>
</div>
<Separator className="my-4" />
</>
)
}
function SoicalLinks() {
return (
<div className="flex flex-col gap-2">
@@ -235,6 +294,7 @@ export default function Sidebar({ level, ign, player, guild, rank, specialRank,
<SkyblockButton />
<Separator className="my-4" />
<GuildInfo />
<OnlineStatus />
<SoicalLinks />
</CardContent>
</Card>

View File

@@ -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<PageProps<"/player/[ign]">, "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<PageProps<"/player/[ign]">, "param
rank={player.newPackageRank}
specialRank={player.rank}
eulaCoins={player.eulaCoins}
session={session}
/>
{player.stats !== undefined ?
(

249
src/data/hypixel/hypixel.ts Normal file
View File

@@ -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"
}
}

View File

@@ -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
}

View File

@@ -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<string, undefined | Record<string, never> | Record<string, string | undefined>>
const gameModes = modes[gameType]
if (!gameModes) return null
const modeName = gameModes[mode]
return modeName || null
}

17
src/lib/schema/status.ts Normal file
View File

@@ -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<typeof statusSchema>

18
đ Normal file
View File

@@ -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
}