hungerlib 3.2.dev13__tar.gz → 3.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hungerlib-3.2.dev13/src/hungerlib.egg-info → hungerlib-3.2.1}/PKG-INFO +1 -1
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/pyproject.toml +1 -1
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/__init__.py +2 -2
- hungerlib-3.2.1/src/hungerlib/bridgeclient.py +41 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/configloader.py +6 -0
- hungerlib-3.2.1/src/hungerlib/messagerouter.py +94 -0
- hungerlib-3.2.1/src/hungerlib/servers/minecraft.py +88 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1/src/hungerlib.egg-info}/PKG-INFO +1 -1
- hungerlib-3.2.dev13/src/hungerlib/bridgeclient.py +0 -42
- hungerlib-3.2.dev13/src/hungerlib/messagerouter.py +0 -135
- hungerlib-3.2.dev13/src/hungerlib/servers/minecraft.py +0 -127
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/LICENSE +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/README.md +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/setup.cfg +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/__init__.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/backups.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/command.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/databases.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/filemanager.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/schedule.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/startup.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/datamap.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/panel.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/servers/__init__.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/servers/generic.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/utils/__init__.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/utils/colormaps.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/utils/time.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/utils/utils.py +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib.egg-info/SOURCES.txt +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib.egg-info/dependency_links.txt +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib.egg-info/requires.txt +0 -0
- {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib.egg-info/top_level.txt +0 -0
|
@@ -13,7 +13,7 @@ from .datamap import set_default_maps, get_default_maps, Syntax, DataMap, datama
|
|
|
13
13
|
from .messagerouter import MessageRouter
|
|
14
14
|
from .panel import Panel
|
|
15
15
|
from .servers import GenericServer, MinecraftServer
|
|
16
|
-
from .bridgeclient import
|
|
16
|
+
from .bridgeclient import BridgeClient
|
|
17
17
|
from .utils import (
|
|
18
18
|
ColorMap,
|
|
19
19
|
ASCII_COLOR_MAP,
|
|
@@ -94,7 +94,7 @@ __all__ = [
|
|
|
94
94
|
'Snapshot',
|
|
95
95
|
'clearTerminal',
|
|
96
96
|
'validateAll',
|
|
97
|
-
'
|
|
97
|
+
'BridgeClient',
|
|
98
98
|
|
|
99
99
|
# namespaces
|
|
100
100
|
'utils',
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
class BridgeClient:
|
|
5
|
+
def __init__(self, url, token):
|
|
6
|
+
self.base = url.rstrip("/")
|
|
7
|
+
self.headers = {
|
|
8
|
+
"X-Auth-Key": token,
|
|
9
|
+
"Content-Type": "application/json"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def _post(self, path, payload):
|
|
13
|
+
r = requests.post(f"{self.base}{path}", headers=self.headers, json=payload)
|
|
14
|
+
if not r.ok:
|
|
15
|
+
raise RuntimeError(f"HungerBridge error {r.status_code}: {r.text}")
|
|
16
|
+
return r.json()
|
|
17
|
+
|
|
18
|
+
def _get(self, path):
|
|
19
|
+
r = requests.get(f"{self.base}{path}", headers=self.headers)
|
|
20
|
+
if not r.ok:
|
|
21
|
+
raise RuntimeError(f"HungerBridge error {r.status_code}: {r.text}")
|
|
22
|
+
return r.json()
|
|
23
|
+
|
|
24
|
+
def runCommand(self, command, show_console=False, silent=False):
|
|
25
|
+
return self._post("/v1/run", {
|
|
26
|
+
"command": command,
|
|
27
|
+
"silent": silent,
|
|
28
|
+
"show_console": show_console
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
def log(self, message, level="info"):
|
|
32
|
+
return self._post("/v1/log", {
|
|
33
|
+
"level": level,
|
|
34
|
+
"message": message
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
def getStatus(self):
|
|
38
|
+
return self._get("/v1/status")
|
|
39
|
+
|
|
40
|
+
def getVersion(self):
|
|
41
|
+
return self._get("/v1/version")
|
|
@@ -81,6 +81,12 @@ def loadConfig(path, default_path, schema):
|
|
|
81
81
|
values[f.name] = convert_value(value, f.type)
|
|
82
82
|
continue
|
|
83
83
|
|
|
84
|
+
# --- UNIVERSAL YAML LOOKUP ---
|
|
85
|
+
yaml_value = raw.get(f.name)
|
|
86
|
+
if yaml_value is not None:
|
|
87
|
+
values[f.name] = convert_value(yaml_value, f.type)
|
|
88
|
+
continue
|
|
89
|
+
|
|
84
90
|
# --- FALLBACK: use dataclass default ---
|
|
85
91
|
if default is not MISSING:
|
|
86
92
|
values[f.name] = default
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MessageRouter:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
name,
|
|
10
|
+
server,
|
|
11
|
+
log_path,
|
|
12
|
+
formatter=None,
|
|
13
|
+
):
|
|
14
|
+
self.name = name
|
|
15
|
+
self.server = server
|
|
16
|
+
self.formatter = formatter
|
|
17
|
+
|
|
18
|
+
self.log_path = Path(log_path)
|
|
19
|
+
self.log_path.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
|
|
21
|
+
self.logger = logging.getLogger(name)
|
|
22
|
+
self.logger.setLevel(logging.DEBUG)
|
|
23
|
+
self._init_file_logger()
|
|
24
|
+
|
|
25
|
+
def _init_file_logger(self):
|
|
26
|
+
log_file = self.log_path / f"{self.name}_{datetime.now().strftime('%Y-%m-%d')}.log"
|
|
27
|
+
if not self.logger.handlers:
|
|
28
|
+
handler = logging.FileHandler(str(log_file))
|
|
29
|
+
formatter = logging.Formatter(
|
|
30
|
+
"[%(asctime)s] [%(levelname)s] %(message)s",
|
|
31
|
+
datefmt="%H:%M:%S"
|
|
32
|
+
)
|
|
33
|
+
handler.setFormatter(formatter)
|
|
34
|
+
self.logger.addHandler(handler)
|
|
35
|
+
|
|
36
|
+
# routing primitives
|
|
37
|
+
def _log_origin(self, msg):
|
|
38
|
+
print(msg)
|
|
39
|
+
|
|
40
|
+
def _log_destination(self, msg):
|
|
41
|
+
if hasattr(self.server, "bridge"):
|
|
42
|
+
self.server.bridge.log(msg)
|
|
43
|
+
|
|
44
|
+
def _log_file(self, msg, level="INFO"):
|
|
45
|
+
{
|
|
46
|
+
"INFO": self.logger.info,
|
|
47
|
+
"WARN": self.logger.warning,
|
|
48
|
+
"ERROR": self.logger.error
|
|
49
|
+
}[level](msg)
|
|
50
|
+
|
|
51
|
+
def _broadcast(self, msg):
|
|
52
|
+
if hasattr(self.server, "sendBroadcast"):
|
|
53
|
+
self.server.sendBroadcast(msg)
|
|
54
|
+
|
|
55
|
+
# high level router
|
|
56
|
+
def say(
|
|
57
|
+
self,
|
|
58
|
+
template,
|
|
59
|
+
level="info",
|
|
60
|
+
destination=False,
|
|
61
|
+
broadcast=False,
|
|
62
|
+
log=True,
|
|
63
|
+
origin=True,
|
|
64
|
+
**fmt
|
|
65
|
+
):
|
|
66
|
+
if not template:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
if self.formatter:
|
|
70
|
+
msg = self.formatter(template, **fmt)
|
|
71
|
+
else:
|
|
72
|
+
msg = template
|
|
73
|
+
|
|
74
|
+
if origin:
|
|
75
|
+
self._log_origin(msg)
|
|
76
|
+
|
|
77
|
+
if destination:
|
|
78
|
+
self._log_destination(msg)
|
|
79
|
+
|
|
80
|
+
if log:
|
|
81
|
+
self._log_file(msg, level=level.upper())
|
|
82
|
+
|
|
83
|
+
if broadcast:
|
|
84
|
+
self._broadcast(msg)
|
|
85
|
+
|
|
86
|
+
# level helpers
|
|
87
|
+
def info(self, template, **fmt):
|
|
88
|
+
self.say(template, level="info", **fmt)
|
|
89
|
+
|
|
90
|
+
def warn(self, template, **fmt):
|
|
91
|
+
self.say(template, level="warn", **fmt)
|
|
92
|
+
|
|
93
|
+
def error(self, template, **fmt):
|
|
94
|
+
self.say(template, level="error", **fmt)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from hungerlib.panel import Panel
|
|
5
|
+
from hungerlib.utils.colormaps import MC_COLOR_MAP, ASCII_COLOR_MAP
|
|
6
|
+
from hungerlib.servers import GenericServer
|
|
7
|
+
from hungerlib.bridgeclient import BridgeClient
|
|
8
|
+
from hungerlib.datamap import mapit
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MinecraftServer(GenericServer):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
name,
|
|
15
|
+
panel: Panel,
|
|
16
|
+
server_id,
|
|
17
|
+
server_domain,
|
|
18
|
+
server_port,
|
|
19
|
+
bridge_url,
|
|
20
|
+
bridge_token,
|
|
21
|
+
tpsCommand='tt20 tps',
|
|
22
|
+
):
|
|
23
|
+
mc_map = (
|
|
24
|
+
mc_color_map.as_dict()
|
|
25
|
+
if hasattr(mc_color_map, "as_dict")
|
|
26
|
+
else mc_color_map
|
|
27
|
+
)
|
|
28
|
+
ascii_map = (
|
|
29
|
+
ascii_color_map.as_dict()
|
|
30
|
+
if hasattr(ascii_color_map, "as_dict")
|
|
31
|
+
else ascii_color_map
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
super().__init__(
|
|
35
|
+
name,
|
|
36
|
+
panel,
|
|
37
|
+
server_id,
|
|
38
|
+
mc_color_map=mc_map,
|
|
39
|
+
ascii_color_map=ascii_map
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Minecraft-specific fields
|
|
43
|
+
self.server_domain = server_domain
|
|
44
|
+
self.server_port = server_port
|
|
45
|
+
self.tpsCommand = tpsCommand
|
|
46
|
+
|
|
47
|
+
# HungerBridge client
|
|
48
|
+
self.bridge = BridgeClient(bridge_url, bridge_token)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# getter methods
|
|
52
|
+
def getPlayers(self):
|
|
53
|
+
output = self.bridge.runCommand("list")
|
|
54
|
+
if not output:
|
|
55
|
+
return None
|
|
56
|
+
m = re.search(r"There are (\d+)", output)
|
|
57
|
+
return int(m.group(1)) if m else None
|
|
58
|
+
|
|
59
|
+
def getTPS(self, type="raw", rounding=10):
|
|
60
|
+
"""
|
|
61
|
+
This has limitations and is not yet complete. It currently ONLY parses TT20's tps command.
|
|
62
|
+
Example: /tt20 tps
|
|
63
|
+
This parser WILL NOT WORK with other configurations!
|
|
64
|
+
"""
|
|
65
|
+
output = self.sendCommand(self.tpsCommand)
|
|
66
|
+
if not output:
|
|
67
|
+
return None
|
|
68
|
+
clean = re.sub(r"§.", "", output)
|
|
69
|
+
m = re.search(
|
|
70
|
+
r"TPS\s+([0-9]+\.[0-9]+)\s+with average\s+([0-9]+\.[0-9]+)\s+accurate\s+([0-9]+\.[0-9]+)",
|
|
71
|
+
clean
|
|
72
|
+
)
|
|
73
|
+
if not m:
|
|
74
|
+
return None
|
|
75
|
+
raw, avg, acc = map(float, m.groups())
|
|
76
|
+
table = {"raw": raw, "avg": avg, "acc": acc}
|
|
77
|
+
value = table.get(type, avg)
|
|
78
|
+
return round(value, rounding) if rounding else value
|
|
79
|
+
|
|
80
|
+
# commands
|
|
81
|
+
def sendConsoleCommand(self, command, show_console=False, silent=False):
|
|
82
|
+
return self.bridge.runCommand(command, show_console, silent)
|
|
83
|
+
|
|
84
|
+
def sendBroadcast(self, message):
|
|
85
|
+
translated = mapit(message, MC_COLOR_MAP)
|
|
86
|
+
safe = translated.replace('"', '\\"')
|
|
87
|
+
cmd = f'tellraw @a {{"text":"{safe}"}}'
|
|
88
|
+
return self.bridge.runCommand(cmd)
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
|
|
3
|
-
class HungerBridgeClient:
|
|
4
|
-
def __init__(self, base_url: str, token: str):
|
|
5
|
-
self.base = base_url.rstrip("/")
|
|
6
|
-
self.headers = {
|
|
7
|
-
"Authorization": f"Bearer {token}",
|
|
8
|
-
"Content-Type": "application/json"
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
def _post(self, path: str, payload: dict):
|
|
12
|
-
r = requests.post(f"{self.base}{path}", headers=self.headers, json=payload)
|
|
13
|
-
if not r.ok:
|
|
14
|
-
raise RuntimeError(f"HungerBridge error {r.status_code}: {r.text}")
|
|
15
|
-
return r.json()
|
|
16
|
-
|
|
17
|
-
def _get(self, path: str):
|
|
18
|
-
r = requests.get(f"{self.base}{path}", headers=self.headers)
|
|
19
|
-
if not r.ok:
|
|
20
|
-
raise RuntimeError(f"HungerBridge error {r.status_code}: {r.text}")
|
|
21
|
-
return r.json()
|
|
22
|
-
|
|
23
|
-
# -----------------------------
|
|
24
|
-
# Public API
|
|
25
|
-
# -----------------------------
|
|
26
|
-
def health(self):
|
|
27
|
-
return self._get("/health")
|
|
28
|
-
|
|
29
|
-
def ping(self):
|
|
30
|
-
return self._get("/ping")
|
|
31
|
-
|
|
32
|
-
def runCommand(self, command: str, silent: bool = False):
|
|
33
|
-
return self._post("/run", {
|
|
34
|
-
"command": command,
|
|
35
|
-
"silent": silent
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
def log(self, message: str, level: str = "info"):
|
|
39
|
-
return self._post("/log", {
|
|
40
|
-
"level": level,
|
|
41
|
-
"message": message
|
|
42
|
-
})
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class MessageRouter:
|
|
7
|
-
def __init__(
|
|
8
|
-
self,
|
|
9
|
-
name,
|
|
10
|
-
server,
|
|
11
|
-
log_path,
|
|
12
|
-
formatter=None,
|
|
13
|
-
console_backspaces=0,
|
|
14
|
-
|
|
15
|
-
info_prefix="<white>[INFO]: ",
|
|
16
|
-
warn_prefix="<yellow>[WARN]: ",
|
|
17
|
-
error_prefix="<red>[ERROR]: "
|
|
18
|
-
):
|
|
19
|
-
self.name = name
|
|
20
|
-
self.server = server
|
|
21
|
-
self.formatter = formatter
|
|
22
|
-
|
|
23
|
-
self.log_path = Path(log_path)
|
|
24
|
-
self.log_path.mkdir(parents=True, exist_ok=True)
|
|
25
|
-
|
|
26
|
-
self.console_backspaces = "\b" * console_backspaces
|
|
27
|
-
|
|
28
|
-
self.info_prefix = info_prefix
|
|
29
|
-
self.warn_prefix = warn_prefix
|
|
30
|
-
self.error_prefix = error_prefix
|
|
31
|
-
|
|
32
|
-
self.logger = logging.getLogger(name)
|
|
33
|
-
self.logger.setLevel(logging.DEBUG)
|
|
34
|
-
self._init_file_logger()
|
|
35
|
-
|
|
36
|
-
def _init_file_logger(self):
|
|
37
|
-
log_file = self.log_path / f"{self.name}_{datetime.now().strftime('%Y-%m-%d')}.log"
|
|
38
|
-
if not self.logger.handlers:
|
|
39
|
-
handler = logging.FileHandler(str(log_file))
|
|
40
|
-
formatter = logging.Formatter(
|
|
41
|
-
"[%(asctime)s] [%(levelname)s] %(message)s",
|
|
42
|
-
datefmt="%H:%M:%S"
|
|
43
|
-
)
|
|
44
|
-
handler.setFormatter(formatter)
|
|
45
|
-
self.logger.addHandler(handler)
|
|
46
|
-
|
|
47
|
-
# routing primitives
|
|
48
|
-
def origin(self, msg):
|
|
49
|
-
print(msg)
|
|
50
|
-
|
|
51
|
-
def destination(self, msg):
|
|
52
|
-
if not self.server:
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
# Prefer HungerBridge client if present
|
|
56
|
-
bridge = getattr(self.server, "bridge", None)
|
|
57
|
-
if bridge is not None:
|
|
58
|
-
try:
|
|
59
|
-
bridge.log(self.console_backspaces + msg, level="info")
|
|
60
|
-
return
|
|
61
|
-
except Exception as e:
|
|
62
|
-
print("Bridge destination error, falling back to RCON:", e)
|
|
63
|
-
|
|
64
|
-
# Fallback to RCON/logtellraw
|
|
65
|
-
if hasattr(self.server, "_rcon_send"):
|
|
66
|
-
self.server._rcon_send(
|
|
67
|
-
f'logtellraw targetless "{self.console_backspaces}{msg}"'
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
def log(self, msg, level="INFO"):
|
|
71
|
-
# Prefer HungerBridge client for server log
|
|
72
|
-
bridge = getattr(self.server, "bridge", None)
|
|
73
|
-
if bridge is not None:
|
|
74
|
-
try:
|
|
75
|
-
bridge.log(msg, level=level.lower())
|
|
76
|
-
except Exception as e:
|
|
77
|
-
print("Bridge log error, falling back to file log:", e)
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
"INFO": self.logger.info,
|
|
81
|
-
"WARN": self.logger.warning,
|
|
82
|
-
"ERROR": self.logger.error
|
|
83
|
-
}[level](msg)
|
|
84
|
-
|
|
85
|
-
def broadcast(self, msg):
|
|
86
|
-
if hasattr(self.server, "sendBroadcast"):
|
|
87
|
-
self.server.sendBroadcast(msg)
|
|
88
|
-
|
|
89
|
-
# high level router
|
|
90
|
-
def say(
|
|
91
|
-
self,
|
|
92
|
-
template,
|
|
93
|
-
level="info",
|
|
94
|
-
destination=False,
|
|
95
|
-
broadcast=False,
|
|
96
|
-
log=True,
|
|
97
|
-
origin=True,
|
|
98
|
-
**fmt
|
|
99
|
-
):
|
|
100
|
-
if not template:
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
if level == "info":
|
|
104
|
-
template = self.info_prefix + template
|
|
105
|
-
elif level == "warn":
|
|
106
|
-
template = self.warn_prefix + template
|
|
107
|
-
elif level == "error":
|
|
108
|
-
template = self.error_prefix + template
|
|
109
|
-
|
|
110
|
-
if self.formatter:
|
|
111
|
-
msg = self.formatter(template, **fmt)
|
|
112
|
-
else:
|
|
113
|
-
msg = template
|
|
114
|
-
|
|
115
|
-
if origin:
|
|
116
|
-
self.origin(msg)
|
|
117
|
-
|
|
118
|
-
if destination:
|
|
119
|
-
self.destination(msg)
|
|
120
|
-
|
|
121
|
-
if log:
|
|
122
|
-
self.log(msg, level=level.upper())
|
|
123
|
-
|
|
124
|
-
if broadcast:
|
|
125
|
-
self.broadcast(msg)
|
|
126
|
-
|
|
127
|
-
# level helpers
|
|
128
|
-
def info(self, template, **fmt):
|
|
129
|
-
self.say(self.info_prefix + template, level="info", **fmt)
|
|
130
|
-
|
|
131
|
-
def warn(self, template, **fmt):
|
|
132
|
-
self.say(self.warn_prefix + template, level="warn", **fmt)
|
|
133
|
-
|
|
134
|
-
def error(self, template, **fmt):
|
|
135
|
-
self.say(self.error_prefix + template, level="error", **fmt)
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import re
|
|
3
|
-
import mcrcon
|
|
4
|
-
|
|
5
|
-
from hungerlib.panel import Panel
|
|
6
|
-
from hungerlib.utils.colormaps import MC_COLOR_MAP, ASCII_COLOR_MAP
|
|
7
|
-
from hungerlib.servers import GenericServer
|
|
8
|
-
from hungerlib.bridgeclient import HungerBridgeClient
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class MinecraftServer(GenericServer):
|
|
12
|
-
def __init__(
|
|
13
|
-
self,
|
|
14
|
-
name,
|
|
15
|
-
panel: Panel,
|
|
16
|
-
server_id,
|
|
17
|
-
server_domain,
|
|
18
|
-
server_port,
|
|
19
|
-
rcon_port,
|
|
20
|
-
rcon_password,
|
|
21
|
-
bridge_url=None,
|
|
22
|
-
bridge_token=None,
|
|
23
|
-
mc_color_map=MC_COLOR_MAP,
|
|
24
|
-
ascii_color_map=ASCII_COLOR_MAP,
|
|
25
|
-
tpsCommand='tt20 tps',
|
|
26
|
-
):
|
|
27
|
-
mc_map = (
|
|
28
|
-
mc_color_map.as_dict()
|
|
29
|
-
if hasattr(mc_color_map, "as_dict")
|
|
30
|
-
else mc_color_map
|
|
31
|
-
)
|
|
32
|
-
ascii_map = (
|
|
33
|
-
ascii_color_map.as_dict()
|
|
34
|
-
if hasattr(ascii_color_map, "as_dict")
|
|
35
|
-
else ascii_color_map
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
super().__init__(
|
|
39
|
-
name,
|
|
40
|
-
panel,
|
|
41
|
-
server_id,
|
|
42
|
-
mc_color_map=mc_map,
|
|
43
|
-
ascii_color_map=ascii_map
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
# Minecraft-specific fields
|
|
47
|
-
self.server_domain = server_domain
|
|
48
|
-
self.server_port = server_port
|
|
49
|
-
self.rcon_port = rcon_port
|
|
50
|
-
self.rcon_password = rcon_password
|
|
51
|
-
self.tpsCommand = tpsCommand
|
|
52
|
-
|
|
53
|
-
# Optional HungerBridge client
|
|
54
|
-
self.bridge: HungerBridgeClient | None = None
|
|
55
|
-
if bridge_url and bridge_token:
|
|
56
|
-
self.bridge = HungerBridgeClient(bridge_url, bridge_token)
|
|
57
|
-
|
|
58
|
-
# rcon handler (fallback path)
|
|
59
|
-
def _rcon_send(self, command):
|
|
60
|
-
if not self.server_domain or not self.rcon_password:
|
|
61
|
-
return None
|
|
62
|
-
try:
|
|
63
|
-
with mcrcon.MCRcon(self.server_domain, self.rcon_password, port=self.rcon_port) as m:
|
|
64
|
-
resp1 = m.command(command)
|
|
65
|
-
time.sleep(0.05)
|
|
66
|
-
resp2 = m.command("") # fetch buffered packets
|
|
67
|
-
return (resp1 or "") + (resp2 or "")
|
|
68
|
-
except Exception as e:
|
|
69
|
-
print("RCON error:", e)
|
|
70
|
-
return None
|
|
71
|
-
|
|
72
|
-
# getter methods
|
|
73
|
-
def getPlayers(self):
|
|
74
|
-
# Prefer bridge if we ever add an endpoint; for now still RCON
|
|
75
|
-
output = self._rcon_send("list")
|
|
76
|
-
if not output:
|
|
77
|
-
return None
|
|
78
|
-
m = re.search(r"There are (\d+)", output)
|
|
79
|
-
return int(m.group(1)) if m else None
|
|
80
|
-
|
|
81
|
-
def getTPS(self, type="raw", rounding=10):
|
|
82
|
-
"""
|
|
83
|
-
This has limitations and is not yet complete. It currently ONLY parses TT20's tps command.
|
|
84
|
-
Example: /tt20 tps
|
|
85
|
-
This parser WILL NOT WORK with other configurations!
|
|
86
|
-
"""
|
|
87
|
-
output = self._rcon_send(self.tpsCommand)
|
|
88
|
-
if not output:
|
|
89
|
-
return None
|
|
90
|
-
clean = re.sub(r"§.", "", output)
|
|
91
|
-
m = re.search(
|
|
92
|
-
r"TPS\s+([0-9]+\.[0-9]+)\s+with average\s+([0-9]+\.[0-9]+)\s+accurate\s+([0-9]+\.[0-9]+)",
|
|
93
|
-
clean
|
|
94
|
-
)
|
|
95
|
-
if not m:
|
|
96
|
-
return None
|
|
97
|
-
raw, avg, acc = map(float, m.groups())
|
|
98
|
-
table = {"raw": raw, "avg": avg, "acc": acc}
|
|
99
|
-
value = table.get(type, avg)
|
|
100
|
-
return round(value, rounding) if rounding else value
|
|
101
|
-
|
|
102
|
-
# commands
|
|
103
|
-
def sendConsoleCommand(self, command: str):
|
|
104
|
-
# Prefer HungerBridge
|
|
105
|
-
if self.bridge is not None:
|
|
106
|
-
try:
|
|
107
|
-
return self.bridge.runCommand(command)
|
|
108
|
-
except Exception as e:
|
|
109
|
-
print("Bridge command error, falling back to panel:", e)
|
|
110
|
-
|
|
111
|
-
# Fallback to panel (which may use RCON or other transport)
|
|
112
|
-
return self.panel.commands.send(self.server_id, command)
|
|
113
|
-
|
|
114
|
-
def sendBroadcast(self, message: str):
|
|
115
|
-
translated = self._translate_mc_colors(message)
|
|
116
|
-
safe = translated.replace('"', '\\"')
|
|
117
|
-
cmd = f'tellraw @a {{"text":"{safe}"}}'
|
|
118
|
-
|
|
119
|
-
# Prefer HungerBridge
|
|
120
|
-
if self.bridge is not None:
|
|
121
|
-
try:
|
|
122
|
-
return self.bridge.runCommand(cmd)
|
|
123
|
-
except Exception as e:
|
|
124
|
-
print("Bridge broadcast error, falling back to panel:", e)
|
|
125
|
-
|
|
126
|
-
# Fallback
|
|
127
|
-
return self.sendConsoleCommand(cmd)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|