Finished sidebar
This commit is contained in:
10
src/lib/env/server.ts
vendored
Normal file
10
src/lib/env/server.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import z from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
HYPIXEL_API_KEY: z.string().min(1),
|
||||
},
|
||||
experimental__runtimeEnv: true,
|
||||
emptyStringAsUndefined: true,
|
||||
})
|
||||
20
src/lib/formatters.ts
Normal file
20
src/lib/formatters.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
const numberFormatter = new Intl.NumberFormat(undefined, {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 0,
|
||||
});
|
||||
|
||||
const dateFormatter = new Intl.DateTimeFormat(undefined, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
});
|
||||
|
||||
export function formatNumber(num: number): string {
|
||||
return numberFormatter.format(num);
|
||||
}
|
||||
export function formatDate(timestamp: number): string {
|
||||
return dateFormatter.format(new Date(timestamp));
|
||||
}
|
||||
20
src/lib/hypixel/api/guild.ts
Normal file
20
src/lib/hypixel/api/guild.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { env } from "../../env/server"
|
||||
import { guildSchema } from "../../schema/guild"
|
||||
|
||||
const guildApi = "https://api.hypixel.net/v2/guild"
|
||||
|
||||
export async function getGuild(id: string, type: "id" | "player" | "name" = "player") {
|
||||
const res = await fetch(`${guildApi}?${type}=${id}`, {
|
||||
headers: {
|
||||
"API-Key": env.HYPIXEL_API_KEY
|
||||
}
|
||||
})
|
||||
|
||||
if (!res.ok) return null
|
||||
|
||||
const { success, data } = guildSchema.safeParse(await res.json())
|
||||
|
||||
if (!success) return null
|
||||
|
||||
return data.guild
|
||||
}
|
||||
22
src/lib/hypixel/api/mojang.ts
Normal file
22
src/lib/hypixel/api/mojang.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import z from "zod"
|
||||
|
||||
const mojangApi = "https://api.mojang.com/users/profiles/minecraft"
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1),
|
||||
id: z.string().min(1)
|
||||
})
|
||||
|
||||
export async function getUuid(ign: string) {
|
||||
const res = await fetch(`${mojangApi}/${ign}`)
|
||||
|
||||
if (!res.ok) return null
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
const parsed = schema.safeParse(data)
|
||||
|
||||
if (!parsed.success) return null
|
||||
|
||||
return parsed.data.id
|
||||
}
|
||||
20
src/lib/hypixel/api/player.ts
Normal file
20
src/lib/hypixel/api/player.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { env } from "../../env/server"
|
||||
import { playerSchema } from "../../schema/player"
|
||||
|
||||
const playerApi = "https://api.hypixel.net/v2/player"
|
||||
|
||||
export async function getPlayer(uuid: string) {
|
||||
const res = await fetch(`${playerApi}?uuid=${uuid}`, {
|
||||
headers: {
|
||||
"API-Key": env.HYPIXEL_API_KEY
|
||||
}
|
||||
})
|
||||
|
||||
if (!res.ok) return null
|
||||
|
||||
const { success, data } = playerSchema.safeParse(await res.json())
|
||||
|
||||
if (!success) return null
|
||||
|
||||
return data.player
|
||||
}
|
||||
9
src/lib/hypixel/formatters.ts
Normal file
9
src/lib/hypixel/formatters.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function floorLevel(level: number, base: number) {
|
||||
const extra = level % base
|
||||
|
||||
if (extra === 0) {
|
||||
return level;
|
||||
}
|
||||
|
||||
return level - extra;
|
||||
}
|
||||
31
src/lib/hypixel/guild.ts
Normal file
31
src/lib/hypixel/guild.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { object } from "zod"
|
||||
import { Guild } from "../schema/guild"
|
||||
|
||||
export function getGuildMember(guild: Guild["guild"], uuid: string) {
|
||||
return guild.members.find(m => m.uuid === uuid)
|
||||
}
|
||||
|
||||
export function getGuildRankTag(guild: Guild["guild"], uuid: string) {
|
||||
const member = getGuildMember(guild, uuid)
|
||||
return member?.rank === "Guild Master"
|
||||
? "[GM]"
|
||||
: `[${guild.ranks.find(r => r.name === member?.rank)?.tag}]`
|
||||
}
|
||||
|
||||
export function getMemberGEXP(guild: Guild["guild"], uuid: string, days: number = 0) {
|
||||
const member = getGuildMember(guild, uuid)
|
||||
|
||||
if (!member) return null
|
||||
|
||||
const index = Object.keys(member.expHistory)[days]
|
||||
|
||||
return member.expHistory[index]
|
||||
}
|
||||
|
||||
export function getMemberWeeklyGEXP(guild: Guild["guild"], uuid: string) {
|
||||
const member = getGuildMember(guild, uuid)
|
||||
|
||||
if (!member) return null
|
||||
|
||||
return Object.values(member.expHistory).reduce((a, b) => a + b)
|
||||
}
|
||||
37
src/lib/hypixel/level.ts
Normal file
37
src/lib/hypixel/level.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
const BASE = 10000;
|
||||
const GROWTH = 2500;
|
||||
|
||||
const HALF_GROWTH = 0.5 * GROWTH;
|
||||
|
||||
const REVERSE_PQ_PREFIX = -(BASE - 0.5 * GROWTH) / GROWTH;
|
||||
const REVERSE_CONST = REVERSE_PQ_PREFIX * REVERSE_PQ_PREFIX;
|
||||
const GROWTH_DIVIDES_2 = 2 / GROWTH;
|
||||
|
||||
export function getLevel(exp: number) {
|
||||
return exp <= 1 ? 1 : Math.floor(1 + REVERSE_PQ_PREFIX + Math.sqrt(REVERSE_CONST + GROWTH_DIVIDES_2 * exp));
|
||||
}
|
||||
|
||||
export function getExactLevel(exp: number) {
|
||||
return getLevel(exp) + getPercentageToNextLevel(exp);
|
||||
}
|
||||
|
||||
// function getExpFromLevelToNext(level: number) {
|
||||
// return level < 1 ? BASE : GROWTH * (level - 1) + BASE;
|
||||
// }
|
||||
|
||||
function getTotalExpToLevel(level: number) {
|
||||
const lv = Math.floor(level); const
|
||||
x0 = getTotalExpToFullLevel(lv);
|
||||
if (level === lv) return x0;
|
||||
return (getTotalExpToFullLevel(lv + 1) - x0) * (level % 1) + x0;
|
||||
}
|
||||
|
||||
function getTotalExpToFullLevel(level: number) {
|
||||
return (HALF_GROWTH * (level - 2) + BASE) * (level - 1);
|
||||
}
|
||||
|
||||
function getPercentageToNextLevel(exp: number) {
|
||||
const lv = getLevel(exp);
|
||||
const x0 = getTotalExpToLevel(lv);
|
||||
return (exp - x0) / (getTotalExpToLevel(lv + 1) - x0);
|
||||
}
|
||||
41
src/lib/hypixel/stats.ts
Normal file
41
src/lib/hypixel/stats.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { MULTIPLIER } from "@/data/hypixel/general"
|
||||
import { Player } from "@/lib/schema/player"
|
||||
|
||||
export function getCoinMultiplier(level: number) {
|
||||
if (level > MULTIPLIER[MULTIPLIER.length - 1].level) {
|
||||
return MULTIPLIER[MULTIPLIER.length - 1].value;
|
||||
}
|
||||
|
||||
for (let i = MULTIPLIER.length - 1; i >= 0; i--) {
|
||||
if (level >= MULTIPLIER[i].level) {
|
||||
return MULTIPLIER[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
return MULTIPLIER[0].value
|
||||
}
|
||||
|
||||
export function getTotalCoins(stats: Player["player"]["stats"]) {
|
||||
return Object.values(stats).reduce((total, stat) => total + (stat.coins || 0), 0);
|
||||
}
|
||||
|
||||
export function getTotalQuests(quests: Player["player"]["quests"]) {
|
||||
return Object.values(quests).reduce((total, quest) => total + (quest.completions?.length || 0), 0);
|
||||
}
|
||||
|
||||
export function getTotalChallenges(challenges: Player["player"]["challenges"]["all_time"]) {
|
||||
return Object.values(challenges).reduce((total, challenge) => total + challenge, 0);
|
||||
}
|
||||
|
||||
export function rewardClaimed(claimedAt?: number) {
|
||||
if (!claimedAt) return false
|
||||
const now = new Date()
|
||||
const claimedDate = new Date(claimedAt)
|
||||
const oneDay = 24 * 60 * 60 * 1000
|
||||
|
||||
if (now.getMilliseconds() - claimedDate.getMilliseconds() > oneDay) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
29
src/lib/hypixel/validatePlayer.ts
Normal file
29
src/lib/hypixel/validatePlayer.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
"use server"
|
||||
|
||||
import { getUuid } from "./api/mojang"
|
||||
import { getPlayer } from "./api/player"
|
||||
|
||||
export async function validatePlayer(ign: string) {
|
||||
const uuid = await getUuid(ign)
|
||||
|
||||
if (!uuid) {
|
||||
return {
|
||||
error: true,
|
||||
message: "Player not found",
|
||||
}
|
||||
}
|
||||
|
||||
const player = await getPlayer(uuid)
|
||||
|
||||
if (!player) {
|
||||
return {
|
||||
error: true,
|
||||
message: "Player never logged on to Hypixel",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error: false,
|
||||
message: "Player found",
|
||||
}
|
||||
}
|
||||
26
src/lib/schema/guild.ts
Normal file
26
src/lib/schema/guild.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import z from "zod"
|
||||
|
||||
export const guildSchema = z.object({
|
||||
guild: z.object({
|
||||
_id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
tag: z.string().optional(),
|
||||
tagColor: z.string().optional(),
|
||||
members: z.array(z.object({
|
||||
uuid: z.string(),
|
||||
rank: z.string(),
|
||||
joined: z.number(),
|
||||
questParticipation: z.number().optional(),
|
||||
expHistory: z.record(z.string(), z.number())
|
||||
})),
|
||||
ranks: z.array(z.object({
|
||||
name: z.string(),
|
||||
default: z.boolean(),
|
||||
tag: z.string().nullish().optional(),
|
||||
created: z.number(),
|
||||
priority: z.number()
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
export type Guild = z.infer<typeof guildSchema>
|
||||
56
src/lib/schema/player.ts
Normal file
56
src/lib/schema/player.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import z from "zod"
|
||||
|
||||
export const playerSchema = z.object({
|
||||
player: z.object({
|
||||
displayname: z.string(),
|
||||
uuid: z.string(),
|
||||
newPackageRank: z.literal("VIP").or(z.literal("VIP_PLUS").or(z.literal("MVP")).or(z.literal("MVP_PLUS"))).optional(),
|
||||
monthlyPackageRank: z.string().optional(),
|
||||
rankPlusColor: z.string().optional(),
|
||||
monthlyRankColor: z.literal("GOLD").or(z.literal("AQUA")).optional(),
|
||||
networkExp: z.number(),
|
||||
karma: z.number(),
|
||||
achievementPoints: z.number().optional(),
|
||||
stats: z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
coins: z.number().optional()
|
||||
})
|
||||
),
|
||||
quests: z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
completions: z.array(
|
||||
z.object({
|
||||
time: z.number()
|
||||
}).optional()
|
||||
).optional()
|
||||
})
|
||||
),
|
||||
challenges: z.object({
|
||||
all_time: z.record(z.string(), z.number())
|
||||
}),
|
||||
lastClaimedReward: z.number().optional(),
|
||||
rewardHighScore: z.number().optional(),
|
||||
rewardStreak: z.number().optional(),
|
||||
totalRewards: z.number().optional(),
|
||||
giftingMeta: z.object({
|
||||
giftsGiven: z.number().optional(),
|
||||
ranksGiven: z.number().optional()
|
||||
}).optional(),
|
||||
firstLogin: z.number().optional(),
|
||||
lastLogin: z.number().optional(),
|
||||
socialMedia: z.object({
|
||||
links: z.object({
|
||||
DISCORD: z.string().optional(),
|
||||
TWITCH: z.string().optional(),
|
||||
HYPIXEL: z.string().optional(),
|
||||
TWITTER: z.string().optional(),
|
||||
YOUTUBE: z.string().optional()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
export type Player = z.infer<typeof playerSchema>
|
||||
|
||||
Reference in New Issue
Block a user