CCSWebsite/console/motd.html

649 lines
24 KiB
HTML
Raw Permalink Normal View History

2025-07-04 23:49:18 +00:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CCS | Minecraft服务器在线状态查询</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#5865F2',
secondary: '#3BA55C',
danger: '#ED4245',
dark: '#23272A',
light: '#F2F3F5',
},
fontFamily: {
minecraft: ['"Minecraft"', 'sans-serif'],
},
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.minecraft-font {
font-family: 'Minecraft', sans-serif;
letter-spacing: 1px;
}
.pixel-borders {
box-shadow: 0 -4px 0 4px rgba(0, 0, 0, 0.2),
0 4px 0 4px rgba(255, 255, 255, 0.2),
-4px 0 0 4px rgba(0, 0, 0, 0.2),
4px 0 0 4px rgba(255, 255, 255, 0.2);
}
.animate-pulse-slow {
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
.minecraft-obfuscated {
animation: obfuscate 0.5s steps(4) infinite;
}
.loading-overlay {
@apply fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-40 opacity-0 pointer-events-none transition-opacity duration-300;
}
.loading-overlay.active {
@apply opacity-100 pointer-events-auto;
}
}
@keyframes obfuscate {
0% { opacity: 1; }
25% { opacity: 0.25; }
50% { opacity: 0.5; }
75% { opacity: 0.75; }
100% { opacity: 1; }
}
.server-card {
transition: all 0.3s ease;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
}
.server-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3);
}
.player-avatar {
transition: transform 0.3s ease;
}
.player-avatar:hover {
transform: scale(1.1);
}
</style>
<!-- 加载Minecraft字体 -->
<style>
@font-face {
font-family: 'Minecraft';
src: url('https://fonts.cdnfonts.com/css/minecraft-4');
font-display: swap;
}
</style>
</head>
<body class="bg-gradient-to-br from-gray-900 to-gray-800 min-h-screen text-light">
<!-- 页面顶部装饰 -->
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary via-secondary to-danger"></div>
<div class="container mx-auto px-4 py-12 max-w-4xl">
<!-- 标题区域 -->
<header class="text-center mb-12">
<h1 class="text-[clamp(2rem,5vw,3.5rem)] font-bold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary minecraft-font tracking-wider">
Minecraft(JE)状态查询
</h1>
<p class="text-gray-300 text-lg max-w-2xl mx-auto">
CCSNetwork | 圆周云境技术部门
</p>
<!-- 自动刷新指示器 -->
<div id="autoRefreshIndicator" class="mt-4 text-sm text-gray-400">
<span id="refreshCountdown">20</span>秒后自动刷新
</div>
</header>
<!-- 主内容区 -->
<main class="bg-dark/80 backdrop-blur-md rounded-xl p-6 border border-gray-700/50 relative server-card">
<!-- 加载遮罩 -->
<div id="loadingOverlay" class="loading-overlay">
<div class="bg-dark p-8 rounded-lg shadow-xl flex flex-col items-center">
<div class="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-primary mb-4"></div>
<p class="text-gray-300">正在刷新服务器状态...</p>
</div>
</div>
<!-- 服务器输入表单 -->
<div class="mb-8">
<form id="serverForm" class="flex flex-col sm:flex-row gap-4">
<div class="flex-1 relative">
<label for="serverAddress" class="block text-sm font-medium text-gray-300 mb-1">服务器地址</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
<i class="fa fa-server"></i>
</span>
<input
type="text"
id="serverAddress"
placeholder="play.example.com"
value="mc.xinpan.xin"
class="w-full bg-gray-800 border border-gray-700 rounded-lg py-3 pl-10 pr-4 focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all duration-300"
required
>
</div>
</div>
<div class="w-full sm:w-32">
<label for="serverPort" class="block text-sm font-medium text-gray-300 mb-1">端口 (可选)</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
<i class="fa fa-link"></i>
</span>
<input
type="number"
id="serverPort"
placeholder="25565"
value="17112"
min="1"
max="65535"
class="w-full bg-gray-800 border border-gray-700 rounded-lg py-3 pl-10 pr-4 focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all duration-300"
>
</div>
</div>
<div class="w-full sm:w-auto flex items-end">
<button
type="submit"
id="fetchButton"
class="w-full sm:w-auto bg-gradient-to-r from-primary to-primary/80 hover:from-primary/90 hover:to-primary/70 text-white font-semibold py-3 px-6 rounded-lg shadow-lg hover:shadow-primary/30 transform hover:-translate-y-0.5 transition-all duration-300 flex items-center justify-center gap-2"
>
<i class="fa fa-search"></i>
<span>查询服务器</span>
</button>
</div>
</form>
</div>
<!-- 服务器信息结果区域 -->
<div id="serverInfo" class="hidden mt-8">
<!-- 服务器状态指示器 -->
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<div id="statusIndicator" class="w-4 h-4 rounded-full bg-danger"></div>
<h2 id="serverName" class="text-xl font-bold text-white"></h2>
</div>
<div class="text-sm text-gray-400">
<span id="lastUpdated">刚刚更新</span>
</div>
</div>
<!-- 服务器MOTD显示 -->
<div id="serverMotd" class="bg-gray-800/50 rounded-lg p-4 mb-6 border border-gray-700/30 pixel-borders min-h-[100px] flex items-center justify-center minecraft-font text-lg text-center">
<!-- MOTD将在这里显示 -->
</div>
<!-- 服务器详细信息 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 hover:border-primary/50 transition-all duration-300">
<div class="text-gray-400 text-sm mb-1">版本</div>
<div id="serverVersion" class="font-semibold"></div>
</div>
<div class="bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 hover:border-primary/50 transition-all duration-300">
<div class="text-gray-400 text-sm mb-1">在线玩家</div>
<div id="playerCount" class="font-semibold"></div>
</div>
<div class="bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 hover:border-primary/50 transition-all duration-300">
<div class="text-gray-400 text-sm mb-1">延迟</div>
<div id="serverPing" class="font-semibold"></div>
</div>
</div>
<!-- 玩家列表 -->
<div id="playersSection" class="hidden bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 mb-6">
<h3 class="text-lg font-semibold mb-3 flex items-center gap-2">
<i class="fa fa-users"></i>
<span>在线玩家</span>
<span id="playerCountBadge" class="bg-primary/20 text-primary text-xs px-2 py-0.5 rounded-full"></span>
</h3>
<div id="playerList" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2">
<!-- 玩家头像将在这里动态生成 -->
</div>
</div>
<!-- 服务器图标 -->
<div id="serverIconSection" class="hidden">
<h3 class="text-lg font-semibold mb-3 flex items-center gap-2">
<i class="fa fa-image"></i>
<span>服务器图标</span>
</h3>
<div class="bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 flex justify-center">
<img id="serverIcon" src="" alt="服务器图标" class="rounded-lg max-w-[128px] max-h-[128px]">
</div>
</div>
<!-- 调试信息(仅开发阶段显示) -->
<div id="debugInfo" class="hidden mt-6 bg-gray-800/50 rounded-lg p-4 border border-gray-700/30">
<h3 class="text-lg font-semibold mb-2 text-gray-300 flex items-center justify-between">
<span>调试信息</span>
<button id="closeDebugBtn" class="text-gray-400 hover:text-white transition-colors">
<i class="fa fa-times"></i>
</button>
</h3>
<pre id="rawData" class="text-xs text-gray-400 max-h-64 overflow-y-auto"></pre>
</div>
</div>
<!-- 错误信息区域 -->
<div id="errorMessage" class="hidden bg-danger/10 border border-danger/30 rounded-lg p-4 mt-6">
<div class="flex items-start gap-3">
<div class="text-danger mt-0.5">
<i class="fa fa-exclamation-triangle"></i>
</div>
<div>
<h3 class="font-semibold text-danger">无法获取服务器信息</h3>
<p id="errorText" class="text-sm text-gray-300 mt-1"></p>
</div>
</div>
</div>
</main>
<footer class="mt-12 text-center text-gray-500 text-sm">
<p>© 2025 圆周云境. | 数据通过MCAPI获取</p>
<p class="mt-2">本工具不隶属于Minecraft或Mojang Studios</p>
</footer>
</div>
<script>
// 全局变量
let autoRefreshInterval = null;
let refreshCountdown = 20;
let currentServer = 'mc.xinpan.xin';
let currentPort = 17112;
let debugInfoVisible = false; // 跟踪调试信息框的显示状态
// 颜色代码转换函数 - 将Minecraft颜色代码转换为HTML颜色
function convertMinecraftColors(text) {
if (!text) return '';
// 替换Minecraft颜色代码
const colorMap = {
'§0': '<span style="color: #000000;">', // 黑色
'§1': '<span style="color: #0000AA;">', // 深蓝色
'§2': '<span style="color: #00AA00;">', // 深绿色
'§3': '<span style="color: #00AAAA;">', // 湖蓝色
'§4': '<span style="color: #AA0000;">', // 深红色
'§5': '<span style="color: #AA00AA;">', // 紫色
'§6': '<span style="color: #FFAA00;">', // 金色
'§7': '<span style="color: #AAAAAA;">', // 灰色
'§8': '<span style="color: #555555;">', // 深灰色
'§9': '<span style="color: #5555FF;">', // 蓝色
'§a': '<span style="color: #55FF55;">', // 绿色
'§b': '<span style="color: #55FFFF;">', // 天蓝色
'§c': '<span style="color: #FF5555;">', // 红色
'§d': '<span style="color: #FF55FF;">', // 粉色
'§e': '<span style="color: #FFFF55;">', // 黄色
'§f': '<span style="color: #FFFFFF;">', // 白色
'§k': '<span class="minecraft-obfuscated">', // 随机字符(混淆)
'§l': '<span style="font-weight: bold;">', // 粗体
'§m': '<span style="text-decoration: line-through;">', // 删除线
'§n': '<span style="text-decoration: underline;">', // 下划线
'§o': '<span style="font-style: italic;">', // 斜体
'§r': '</span>' // 重置
};
// 处理特殊格式
let formattedText = text;
// 先处理换行符
formattedText = formattedText.replace(/\n/g, '<br>');
// 处理颜色代码
Object.keys(colorMap).forEach(code => {
formattedText = formattedText.replace(new RegExp(code, 'g'), colorMap[code]);
});
// 确保所有标签都有闭合标签
const openTags = (formattedText.match(/<span/g) || []).length;
const closeTags = (formattedText.match(/<\/span>/g) || []).length;
if (openTags > closeTags) {
formattedText += '</span>'.repeat(openTags - closeTags);
}
return formattedText;
}
// 解析MOTD对象为文本
function parseMotd(motd) {
if (!motd) return '';
// 处理特殊的MCAPI格式包含clean/html/raw数组
if (motd.raw && Array.isArray(motd.raw)) {
return motd.raw.join('\n');
}
// 处理字符串格式
if (typeof motd === 'string') {
return motd;
}
// 处理JSON对象格式
if (motd.text) {
return motd.text;
}
// 处理包含extra数组的格式
if (motd.extra && Array.isArray(motd.extra)) {
return motd.extra.map(part => {
// 处理嵌套的extra数组
if (part.extra && Array.isArray(part.extra)) {
return part.extra.map(p => p.text || '').join('');
}
return part.text || '';
}).join('');
}
// 未知格式
console.warn('无法解析MOTD格式:', motd);
return JSON.stringify(motd, null, 2);
}
// 更新最后更新时间
function updateLastUpdatedTime() {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
document.getElementById('lastUpdated').textContent = `最后更新: ${hours}:${minutes}:${seconds}`;
}
// 显示错误信息
function showError(message) {
document.getElementById('serverInfo').classList.add('hidden');
document.getElementById('errorMessage').classList.remove('hidden');
document.getElementById('errorText').textContent = message;
stopAutoRefresh(); // 发生错误时停止自动刷新
hideLoading(); // 确保在出错时隐藏加载遮罩
}
// 隐藏错误信息
function hideError() {
document.getElementById('errorMessage').classList.add('hidden');
}
// 显示加载状态
function showLoading() {
const loadingOverlay = document.getElementById('loadingOverlay');
loadingOverlay.classList.add('active');
hideError();
}
// 隐藏加载状态
function hideLoading() {
const loadingOverlay = document.getElementById('loadingOverlay');
loadingOverlay.classList.remove('active');
}
// 更新倒计时显示
function updateCountdown() {
const countdownEl = document.getElementById('refreshCountdown');
if (countdownEl) {
countdownEl.textContent = refreshCountdown;
}
}
// 开始自动刷新
function startAutoRefresh() {
if (!currentServer) return; // 没有当前服务器,不启动自动刷新
refreshCountdown = 20;
updateCountdown();
// 清除之前的定时器
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
// 设置新的定时器
autoRefreshInterval = setInterval(() => {
refreshCountdown--;
updateCountdown();
if (refreshCountdown <= 0) {
fetchServerInfo(currentServer, currentPort);
refreshCountdown = 20;
updateLastUpdatedTime();
}
}, 1000);
}
// 停止自动刷新
function stopAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
}
// 获取服务器信息
async function fetchServerInfo(address, port = 25565) {
try {
// 保存当前服务器和端口
currentServer = address;
currentPort = port;
showLoading();
// 使用MCAPI获取服务器信息
const response = await fetch(`https://api.mcsrvstat.us/3/${address}:${port}`);
if (!response.ok) {
throw new Error(`API请求失败: ${response.status}`);
}
const data = await response.json();
console.log('服务器数据:', data); // 调试日志
// 保存调试信息框的当前状态
debugInfoVisible = !document.getElementById('debugInfo').classList.contains('hidden');
return data;
} catch (error) {
console.error('获取服务器信息时出错:', error);
throw error;
} finally {
// 无论成功或失败,都隐藏加载遮罩
hideLoading();
}
}
// 显示服务器信息
function displayServerInfo(data) {
// 显示服务器信息区域
document.getElementById('serverInfo').classList.remove('hidden');
// 更新服务器名称和状态
const statusIndicator = document.getElementById('statusIndicator');
const serverName = document.getElementById('serverName');
if (data.online) {
statusIndicator.className = 'w-4 h-4 rounded-full bg-secondary animate-pulse-slow';
serverName.textContent = data.hostname || currentServer;
} else {
statusIndicator.className = 'w-4 h-4 rounded-full bg-danger';
serverName.textContent = `${data.hostname || currentServer} (离线)`;
}
// 更新MOTD
const serverMotd = document.getElementById('serverMotd');
if (data.motd) {
const motdText = parseMotd(data.motd);
if (motdText) {
serverMotd.innerHTML = convertMinecraftColors(motdText);
} else {
serverMotd.textContent = 'MOTD为空';
}
} else {
serverMotd.textContent = '无MOTD信息';
}
// 更新版本信息
document.getElementById('serverVersion').textContent = data.version || '未知版本';
// 更新玩家信息
const playerCount = document.getElementById('playerCount');
const playersSection = document.getElementById('playersSection');
const playerList = document.getElementById('playerList');
const playerCountBadge = document.getElementById('playerCountBadge');
if (data.players && data.players.online !== undefined) {
playerCount.textContent = `${data.players.online}/${data.players.max}`;
if (data.players.list && data.players.list.length > 0) {
playerCountBadge.textContent = `${data.players.list.length} 名玩家`;
playersSection.classList.remove('hidden');
// 清空玩家列表
playerList.innerHTML = '';
// 添加玩家头像
data.players.list.forEach(player => {
const playerCard = document.createElement('div');
playerCard.className = 'bg-gray-700/50 rounded-lg p-2 flex flex-col items-center hover:bg-gray-700 transition-colors duration-300';
playerCard.innerHTML = `
<img src="https://minotar.net/avatar/${player}/64" alt="${player}的头像" class="w-12 h-12 rounded-full mb-1 player-avatar">
<span class="text-xs truncate w-full text-center">${player}</span>
`;
playerList.appendChild(playerCard);
});
} else {
playersSection.classList.add('hidden');
}
} else {
playerCount.textContent = '未知';
playersSection.classList.add('hidden');
}
// 更新延迟信息(改进版)
const pingValue = getValidPing(data);
document.getElementById('serverPing').textContent = pingValue !== null ? `${pingValue}ms` : '未知';
// 更新服务器图标
const serverIconSection = document.getElementById('serverIconSection');
const serverIcon = document.getElementById('serverIcon');
if (data.icon) {
serverIconSection.classList.remove('hidden');
serverIcon.src = data.icon;
} else {
serverIconSection.classList.add('hidden');
}
// 更新最后更新时间
updateLastUpdatedTime();
// 恢复调试信息框的状态
if (debugInfoVisible) {
document.getElementById('debugInfo').classList.remove('hidden');
} else {
document.getElementById('debugInfo').classList.add('hidden');
}
// 启动自动刷新(即使之前出错)
startAutoRefresh();
}
// 获取有效的ping值
function getValidPing(data) {
// 尝试从不同位置获取ping值
if (typeof data.ping === 'number') {
return Math.round(data.ping);
}
if (data.debug && typeof data.debug.ping === 'number') {
return Math.round(data.debug.ping);
}
// 检查可能的其他位置...
return null; // 没有找到有效的ping值
}
// 表单提交处理
document.getElementById('serverForm').addEventListener('submit', async function(e) {
e.preventDefault();
const address = document.getElementById('serverAddress').value.trim();
const portInput = document.getElementById('serverPort').value;
const port = portInput ? parseInt(portInput, 10) : 25565;
console.log('提交请求:', address, port); // 调试日志
if (!address) {
showError('请输入有效的服务器地址');
return;
}
try {
const data = await fetchServerInfo(address, port);
if (!data) {
throw new Error('未获取到服务器数据');
}
// 显示原始数据(用于调试)
document.getElementById('rawData').textContent = JSON.stringify(data, null, 2);
displayServerInfo(data);
} catch (error) {
showError(`获取服务器信息失败: ${error.message}`);
}
});
// 关闭调试信息按钮
document.getElementById('closeDebugBtn').addEventListener('click', function() {
document.getElementById('debugInfo').classList.add('hidden');
debugInfoVisible = false;
});
// 页面加载完成后自动查询初始服务器
document.addEventListener('DOMContentLoaded', function() {
// 自动查询初始服务器
fetchServerInfo(currentServer, currentPort)
.then(data => {
if (data) {
// 显示原始数据(用于调试)
document.getElementById('rawData').textContent = JSON.stringify(data, null, 2);
displayServerInfo(data);
}
})
.catch(error => {
showError(`获取初始服务器信息失败: ${error.message}`);
});
// 添加键盘快捷键按F12显示/隐藏调试信息)
document.addEventListener('keydown', function(e) {
if (e.key === 'F12') {
e.preventDefault();
const debugInfo = document.getElementById('debugInfo');
debugInfo.classList.toggle('hidden');
debugInfoVisible = !debugInfo.classList.contains('hidden');
// 如果显示调试信息,则更新原始数据
if (debugInfoVisible) {
fetchServerInfo(currentServer, currentPort)
.then(data => {
if (data) {
document.getElementById('rawData').textContent = JSON.stringify(data, null, 2);
}
})
.catch(error => {
console.error('更新调试信息失败:', error);
});
}
}
});
});
</script>
</body>
</html>