diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c19066 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.pdm-python +/__pycache__ +uv.lock +.python-version diff --git a/BedrockServerStatus.py b/BedrockServerStatus.py new file mode 100644 index 0000000..4b13254 --- /dev/null +++ b/BedrockServerStatus.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# 此模块用于查询基岩版版服务器状态,并将其数据赋值给类以供灵活调用单个值 +from mcstatus import BedrockServer + + +class BedrockStatus: + def __init__(self, gamemode, map_name, latency, motd, version, players, online_player, max_players): + self.gamemode = gamemode + self.map_name = map_name + self.latency = latency + self.motd = motd + self.version = version + self.players = players + self.online_player = online_player + self.max_players = max_players + +def bedrock_status(ip): + try: + server = BedrockServer.lookup(ip) + print(f'正在解析: {ip}') + status = server.status() + + # 将 status 属性拆分成多个变量 + gamemode = status.gamemode # 游戏模式 + map_name = status.map_name # 地图名 + latency = status.latency # 延迟 + motd = status.description # 标题 + version = status.version.name # 版本 + + # 关于玩家信息的获取 + players = status.players # 玩家总类 + online_player = players.online # 在线玩家数 + max_players = players.max # 最大玩家数 + + return BedrockStatus( + gamemode, + map_name, + latency, + motd, + version, + players, + online_player, + max_players + ) + + except Exception as e: + return print(f"获取服务器状态时出错: {e}") \ No newline at end of file diff --git a/FormatData.py b/FormatData.py new file mode 100644 index 0000000..24f01b0 --- /dev/null +++ b/FormatData.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# 此模块用于格式化此项目的所有文本输出数据,包括那些鸡毛蒜皮的空值提示 + +# 格式化 Java 服务器状态的数据 +def format_java_data(ip, type, status): + data = { + 'ip': ip, + 'type': type, + 'latency': status.latency, + 'motd': status.motd, + 'version': status.version, + 'enforces_secure_chat': status.enforces_secure_chat, + 'forge_data': status.forge_data, + 'players': { + 'online': status.online_player, + 'max': status.max_players, + 'sample': status.sample_players + }, + 'icon': status.icon + } + + return data + +# 格式化基岩版服务器状态的数据 +def format_bedrock_data(ip, status): + data = { + 'ip': ip, + 'gamemode': status.gamemode, + 'map_name': status.map_name, + 'latency': status.latency, + 'motd': status.motd, + 'version': status.version, + 'players': { + 'online': status.online_player, + 'max': status.max_players + } + } + + return data + +def format_index(): + message = { + "message": "欢迎使用 Minecraft 服务器状态查询 API!", + "usage": { + "JavaStatus": "/java?ip= - (Required)", + "BedrockStatus": "/bedrock?ip= - (Required)" + } + } + + return message + +def format_java_index(): + message = { + "message": "缺少 IP 参数", + "usage": "/java?ip= - (Required)" + } + + return message + +def format_bedrock_index(): + message = { + "message": "缺少 IP 参数", + "usage": "/bedrock?ip= - (Required)" + } + + return message diff --git a/JavaServerStatus.py b/JavaServerStatus.py new file mode 100644 index 0000000..84dc8e4 --- /dev/null +++ b/JavaServerStatus.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# 此模块用于查询 Java 版服务器状态,并将其数据赋值给类以供灵活调用单个值 +from mcstatus import JavaServer + +class JavaStatus: + def __init__(self, enforces_secure_chat, forge_data, icon, latency, motd, version, players, online_player, max_players, sample_players): + self.enforces_secure_chat = enforces_secure_chat + self.forge_data = forge_data + self.icon = icon + self.latency = latency + self.motd = motd + self.version = version + self.players = players + self.online_player = online_player + self.max_players = max_players + self.sample_players = sample_players + +def java_status(ip): + try: + print(f'正在解析: {ip}') + server = JavaServer.lookup(ip) + status = server.status() + + # 将 status 属性拆分成多个变量 + enforces_secure_chat = status.enforces_secure_chat # 是否开启聊天签名 + forge_data = status.forge_data # FML版本 + icon = status.icon # 图标,输出为 base64 + latency = status.latency # 延迟 + motd = status.description # 标题 + version = status.version.name # 版本 + + # 关于玩家信息的获取 + players = status.players # 玩家总类 + online_player = players.online # 在线玩家数 + max_players = players.max # 最大玩家数 + sample_players = players.sample # 在线玩家的 ID,UUID + + return JavaStatus( + enforces_secure_chat, + forge_data, + icon, + latency, + motd, + version, + players, + online_player, + max_players, + sample_players, + ) + + except Exception as e: + return print(f"获取服务器状态时出错: {e}") \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..e3552f4 --- /dev/null +++ b/app.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# 重写 Flask-MCMOTD,早期版本用的是面向过程的方式进行写的,一个文件写了400多行,真是要爆了T.T + +# API +from flask import Flask, request, jsonify +from flask_cors import CORS + +# Java版查询模块 +from JavaServerStatus import java_status +# 基岩版查询模块 +from BedrockServerStatus import bedrock_status +# 此API优先解析 srv 记录 +from dnslookup import dns_lookup + +# 格式化文本 +from FormatData import format_java_data, format_bedrock_data, format_index, format_java_index, format_bedrock_index + + +app = Flask(__name__) +app.json.sort_keys = False +app.json.ensure_ascii = False +app.json.mimetype = 'application/json;charset=UTF-8' +app.json.compact = False +CORS(app) + +@app.route('/') +def index(): + message = format_index() + return jsonify(message), 200 + +# Java 服务器状态查询 +@app.route('/java') +def get_java_status(): + ip = request.args.get('ip') + # 空值输出 API 用法 + if not ip: + message = format_java_index() + return jsonify(message), 400 + + try: + ip, type = dns_lookup(ip) + print(f"解析Java版IP: {ip}, 是否为 SRV: {type}") + status = java_status(ip) + + data = format_java_data(ip, type, status) + + return jsonify(data), 200 + + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# 基岩版服务器状态查询 +@app.route('/bedrock') +def get_bedrock_status(): + ip = request.args.get('ip') + # 空值输出 API 用法 + if not ip: + message = format_bedrock_index() + return jsonify(message), 400 + + try: + print(f"解析基岩版IP: {ip}") + status = bedrock_status(ip) + + data = format_bedrock_data(ip, status) + + return jsonify(data), 200 + + except Exception as e: + return jsonify({"error": str(e)}), 500 + +if __name__ == '__main__': + app.run(debug=True, port=5000) \ No newline at end of file diff --git a/dnslookup.py b/dnslookup.py new file mode 100644 index 0000000..8579c34 --- /dev/null +++ b/dnslookup.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# 此模块用于解析 SRV 记录,返回值为 IP 和 解析类型(srv 或者 normal) +import dns.resolver + +def dns_lookup(ip): + # 因为第一次解析是面向于 SRV 的,所以需要做是否带端口号的判断 + if ':' in ip: + type = f'normal' + return ip, type + + # 自定义 DNS 解析器 + resolver = dns.resolver.Resolver() + resolver.nameservers = ['223.5.5.5', '223.6.6.6'] # 添加备用DNS + resolver.timeout = 5 # 单次查询超时时间 + resolver.lifetime = 10 # 总解析时间限制 + + try: + print(f'正在解析 SRV 记录: _minecraft._tcp.{ip}') + answers = resolver.resolve(f'_minecraft._tcp.{ip}', 'SRV') + + # address 的源输出末端有沟槽的 ".",去掉它 + address = str(answers[0].target).rstrip('.') + port = answers[0].port + + # 拼接端口号,赋值 SRV + ip = f'{address}:{port}' + print(f'解析出 SRV 地址: {ip}') + type = f'srv' + return ip, type + + except dns.resolver.NoAnswer: + print(f'无法解析SRV记录: _minecraft._tcp.{ip} (DNS服务器无答应)') + except dns.resolver.NXDOMAIN: + print(f'域名不存在SRV记录: _minecraft._tcp.{ip}') + except (dns.resolver.Timeout, dns.resolver.LifetimeTimeout): + print(f'DNS解析超时: {ip}') + except Exception as e: + print(f'DNS解析出现未知错误: {e}') + + type = f'normal' + return ip, type \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..78e7116 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "mcstatus-api" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "flask>=3.1.1", + "flask-cors>=6.0.1", + "mcstatus>=12.0.5", +]