CCSWebsite/api.php
2025-07-04 23:49:18 +00:00

291 lines
8.9 KiB
PHP
Executable File

<?php
// Minecraft 服务器查询类
class MinecraftServerQuery {
private $socket;
private $host;
private $port;
private $timeout;
private $info = [];
private $ping;
public function __construct($host, $port = 25565, $timeout = 3) {
$this->host = $host;
$this->port = $port;
$this->timeout = $timeout;
}
public function connect() {
// 记录开始时间计算延迟
$start = microtime(true);
// 创建 socket 连接
$this->socket = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
if (!$this->socket) {
return false;
}
// 设置超时
stream_set_timeout($this->socket, $this->timeout);
// 发送握手包
$this->sendHandshake();
// 发送状态请求
$this->sendStatusRequest();
// 读取状态响应
$this->readStatusResponse();
// 计算延迟
$this->ping = round((microtime(true) - $start) * 1000);
// 关闭连接
fclose($this->socket);
return true;
}
private function sendHandshake() {
$packet = pack('c', 0x00); // 包ID (Handshake)
$packet .= pack('c', 0x04); // 协议版本 (4 for 1.7.2+)
$packet .= pack('c', strlen($this->host)) . $this->host; // 服务器地址
$packet .= pack('n', $this->port); // 服务器端口
$packet .= pack('c', 0x01); // 下一步状态 (1 for Status)
$packet = pack('c', strlen($packet)) . $packet; // 添加包长度前缀
fwrite($this->socket, $packet);
}
private function sendStatusRequest() {
$packet = pack('c', 0x00); // 包ID (Status Request)
$packet = pack('c', strlen($packet)) . $packet;
fwrite($this->socket, $packet);
}
private function readStatusResponse() {
// 读取包长度
$length = $this->readVarInt();
// 读取包ID
$packetID = $this->readVarInt();
if ($packetID !== 0x00) {
return false; // 不是状态响应包
}
// 读取JSON数据长度
$jsonLength = $this->readVarInt();
// 读取JSON数据
$json = '';
$bytesRead = 0;
while ($bytesRead < $jsonLength) {
$chunk = fread($this->socket, $jsonLength - $bytesRead);
if ($chunk === false) break;
$json .= $chunk;
$bytesRead += strlen($chunk);
}
$this->info = json_decode($json, true);
return true;
}
private function readVarInt() {
$value = 0;
$size = 0;
$b;
do {
$b = ord(fgetc($this->socket));
$value |= ($b & 0x7F) << ($size++ * 7);
if ($size > 5) {
throw new Exception('VarInt too big');
}
} while (($b & 0x80) == 0x80);
return $value;
}
public function getInfo() {
return $this->info;
}
public function getPing() {
return $this->ping;
}
public function formatMotd($motd) {
// 颜色代码映射
$colors = [
'0' => '#000000', '1' => '#0000AA', '2' => '#00AA00', '3' => '#00AAAA',
'4' => '#AA0000', '5' => '#AA00AA', '6' => '#FFAA00', '7' => '#AAAAAA',
'8' => '#555555', '9' => '#5555FF', 'a' => '#55FF55', 'b' => '#55FFFF',
'c' => '#FF5555', 'd' => '#FF55FF', 'e' => '#FFFF55', 'f' => '#FFFFFF'
];
// 格式化代码映射
$formats = [
'l' => 'font-weight:bold', // 粗体
'm' => 'text-decoration:line-through', // 删除线
'n' => 'text-decoration:underline', // 下划线
'o' => 'font-style:italic', // 斜体
'r' => '' // 重置
];
$html = '';
$currentStyles = [];
$buffer = '';
$chars = preg_split('//u', $motd, -1, PREG_SPLIT_NO_EMPTY);
for ($i = 0; $i < count($chars); $i++) {
$char = $chars[$i];
if ($char === '§' && isset($chars[$i + 1])) {
$code = $chars[$i + 1];
$i++;
// 处理颜色代码
if (isset($colors[strtolower($code)])) {
if ($buffer !== '') {
$html .= $this->wrapWithStyles($buffer, $currentStyles);
$buffer = '';
}
$currentStyles['color'] = $colors[strtolower($code)];
}
// 处理格式代码
elseif (isset($formats[strtolower($code)])) {
if ($buffer !== '') {
$html .= $this->wrapWithStyles($buffer, $currentStyles);
$buffer = '';
}
if (strtolower($code) === 'r') {
$currentStyles = []; // 重置所有样式
} else {
$currentStyles[] = $formats[strtolower($code)];
}
}
// 忽略未知代码
else {
$buffer .= $char . $code;
}
} else {
$buffer .= $char;
}
}
if ($buffer !== '') {
$html .= $this->wrapWithStyles($buffer, $currentStyles);
}
// 处理换行符
$html = str_replace("\n", "<br>", $html);
return $html;
}
private function wrapWithStyles($text, $styles) {
if (empty($styles)) {
return htmlspecialchars($text);
}
$styleString = '';
foreach ($styles as $key => $value) {
if (is_numeric($key)) {
$styleString .= $value . ';';
} else {
$styleString .= $key . ':' . $value . ';';
}
}
return '<span style="' . $styleString . '">' . htmlspecialchars($text) . '</span>';
}
}
// 处理API请求
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// 获取查询参数
$host = isset($_GET['host']) ? $_GET['host'] : '';
$port = isset($_GET['port']) ? (int)$_GET['port'] : 25565;
// 验证输入
if (empty($host)) {
http_response_code(400);
echo json_encode(['error' => 'Missing host parameter']);
exit;
}
// 创建服务器查询实例
$server = new MinecraftServerQuery($host, $port);
try {
// 尝试连接并获取信息
$connected = $server->connect();
if ($connected) {
$info = $server->getInfo();
$ping = $server->getPing();
// 处理玩家列表
$players = [];
if (isset($info['players']['sample'])) {
foreach ($info['players']['sample'] as $player) {
$players[] = $player['name'];
}
}
// 格式化MOTD
$motd = '';
$formattedMotd = '';
if (isset($info['description'])) {
if (is_string($info['description'])) {
$motd = $info['description'];
$formattedMotd = $server->formatMotd($motd);
} elseif (is_array($info['description']) && isset($info['description']['text'])) {
$motd = $info['description']['text'];
$formattedMotd = $server->formatMotd($motd);
}
}
// 构建响应数据
$response = [
'online' => true,
'host' => $host,
'port' => $port,
'ping' => $ping,
'version' => $info['version']['name'] ?? 'Unknown',
'protocol' => $info['version']['protocol'] ?? -1,
'online_players' => $info['players']['online'] ?? 0,
'max_players' => $info['players']['max'] ?? 0,
'motd' => $motd,
'formatted_motd' => $formattedMotd,
'players' => $players
];
echo json_encode($response);
} else {
http_response_code(500);
echo json_encode([
'online' => false,
'error' => '无法连接到服务器',
'host' => $host,
'port' => $port
]);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'online' => false,
'error' => $e->getMessage(),
'host' => $host,
'port' => $port
]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>