const API_BASE_URL = process.env.NEXT_PUBLIC_MINECRAFT_API_URL || 'http://localhost:6767'; const API_KEY = process.env.NEXT_PUBLIC_MINECRAFT_API_KEY; interface ApiResponse { success: boolean; message?: string; error?: string; details?: string; data?: T; } interface PlayerIdentifierInput { type: 'java' | 'bedrock'; username: string; } interface PlayerIdentifierOutput { platform: 'java' | 'bedrock'; username: string; uuid?: string; xuid?: string; } interface MojangResponse { id: string; name: string; } interface GeyserResponse { xuid: string; gamertag: string; } class MinecraftApiClient { private cache = new Map(); private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes private isCacheValid(timestamp: number): boolean { return Date.now() - timestamp < this.CACHE_TTL; } private getCacheKey(type: string, username: string): string { return `${type}:${username.toLowerCase()}`; } private formatUUID(uuid: string): string { // Convert UUID from Mojang format (no dashes) to dashed format return `${uuid.slice(0,8)}-${uuid.slice(8,12)}-${uuid.slice(12,16)}-${uuid.slice(16,20)}-${uuid.slice(20)}`; } private async resolveJavaUsername(username: string): Promise { const cacheKey = this.getCacheKey('java', username); const cached = this.cache.get(cacheKey); if (cached && this.isCacheValid((cached as any).timestamp)) { return cached; } try { const response = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`, { headers: { 'User-Agent': 'MinecraftResolver/1.0' } }); if (response.status === 404) { throw new Error('Player not found'); } if (response.status === 429) { throw new Error('Rate limit exceeded'); } if (!response.ok) { throw new Error(`Mojang API error: ${response.status}`); } const data: MojangResponse = await response.json(); const result: PlayerIdentifierOutput = { platform: 'java', username: data.name, uuid: this.formatUUID(data.id) }; // Cache with timestamp (result as any).timestamp = Date.now(); this.cache.set(cacheKey, result); return result; } catch (error) { throw new Error(`Failed to resolve Java username: ${error instanceof Error ? error.message : 'Unknown error'}`); } } private async resolveBedrockGamertag(gamertag: string): Promise { const cacheKey = this.getCacheKey('bedrock', gamertag); const cached = this.cache.get(cacheKey); if (cached && this.isCacheValid((cached as any).timestamp)) { return cached; } try { // Try GeyserMC API first (most reliable for public use) const response = await fetch(`https://api.geysermc.org/v2/xbox/xuid/${encodeURIComponent(gamertag)}`, { headers: { 'User-Agent': 'MinecraftResolver/1.0' } }); if (response.status === 404) { throw new Error('Gamertag not found'); } if (response.status === 429) { throw new Error('Rate limit exceeded'); } if (!response.ok) { throw new Error(`GeyserMC API error: ${response.status}`); } const data: GeyserResponse = await response.json(); const result: PlayerIdentifierOutput = { platform: 'bedrock', username: data.gamertag, xuid: data.xuid }; // Cache with timestamp (result as any).timestamp = Date.now(); this.cache.set(cacheKey, result); return result; } catch (error) { throw new Error(`Failed to resolve Bedrock gamertag: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async resolvePlayerIdentifier(input: PlayerIdentifierInput): Promise { if (!input.username || input.username.trim().length === 0) { throw new Error('Username is required'); } const trimmedUsername = input.username.trim(); if (input.type === 'java') { return this.resolveJavaUsername(trimmedUsername); } else if (input.type === 'bedrock') { return this.resolveBedrockGamertag(trimmedUsername); } else { throw new Error('Invalid platform type. Must be "java" or "bedrock"'); } } private async request( endpoint: string, options: RequestInit = {} ): Promise> { const url = `${API_BASE_URL}${endpoint}`; const headers: Record = { 'Content-Type': 'application/json', ...(options.headers as Record || {}), }; if (API_KEY) { headers.Authorization = `Bearer ${API_KEY}`; } try { const response = await fetch(url, { ...options, headers, }); const data = await response.json(); if (!response.ok) { return { success: false, error: data.error || 'Request failed', details: data.details, }; } return { success: true, data, message: data.message, }; } catch (error) { return { success: false, error: 'Network error', details: error instanceof Error ? error.message : 'Unknown error', }; } } async rewardPlayer(playerName: string): Promise { return this.request('/reward', { method: 'POST', body: JSON.stringify({ player: playerName }), }); } async addToWhitelist(playerName: string): Promise { return this.request('/api/whitelist/add', { method: 'POST', body: JSON.stringify({ player: playerName }), }); } async removeFromWhitelist(playerName: string): Promise { return this.request('/api/whitelist/remove', { method: 'POST', body: JSON.stringify({ player: playerName }), }); } async getWhitelist(): Promise> { return this.request('/api/whitelist/list', { method: 'GET', }); } async executeCommand(command: string): Promise> { return this.request('/api/command', { method: 'POST', body: JSON.stringify({ command }), }); } } export const minecraftApi = new MinecraftApiClient(); export type { ApiResponse, PlayerIdentifierInput, PlayerIdentifierOutput };