291 lines
8.9 KiB
PHP
Executable File
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']);
|
|
}
|
|
?>
|