2020-08-19 01:04:43 +00:00
|
|
|
import struct
|
|
|
|
import socket
|
|
|
|
import base64
|
|
|
|
import json
|
2021-08-28 22:48:30 +00:00
|
|
|
import os
|
2022-03-01 17:58:39 +00:00
|
|
|
import re
|
2020-08-19 01:04:43 +00:00
|
|
|
import logging.config
|
2022-03-01 17:58:39 +00:00
|
|
|
import uuid
|
|
|
|
import random
|
2022-01-26 01:45:30 +00:00
|
|
|
|
2022-03-01 03:40:11 +00:00
|
|
|
from app.classes.minecraft.bedrock_ping import BedrockPing
|
2022-04-12 01:34:46 +00:00
|
|
|
from app.classes.shared.console import Console
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
2023-12-29 17:11:29 +00:00
|
|
|
MOTD_CODES = ["bold", "italic", "underlined", "strikethrough"]
|
2020-08-19 01:04:43 +00:00
|
|
|
|
2022-03-23 02:50:12 +00:00
|
|
|
|
2020-08-19 01:04:43 +00:00
|
|
|
class Server:
|
2022-04-12 01:34:46 +00:00
|
|
|
def __init__(self, data):
|
2023-08-02 23:20:26 +00:00
|
|
|
if isinstance(data, str):
|
|
|
|
logger.error(
|
|
|
|
"Failed to calculate stats. Expected object. "
|
|
|
|
f"Server returned string: {data}"
|
|
|
|
)
|
|
|
|
return
|
2022-03-23 02:50:12 +00:00
|
|
|
self.description = data.get("description")
|
2020-08-19 01:04:43 +00:00
|
|
|
# print(self.description)
|
|
|
|
if isinstance(self.description, dict):
|
|
|
|
# cat server
|
|
|
|
if "translate" in self.description:
|
2022-03-23 02:50:12 +00:00
|
|
|
self.description = self.description["translate"]
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
# waterfall / bungee
|
2022-03-23 02:50:12 +00:00
|
|
|
elif "extra" in self.description:
|
2020-08-19 01:04:43 +00:00
|
|
|
lines = []
|
|
|
|
|
|
|
|
description = self.description
|
2023-12-28 23:34:35 +00:00
|
|
|
if "text" in description.keys():
|
|
|
|
lines.append(description["text"])
|
2022-03-23 02:50:12 +00:00
|
|
|
if "extra" in description.keys():
|
2023-12-29 17:11:29 +00:00
|
|
|
if isinstance(description["extra"], list):
|
|
|
|
for e in description["extra"]:
|
|
|
|
if not isinstance(e, dict):
|
|
|
|
lines.append(e)
|
|
|
|
continue
|
|
|
|
# Conversion format code needed only for Java Version
|
|
|
|
lines.append(get_code_format("reset"))
|
|
|
|
for item in MOTD_CODES:
|
|
|
|
if e.get(item, False):
|
|
|
|
lines.append(get_code_format(item))
|
|
|
|
if "color" in e.keys():
|
|
|
|
lines.append(get_code_format(e["color"]))
|
|
|
|
# Then append the text
|
|
|
|
if "text" in e.keys():
|
|
|
|
if e["text"] == "\n":
|
|
|
|
lines.append("§§")
|
|
|
|
else:
|
|
|
|
lines.append(e["text"])
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
total_text = " ".join(lines)
|
|
|
|
self.description = total_text
|
|
|
|
|
|
|
|
# normal MC
|
|
|
|
else:
|
2022-03-23 02:50:12 +00:00
|
|
|
self.description = self.description["text"]
|
2020-08-19 01:04:43 +00:00
|
|
|
|
2022-03-23 02:50:12 +00:00
|
|
|
self.icon = base64.b64decode(data.get("favicon", "")[22:])
|
2022-03-02 03:30:53 +00:00
|
|
|
try:
|
2022-03-23 02:50:12 +00:00
|
|
|
self.players = Players(data["players"]).report()
|
2022-03-02 03:30:53 +00:00
|
|
|
except KeyError:
|
|
|
|
logger.error("Error geting player information key error")
|
|
|
|
self.players = []
|
2022-03-23 02:50:12 +00:00
|
|
|
self.version = data["version"]["name"]
|
|
|
|
self.protocol = data["version"]["protocol"]
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Players(list):
|
|
|
|
def __init__(self, data):
|
2022-03-23 02:50:12 +00:00
|
|
|
super().__init__(Player(x) for x in data.get("sample", []))
|
|
|
|
self.max = data["max"]
|
|
|
|
self.online = data["online"]
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
def report(self):
|
|
|
|
players = []
|
|
|
|
|
2022-04-14 02:10:25 +00:00
|
|
|
for player in self:
|
|
|
|
players.append(str(player))
|
2020-08-19 01:04:43 +00:00
|
|
|
|
2022-03-23 02:50:12 +00:00
|
|
|
r_data = {"online": self.online, "max": self.max, "players": players}
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
return json.dumps(r_data)
|
|
|
|
|
|
|
|
|
|
|
|
class Player:
|
|
|
|
def __init__(self, data):
|
2022-03-23 02:50:12 +00:00
|
|
|
self.id = data["id"]
|
|
|
|
self.name = data["name"]
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.name
|
|
|
|
|
2022-03-23 02:50:12 +00:00
|
|
|
|
2022-04-12 01:34:46 +00:00
|
|
|
def get_code_format(format_name):
|
2021-08-28 22:48:30 +00:00
|
|
|
root_dir = os.path.abspath(os.path.curdir)
|
2022-03-23 02:50:12 +00:00
|
|
|
format_file = os.path.join(root_dir, "app", "config", "motd_format.json")
|
2021-08-28 22:48:30 +00:00
|
|
|
try:
|
2022-03-23 02:50:12 +00:00
|
|
|
with open(format_file, "r", encoding="utf-8") as f:
|
2021-08-28 22:48:30 +00:00
|
|
|
data = json.load(f)
|
|
|
|
|
|
|
|
if format_name in data.keys():
|
|
|
|
return data.get(format_name)
|
2022-06-14 12:40:57 +00:00
|
|
|
logger.error(f"Format MOTD Error: format name {format_name} does not exist")
|
|
|
|
Console.error(f"Format MOTD Error: format name {format_name} does not exist")
|
|
|
|
return ""
|
2021-08-28 22:48:30 +00:00
|
|
|
|
|
|
|
except Exception as e:
|
2022-01-26 01:45:30 +00:00
|
|
|
logger.critical(f"Config File Error: Unable to read {format_file} due to {e}")
|
2022-04-12 01:34:46 +00:00
|
|
|
Console.critical(f"Config File Error: Unable to read {format_file} due to {e}")
|
2021-08-28 22:48:30 +00:00
|
|
|
|
|
|
|
return ""
|
|
|
|
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
# For the rest of requests see wiki.vg/Protocol
|
2021-03-09 03:01:42 +00:00
|
|
|
def ping(ip, port):
|
2020-08-19 01:04:43 +00:00
|
|
|
def read_var_int():
|
|
|
|
i = 0
|
|
|
|
j = 0
|
|
|
|
while True:
|
2022-09-01 14:29:47 +00:00
|
|
|
try:
|
|
|
|
k = sock.recv(1)
|
2022-09-03 16:40:59 +00:00
|
|
|
if not k:
|
2023-02-15 23:40:20 +00:00
|
|
|
raise ValueError()
|
2022-09-01 14:29:47 +00:00
|
|
|
except:
|
|
|
|
return 0
|
2020-08-19 01:04:43 +00:00
|
|
|
k = k[0]
|
2022-03-23 02:50:12 +00:00
|
|
|
i |= (k & 0x7F) << (j * 7)
|
2020-08-19 01:04:43 +00:00
|
|
|
j += 1
|
|
|
|
if j > 5:
|
2022-03-23 02:50:12 +00:00
|
|
|
raise ValueError("var_int too big")
|
2022-01-26 01:45:30 +00:00
|
|
|
if not k & 0x80:
|
2020-08-19 01:04:43 +00:00
|
|
|
return i
|
|
|
|
|
2021-03-09 03:01:42 +00:00
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
2022-10-07 23:42:28 +00:00
|
|
|
sock.settimeout(5)
|
2020-08-19 01:04:43 +00:00
|
|
|
try:
|
2021-03-09 21:57:45 +00:00
|
|
|
sock.connect((ip, port))
|
2021-03-09 03:01:42 +00:00
|
|
|
|
2022-10-04 20:14:22 +00:00
|
|
|
except:
|
2022-02-10 23:20:36 +00:00
|
|
|
return False
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
try:
|
2022-03-23 02:50:12 +00:00
|
|
|
host = ip.encode("utf-8")
|
|
|
|
data = b"" # wiki.vg/Server_List_Ping
|
|
|
|
data += b"\x00" # packet ID
|
|
|
|
data += b"\x04" # protocol variant
|
|
|
|
data += struct.pack(">b", len(host)) + host
|
|
|
|
data += struct.pack(">H", port)
|
|
|
|
data += b"\x01" # next state
|
|
|
|
data = struct.pack(">b", len(data)) + data
|
|
|
|
sock.sendall(data + b"\x01\x00") # handshake + status ping
|
2020-08-19 01:04:43 +00:00
|
|
|
length = read_var_int() # full packet length
|
|
|
|
if length < 10:
|
2022-06-14 12:40:57 +00:00
|
|
|
return not length < 0
|
2020-08-19 01:04:43 +00:00
|
|
|
|
|
|
|
sock.recv(1) # packet type, 0 for pings
|
|
|
|
length = read_var_int() # string length
|
2022-03-23 02:50:12 +00:00
|
|
|
data = b""
|
2020-08-19 01:04:43 +00:00
|
|
|
while len(data) != length:
|
|
|
|
chunk = sock.recv(length - len(data))
|
|
|
|
if not chunk:
|
|
|
|
return False
|
|
|
|
|
|
|
|
data += chunk
|
2022-01-26 01:45:30 +00:00
|
|
|
logger.debug(f"Server reports this data on ping: {data}")
|
2022-03-02 03:45:44 +00:00
|
|
|
try:
|
|
|
|
return Server(json.loads(data))
|
|
|
|
except KeyError:
|
|
|
|
return {}
|
2020-08-19 01:04:43 +00:00
|
|
|
finally:
|
|
|
|
sock.close()
|
2022-02-10 23:20:36 +00:00
|
|
|
|
2022-03-23 02:50:12 +00:00
|
|
|
|
2022-02-10 23:20:36 +00:00
|
|
|
# For the rest of requests see wiki.vg/Protocol
|
|
|
|
def ping_bedrock(ip, port):
|
2022-04-14 02:10:25 +00:00
|
|
|
rand = random.Random()
|
2022-03-01 17:58:39 +00:00
|
|
|
try:
|
2022-03-23 02:50:12 +00:00
|
|
|
# pylint: disable=consider-using-f-string
|
2022-04-14 02:10:25 +00:00
|
|
|
rand.seed("".join(re.findall("..", "%012x" % uuid.getnode())))
|
|
|
|
client_guid = uuid.UUID(int=rand.getrandbits(32)).int
|
2022-03-01 17:58:39 +00:00
|
|
|
except:
|
2022-03-01 03:40:11 +00:00
|
|
|
client_guid = 0
|
2022-02-10 23:20:36 +00:00
|
|
|
try:
|
2022-03-01 03:40:11 +00:00
|
|
|
brp = BedrockPing(ip, port, client_guid)
|
|
|
|
return brp.ping()
|
2022-03-02 00:47:29 +00:00
|
|
|
except:
|
2022-03-01 03:40:11 +00:00
|
|
|
logger.debug("Unable to get RakNet stats")
|