whoops, we do need xbox api

This commit is contained in:
anonymousratwastaken 2025-12-20 07:47:49 +00:00
parent 4b467cba81
commit 9e24d86140
7 changed files with 431 additions and 19 deletions

View file

@ -0,0 +1,41 @@
import { NextRequest, NextResponse } from 'next/server';
import { minecraftApi, PlayerIdentifierInput } from '@/lib/minecraft-api';
export async function POST(request: NextRequest) {
try {
const body: PlayerIdentifierInput = await request.json();
// Validate input
if (!body.type || !body.username) {
return NextResponse.json(
{ error: 'Missing required fields: type and username' },
{ status: 400 }
);
}
if (body.type !== 'java' && body.type !== 'bedrock') {
return NextResponse.json(
{ error: 'Invalid type. Must be "java" or "bedrock"' },
{ status: 400 }
);
}
const result = await minecraftApi.resolvePlayerIdentifier(body);
return NextResponse.json({
success: true,
data: result
});
} catch (error) {
console.error('Player resolution error:', error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'
},
{ status: 500 }
);
}
}

View file

@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { servertap } from "@/lib/servertap";
import { minecraftApi } from "@/lib/minecraft-api";
import { verifyAccessCode } from "@/app/actions";
type WhitelistRequest = {
@ -9,6 +10,13 @@ type WhitelistRequest = {
accessCode: string;
};
type ResolvedPlayer = {
platform: "java" | "bedrock";
username: string;
uuid?: string;
xuid?: string;
};
export async function POST(request: Request) {
try {
const data = (await request.json()) as WhitelistRequest;
@ -28,25 +36,45 @@ export async function POST(request: Request) {
const results: string[] = [];
const errors: string[] = [];
const resolvedPlayers: ResolvedPlayer[] = [];
// Handle Java whitelist
// Resolve Java player if provided
if ((preference === "java" || preference === "both") && javaName?.trim()) {
const result = await servertap.addToWhitelist(javaName.trim());
if (result.success) {
results.push(`Successfully whitelisted Java player: ${javaName}`);
} else {
errors.push(`Failed to whitelist Java player ${javaName}: ${result.error}`);
try {
const resolved = await minecraftApi.resolvePlayerIdentifier({
type: 'java',
username: javaName.trim()
});
resolvedPlayers.push(resolved);
} catch (error) {
errors.push(`Failed to resolve Java player ${javaName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Handle Bedrock whitelist (for now, we'll use the same endpoint but could be different)
// Resolve Bedrock player if provided
if ((preference === "bedrock" || preference === "both") && bedrockName?.trim()) {
const bedrockNameWithDot = `.${bedrockName.trim()}`;
const result = await servertap.addToWhitelist(bedrockNameWithDot);
try {
const resolved = await minecraftApi.resolvePlayerIdentifier({
type: 'bedrock',
username: bedrockName.trim()
});
resolvedPlayers.push(resolved);
} catch (error) {
errors.push(`Failed to resolve Bedrock player ${bedrockName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Add resolved players to whitelist
for (const player of resolvedPlayers) {
try {
const result = await servertap.addToWhitelist(player);
if (result.success) {
results.push(`Successfully whitelisted Bedrock player: ${bedrockNameWithDot}`);
results.push(`Successfully whitelisted ${player.platform} player: ${player.username} (${player.uuid || player.xuid})`);
} else {
errors.push(`Failed to whitelist Bedrock player ${bedrockNameWithDot}: ${result.error}`);
errors.push(`Failed to whitelist ${player.platform} player ${player.username}: ${result.error}`);
}
} catch (error) {
errors.push(`Failed to whitelist ${player.platform} player ${player.username}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}

View file

@ -9,7 +9,147 @@ interface ApiResponse<T = any> {
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<string, PlayerIdentifierOutput>();
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<PlayerIdentifierOutput> {
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<PlayerIdentifierOutput> {
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<PlayerIdentifierOutput> {
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<T = any>(
endpoint: string,
options: RequestInit = {}
@ -90,4 +230,4 @@ class MinecraftApiClient {
}
export const minecraftApi = new MinecraftApiClient();
export type { ApiResponse };
export type { ApiResponse, PlayerIdentifierInput, PlayerIdentifierOutput };

View file

@ -4,6 +4,13 @@ type MinecraftPlayer = {
name: string;
};
type ResolvedPlayer = {
platform: "java" | "bedrock";
username: string;
uuid?: string;
xuid?: string;
};
type MinecraftResponse<T> = {
success: boolean;
data?: T;
@ -72,17 +79,61 @@ class MinecraftClient {
}
}
async addToWhitelist(playerName: string): Promise<MinecraftResponse<void>> {
async addToWhitelist(player: string | ResolvedPlayer): Promise<MinecraftResponse<void>> {
// Handle legacy string input for backward compatibility
if (typeof player === 'string') {
return this.makeRequest("/api/whitelist/add", {
method: "POST",
body: JSON.stringify({ player: playerName }),
body: JSON.stringify({ player }),
});
}
async removeFromWhitelist(playerName: string): Promise<MinecraftResponse<void>> {
// Send resolved player data with UUID/XUID
const payload: any = {
player: player.username,
platform: player.platform,
};
if (player.uuid) {
payload.uuid = player.uuid;
}
if (player.xuid) {
payload.xuid = player.xuid;
}
return this.makeRequest("/api/whitelist/add", {
method: "POST",
body: JSON.stringify(payload),
});
}
async removeFromWhitelist(player: string | ResolvedPlayer): Promise<MinecraftResponse<void>> {
// Handle legacy string input for backward compatibility
if (typeof player === 'string') {
return this.makeRequest("/api/whitelist/remove", {
method: "POST",
body: JSON.stringify({ player: playerName }),
body: JSON.stringify({ player }),
});
}
// Send resolved player data with UUID/XUID
const payload: any = {
player: player.username,
platform: player.platform,
};
if (player.uuid) {
payload.uuid = player.uuid;
}
if (player.xuid) {
payload.xuid = player.xuid;
}
return this.makeRequest("/api/whitelist/remove", {
method: "POST",
body: JSON.stringify(payload),
});
}
@ -111,4 +162,4 @@ export const minecraft = new MinecraftClient();
// Legacy exports for backward compatibility
export const servertap = minecraft;
export type { MinecraftPlayer as ServerTapPlayer, MinecraftResponse as ServerTapResponse };
export type { MinecraftPlayer as ServerTapPlayer, MinecraftResponse as ServerTapResponse, ResolvedPlayer };

28
test-api.sh Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
# Test script for the player resolver API endpoint
# Start the dev server first with: npm run dev
echo "Testing Java player resolution..."
curl -X POST http://localhost:3000/api/resolve-player \
-H "Content-Type: application/json" \
-d '{"type": "java", "username": "Notch"}' \
| jq .
echo -e "\nTesting Bedrock player resolution..."
curl -X POST http://localhost:3000/api/resolve-player \
-H "Content-Type: application/json" \
-d '{"type": "bedrock", "username": "TestGamertag"}' \
| jq .
echo -e "\nTesting invalid input..."
curl -X POST http://localhost:3000/api/resolve-player \
-H "Content-Type: application/json" \
-d '{"type": "invalid", "username": ""}' \
| jq .
echo -e "\nTesting missing fields..."
curl -X POST http://localhost:3000/api/resolve-player \
-H "Content-Type: application/json" \
-d '{}' \
| jq .

View file

@ -0,0 +1,65 @@
#!/bin/bash
# Test script for the integrated whitelist with player resolver
# This tests the full flow: resolve player -> send UUID/XUID to server
echo "Testing integrated whitelist with player resolver..."
# Test Java player with valid access code (you'll need to replace with actual access code)
echo -e "\n=== Testing Java Player Resolution + Whitelist ==="
curl -X POST http://localhost:3000/api/whitelist \
-H "Content-Type: application/json" \
-d '{
"javaName": "Notch",
"preference": "java",
"accessCode": "YOUR_ACCESS_CODE_HERE"
}' \
| jq .
# Test Bedrock player with valid access code
echo -e "\n=== Testing Bedrock Player Resolution + Whitelist ==="
curl -X POST http://localhost:3000/api/whitelist \
-H "Content-Type: application/json" \
-d '{
"bedrockName": "TestGamertag",
"preference": "bedrock",
"accessCode": "YOUR_ACCESS_CODE_HERE"
}' \
| jq .
# Test both platforms
echo -e "\n=== Testing Both Platforms Resolution + Whitelist ==="
curl -X POST http://localhost:3000/api/whitelist \
-H "Content-Type: application/json" \
-d '{
"javaName": "Notch",
"bedrockName": "TestGamertag",
"preference": "both",
"accessCode": "YOUR_ACCESS_CODE_HERE"
}' \
| jq .
# Test invalid access code
echo -e "\n=== Testing Invalid Access Code ==="
curl -X POST http://localhost:3000/api/whitelist \
-H "Content-Type: application/json" \
-d '{
"javaName": "Notch",
"preference": "java",
"accessCode": "invalid_code"
}' \
| jq .
# Test invalid username
echo -e "\n=== Testing Invalid Username ==="
curl -X POST http://localhost:3000/api/whitelist \
-H "Content-Type: application/json" \
-d '{
"javaName": "nonexistentplayer12345",
"preference": "java",
"accessCode": "YOUR_ACCESS_CODE_HERE"
}' \
| jq .
echo -e "\n=== Test completed ==="
echo "Note: Replace YOUR_ACCESS_CODE_HERE with a valid access code from your system"

59
test-player-resolver.js Normal file
View file

@ -0,0 +1,59 @@
// Simple test script for the player resolver
// Run with: node test-player-resolver.js
const { minecraftApi } = require('./src/lib/minecraft-api.ts');
async function testJavaPlayer() {
console.log('Testing Java player resolution...');
try {
const result = await minecraftApi.resolvePlayerIdentifier({
type: 'java',
username: 'Notch' // Famous Minecraft player
});
console.log('Java player result:', result);
} catch (error) {
console.error('Java player error:', error.message);
}
}
async function testBedrockPlayer() {
console.log('Testing Bedrock player resolution...');
try {
const result = await minecraftApi.resolvePlayerIdentifier({
type: 'bedrock',
username: 'TestGamertag' // This will likely fail but tests the API
});
console.log('Bedrock player result:', result);
} catch (error) {
console.error('Bedrock player error:', error.message);
}
}
async function testInvalidInput() {
console.log('Testing invalid input...');
try {
await minecraftApi.resolvePlayerIdentifier({
type: 'invalid',
username: ''
});
} catch (error) {
console.error('Expected error for invalid input:', error.message);
}
}
async function runTests() {
console.log('Starting player resolver tests...\n');
await testJavaPlayer();
console.log('\n');
await testBedrockPlayer();
console.log('\n');
await testInvalidInput();
console.log('\n');
console.log('Tests completed!');
}
runTests().catch(console.error);