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.
Files changed (33) hide show
  1. {hungerlib-3.2.dev13/src/hungerlib.egg-info → hungerlib-3.2.1}/PKG-INFO +1 -1
  2. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/pyproject.toml +1 -1
  3. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/__init__.py +2 -2
  4. hungerlib-3.2.1/src/hungerlib/bridgeclient.py +41 -0
  5. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/configloader.py +6 -0
  6. hungerlib-3.2.1/src/hungerlib/messagerouter.py +94 -0
  7. hungerlib-3.2.1/src/hungerlib/servers/minecraft.py +88 -0
  8. {hungerlib-3.2.dev13 → hungerlib-3.2.1/src/hungerlib.egg-info}/PKG-INFO +1 -1
  9. hungerlib-3.2.dev13/src/hungerlib/bridgeclient.py +0 -42
  10. hungerlib-3.2.dev13/src/hungerlib/messagerouter.py +0 -135
  11. hungerlib-3.2.dev13/src/hungerlib/servers/minecraft.py +0 -127
  12. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/LICENSE +0 -0
  13. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/README.md +0 -0
  14. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/setup.cfg +0 -0
  15. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/__init__.py +0 -0
  16. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/backups.py +0 -0
  17. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/command.py +0 -0
  18. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/databases.py +0 -0
  19. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/filemanager.py +0 -0
  20. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/schedule.py +0 -0
  21. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/api/startup.py +0 -0
  22. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/datamap.py +0 -0
  23. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/panel.py +0 -0
  24. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/servers/__init__.py +0 -0
  25. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/servers/generic.py +0 -0
  26. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/utils/__init__.py +0 -0
  27. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/utils/colormaps.py +0 -0
  28. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/utils/time.py +0 -0
  29. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib/utils/utils.py +0 -0
  30. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib.egg-info/SOURCES.txt +0 -0
  31. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib.egg-info/dependency_links.txt +0 -0
  32. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib.egg-info/requires.txt +0 -0
  33. {hungerlib-3.2.dev13 → hungerlib-3.2.1}/src/hungerlib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hungerlib
3
- Version: 3.2.dev13
3
+ Version: 3.2.1
4
4
  Summary: Powerful automation library for Pterodactyl.
5
5
  Author: iFamished
6
6
  License: MIT
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "hungerlib"
10
- version = "3.2.dev13"
10
+ version = "3.2.1"
11
11
  description = "Powerful automation library for Pterodactyl."
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.9"
@@ -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 HungerBridgeClient
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
- 'HungerBridgeClient',
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hungerlib
3
- Version: 3.2.dev13
3
+ Version: 3.2.1
4
4
  Summary: Powerful automation library for Pterodactyl.
5
5
  Author: iFamished
6
6
  License: MIT
@@ -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