CCSWebsite/console/motd.html
2025-07-04 23:49:18 +00:00

649 lines
24 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>