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", "
", $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 '' . htmlspecialchars($text) . ''; } } // 处理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']); } ?>