2025-12-16 08:40:32 +09:00
|
|
|
import { status } from "minecraft-server-util";
|
|
|
|
|
|
|
|
|
|
const MOD_API_URL = process.env.MOD_API_URL || "http://minecraft-server:8080";
|
|
|
|
|
|
|
|
|
|
// 캐시된 데이터
|
|
|
|
|
let cachedStatus = null;
|
|
|
|
|
let cachedPlayers = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 모드 API에서 상태 가져오기
|
|
|
|
|
*/
|
|
|
|
|
async function fetchModStatus() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${MOD_API_URL}/status`);
|
|
|
|
|
if (response.ok) return await response.json();
|
|
|
|
|
return null;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[ModAPI] 상태 조회 실패:", error.message);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* MOTD 가져오기 (마인크래프트 프로토콜 사용)
|
|
|
|
|
*/
|
|
|
|
|
async function fetchMotd() {
|
|
|
|
|
try {
|
|
|
|
|
const result = await status("minecraft-server", 25565, { timeout: 5000 });
|
|
|
|
|
return {
|
|
|
|
|
motd: result.motd?.html || null,
|
|
|
|
|
icon: result.favicon || null,
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return { motd: null, icon: null };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 클라이언트용 상태 포맷 변환
|
|
|
|
|
*/
|
|
|
|
|
function formatStatusForClient(modStatus, motdData) {
|
|
|
|
|
if (!modStatus) {
|
|
|
|
|
return {
|
|
|
|
|
online: false,
|
|
|
|
|
motd: null,
|
|
|
|
|
players: { current: 0, max: 0, list: [] },
|
|
|
|
|
version: "Unknown",
|
|
|
|
|
icon: null,
|
|
|
|
|
uptime: "오프라인",
|
2025-12-23 12:45:40 +09:00
|
|
|
tps: 0,
|
|
|
|
|
mspt: 0,
|
|
|
|
|
memoryUsedMb: 0,
|
|
|
|
|
memoryMaxMb: 0,
|
2025-12-16 08:40:32 +09:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uptimeMinutes = modStatus.uptimeMinutes || 0;
|
|
|
|
|
const days = Math.floor(uptimeMinutes / 1440);
|
|
|
|
|
const hours = Math.floor((uptimeMinutes % 1440) / 60);
|
|
|
|
|
const minutes = uptimeMinutes % 60;
|
|
|
|
|
|
|
|
|
|
let uptime;
|
|
|
|
|
if (days > 0) uptime = `${days}일 ${hours}시간`;
|
|
|
|
|
else if (hours > 0) uptime = `${hours}시간 ${minutes}분`;
|
|
|
|
|
else uptime = `${minutes}분`;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
online: modStatus.online,
|
|
|
|
|
motd: motdData?.motd || null,
|
|
|
|
|
icon: motdData?.icon || null,
|
|
|
|
|
players: {
|
|
|
|
|
current: modStatus.players?.current || 0,
|
|
|
|
|
max: modStatus.players?.max || 20,
|
|
|
|
|
list:
|
|
|
|
|
modStatus.players?.online?.map((p) => ({
|
|
|
|
|
name: p.name,
|
|
|
|
|
uuid: p.uuid,
|
|
|
|
|
id: p.uuid,
|
|
|
|
|
isOp: p.isOp,
|
|
|
|
|
})) || [],
|
|
|
|
|
},
|
|
|
|
|
version: modStatus.version || "Unknown",
|
|
|
|
|
modLoader: modStatus.modLoader || null,
|
|
|
|
|
uptime: modStatus.online ? uptime : "오프라인",
|
|
|
|
|
difficulty: modStatus.difficulty || "알 수 없음",
|
|
|
|
|
gameRules: modStatus.gameRules || {},
|
|
|
|
|
mods: modStatus.mods || [],
|
2025-12-23 12:45:40 +09:00
|
|
|
tps: modStatus.tps || 0,
|
|
|
|
|
mspt: modStatus.mspt || 0,
|
|
|
|
|
memoryUsedMb: modStatus.memoryUsedMb || 0,
|
|
|
|
|
memoryMaxMb: modStatus.memoryMaxMb || 0,
|
2025-12-16 08:40:32 +09:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 전체 플레이어 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
async function fetchAllPlayers() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${MOD_API_URL}/players`);
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
return data.players || [];
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[ModAPI] 플레이어 목록 조회 실패:", error.message);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 특정 플레이어 정보 조회
|
|
|
|
|
*/
|
|
|
|
|
async function fetchPlayerDetail(uuid) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${MOD_API_URL}/player/${uuid}`);
|
|
|
|
|
if (response.ok) return await response.json();
|
|
|
|
|
return null;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[ModAPI] 플레이어 조회 실패:", error.message);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 주기적 데이터 갱신
|
|
|
|
|
*/
|
|
|
|
|
async function refreshData() {
|
|
|
|
|
const [modStatus, motdData, players] = await Promise.all([
|
|
|
|
|
fetchModStatus(),
|
|
|
|
|
fetchMotd(),
|
|
|
|
|
fetchAllPlayers(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
cachedStatus = formatStatusForClient(modStatus, motdData);
|
|
|
|
|
cachedPlayers = players;
|
|
|
|
|
|
|
|
|
|
return { cachedStatus, cachedPlayers };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getCachedStatus = () => cachedStatus;
|
|
|
|
|
const getCachedPlayers = () => cachedPlayers;
|
|
|
|
|
|
|
|
|
|
export {
|
|
|
|
|
MOD_API_URL,
|
|
|
|
|
fetchModStatus,
|
|
|
|
|
fetchMotd,
|
|
|
|
|
formatStatusForClient,
|
|
|
|
|
fetchAllPlayers,
|
|
|
|
|
fetchPlayerDetail,
|
|
|
|
|
refreshData,
|
|
|
|
|
getCachedStatus,
|
|
|
|
|
getCachedPlayers,
|
|
|
|
|
};
|