hungerlib 2.19.dev1__tar.gz → 3.0__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 (35) hide show
  1. {hungerlib-2.19.dev1/src/hungerlib.egg-info → hungerlib-3.0}/PKG-INFO +1 -1
  2. {hungerlib-2.19.dev1 → hungerlib-3.0}/pyproject.toml +1 -1
  3. hungerlib-3.0/src/hungerlib/__init__.py +101 -0
  4. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/api/backups.py +2 -10
  5. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/api/command.py +0 -6
  6. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/api/databases.py +2 -10
  7. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/api/filemanager.py +2 -10
  8. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/api/schedule.py +1 -9
  9. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/api/startup.py +0 -4
  10. {hungerlib-2.19.dev1/src/hungerlib/addons → hungerlib-3.0/src/hungerlib}/configloader.py +4 -4
  11. hungerlib-3.0/src/hungerlib/datamap.py +87 -0
  12. hungerlib-3.0/src/hungerlib/messagerouter.py +148 -0
  13. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/panel.py +2 -6
  14. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/servers/__init__.py +1 -1
  15. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/servers/generic.py +2 -6
  16. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/servers/minecraft.py +2 -3
  17. hungerlib-3.0/src/hungerlib/utils/__init__.py +25 -0
  18. hungerlib-3.0/src/hungerlib/utils/colormaps.py +52 -0
  19. hungerlib-2.19.dev1/src/hungerlib/addons/scheduler.py → hungerlib-3.0/src/hungerlib/utils/time.py +0 -35
  20. hungerlib-2.19.dev1/src/hungerlib/addons/snapshot.py → hungerlib-3.0/src/hungerlib/utils/utils.py +13 -1
  21. {hungerlib-2.19.dev1 → hungerlib-3.0/src/hungerlib.egg-info}/PKG-INFO +1 -1
  22. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib.egg-info/SOURCES.txt +8 -9
  23. hungerlib-2.19.dev1/src/hungerlib/__init__.py +0 -33
  24. hungerlib-2.19.dev1/src/hungerlib/addons/__init__.py +0 -29
  25. hungerlib-2.19.dev1/src/hungerlib/addons/colormap.py +0 -96
  26. hungerlib-2.19.dev1/src/hungerlib/addons/mchelpers.py +0 -107
  27. hungerlib-2.19.dev1/src/hungerlib/addons/utils.py +0 -26
  28. hungerlib-2.19.dev1/src/hungerlib/logger.py +0 -124
  29. {hungerlib-2.19.dev1 → hungerlib-3.0}/LICENSE +0 -0
  30. {hungerlib-2.19.dev1 → hungerlib-3.0}/README.md +0 -0
  31. {hungerlib-2.19.dev1 → hungerlib-3.0}/setup.cfg +0 -0
  32. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib/api/__init__.py +0 -0
  33. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib.egg-info/dependency_links.txt +0 -0
  34. {hungerlib-2.19.dev1 → hungerlib-3.0}/src/hungerlib.egg-info/requires.txt +0 -0
  35. {hungerlib-2.19.dev1 → hungerlib-3.0}/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: 2.19.dev1
3
+ Version: 3.0
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 = "2.19.dev1"
10
+ version = "3.0"
11
11
  description = "Powerful automation library for Pterodactyl."
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.9"
@@ -0,0 +1,101 @@
1
+ from importlib.metadata import version as _pkg_version, PackageNotFoundError
2
+ from types import SimpleNamespace
3
+
4
+ # package version
5
+ try:
6
+ __version__ = _pkg_version('hungerlib')
7
+ except PackageNotFoundError:
8
+ __version__ = '0.0.0'
9
+
10
+ # modules
11
+ from .configloader import loadConfig
12
+ from .datamap import set_default_maps, get_default_maps, Syntax, DataMap, datamap, mapit
13
+ from .messagerouter import MessageRouter
14
+ from .panel import Panel
15
+ from .servers import GenericServer, MinecraftServer
16
+ from .utils import (
17
+ ColorMap,
18
+ ASCII_COLOR_MAP,
19
+ MC_COLOR_MAP,
20
+ snapSchedule,
21
+ runCountdownEvents,
22
+ waitForOnline,
23
+ waitForOffline,
24
+ secsUntil,
25
+ minsUntil,
26
+ Snapshot,
27
+ clearTerminal,
28
+ validateAll,
29
+ )
30
+
31
+ # namespaces
32
+ utils = SimpleNamespace(
33
+ ColorMap = ColorMap,
34
+ ASCII_COLOR_MAP = ASCII_COLOR_MAP,
35
+ MC_COLOR_MAP = MC_COLOR_MAP,
36
+ snapSchedule = snapSchedule,
37
+ runCountdownEvents = runCountdownEvents,
38
+ waitForOnline = waitForOnline,
39
+ waitForOffline = waitForOffline,
40
+ secsUntil = secsUntil,
41
+ minsUntil = minsUntil,
42
+ Snapshot = Snapshot,
43
+ clearTerminal = clearTerminal,
44
+ validateAll = validateAll,
45
+ )
46
+
47
+ servers = SimpleNamespace(
48
+ Panel = Panel,
49
+ Generic = GenericServer,
50
+ Minecraft = MinecraftServer,
51
+ )
52
+
53
+ datamap_api = SimpleNamespace(
54
+ set_default_maps = set_default_maps,
55
+ get_default_maps = get_default_maps,
56
+ Syntax = Syntax,
57
+ DataMap = DataMap,
58
+ decorator = datamap,
59
+ mapit = mapit,
60
+
61
+ braces = Syntax.braces,
62
+ dollars = Syntax.dollars,
63
+ angles = Syntax.angles,
64
+ percents = Syntax.percents,
65
+ )
66
+
67
+
68
+
69
+ __all__ = [
70
+ '__version__',
71
+
72
+ # modules
73
+ 'loadConfig',
74
+ 'MessageRouter',
75
+ 'Panel',
76
+ 'set_default_maps',
77
+ 'get_default_maps',
78
+ 'Syntax',
79
+ 'DataMap',
80
+ 'datamap',
81
+ 'mapit',
82
+ 'GenericServer',
83
+ 'MinecraftServer',
84
+ 'ColorMap',
85
+ 'ASCII_COLOR_MAP',
86
+ 'MC_COLOR_MAP',
87
+ 'snapSchedule',
88
+ 'runCountdownEvents',
89
+ 'waitForOnline',
90
+ 'waitForOffline',
91
+ 'secsUntil',
92
+ 'minsUntil',
93
+ 'Snapshot',
94
+ 'clearTerminal',
95
+ 'validateAll',
96
+
97
+ # namespaces
98
+ 'utils',
99
+ 'servers',
100
+ 'datamap_api',
101
+ ]
@@ -1,8 +1,4 @@
1
1
  class BackupsAPI:
2
- """
3
- Raw backups endpoints.
4
- """
5
-
6
2
  def __init__(self, panel):
7
3
  self.panel = panel
8
4
 
@@ -16,11 +12,7 @@ class BackupsAPI:
16
12
  )
17
13
 
18
14
  def delete(self, server_id, backup_id):
19
- return self.panel.delete(
20
- f"/api/client/servers/{server_id}/backups/{backup_id}"
21
- )
15
+ return self.panel.delete(f"/api/client/servers/{server_id}/backups/{backup_id}")
22
16
 
23
17
  def download(self, server_id, backup_id):
24
- return self.panel.get(
25
- f"/api/client/servers/{server_id}/backups/{backup_id}/download"
26
- )
18
+ return self.panel.get(f"/api/client/servers/{server_id}/backups/{backup_id}/download")
@@ -1,10 +1,4 @@
1
- # src/hungerlib/api/command.py
2
-
3
1
  class CommandAPI:
4
- """
5
- Raw command endpoint.
6
- """
7
-
8
2
  def __init__(self, panel):
9
3
  self.panel = panel
10
4
 
@@ -1,8 +1,4 @@
1
1
  class DatabasesAPI:
2
- """
3
- Raw database endpoints.
4
- """
5
-
6
2
  def __init__(self, panel):
7
3
  self.panel = panel
8
4
 
@@ -19,11 +15,7 @@ class DatabasesAPI:
19
15
  )
20
16
 
21
17
  def rotate_password(self, server_id, db_id):
22
- return self.panel.post(
23
- f"/api/client/servers/{server_id}/databases/{db_id}/rotate-password"
24
- )
18
+ return self.panel.post(f"/api/client/servers/{server_id}/databases/{db_id}/rotate-password")
25
19
 
26
20
  def delete(self, server_id, db_id):
27
- return self.panel.delete(
28
- f"/api/client/servers/{server_id}/databases/{db_id}"
29
- )
21
+ return self.panel.delete(f"/api/client/servers/{server_id}/databases/{db_id}")
@@ -1,20 +1,12 @@
1
1
  class FileManagerAPI:
2
- """
3
- Raw file manager endpoints.
4
- """
5
-
6
2
  def __init__(self, panel):
7
3
  self.panel = panel
8
4
 
9
5
  def list(self, server_id, directory="/"):
10
- return self.panel.get(
11
- f"/api/client/servers/{server_id}/files/list?directory={directory}"
12
- )
6
+ return self.panel.get(f"/api/client/servers/{server_id}/files/list?directory={directory}")
13
7
 
14
8
  def download(self, server_id, file_path):
15
- return self.panel.get(
16
- f"/api/client/servers/{server_id}/files/download?file={file_path}"
17
- )
9
+ return self.panel.get(f"/api/client/servers/{server_id}/files/download?file={file_path}")
18
10
 
19
11
  def upload(self, server_id, directory, file_data):
20
12
  return self.panel._raw_upload(
@@ -1,9 +1,4 @@
1
1
  class ScheduleAPI:
2
- """
3
- Raw schedule endpoints.
4
- No logic. No models. No convenience.
5
- """
6
-
7
2
  def __init__(self, panel):
8
3
  self.panel = panel
9
4
 
@@ -14,7 +9,6 @@ class ScheduleAPI:
14
9
  return self.panel.post(f"/api/client/servers/{server_id}/schedules", json=payload)
15
10
 
16
11
  def update(self, server_id, schedule_id, payload):
17
- # Your panel uses POST for full updates
18
12
  return self.panel.post(
19
13
  f"/api/client/servers/{server_id}/schedules/{schedule_id}",
20
14
  json=payload
@@ -24,6 +18,4 @@ class ScheduleAPI:
24
18
  return self.panel.delete(f"/api/client/servers/{server_id}/schedules/{schedule_id}")
25
19
 
26
20
  def run(self, server_id, schedule_id):
27
- return self.panel.post(
28
- f"/api/client/servers/{server_id}/schedules/{schedule_id}/execute"
29
- )
21
+ return self.panel.post(f"/api/client/servers/{server_id}/schedules/{schedule_id}/execute")
@@ -1,8 +1,4 @@
1
1
  class StartupAPI:
2
- """
3
- Raw startup variable endpoints.
4
- """
5
-
6
2
  def __init__(self, panel):
7
3
  self.panel = panel
8
4
 
@@ -1,3 +1,4 @@
1
+ import inspect
1
2
  import os
2
3
  import yaml
3
4
  import importlib
@@ -50,7 +51,7 @@ def loadConfig(path, default_path, schema):
50
51
 
51
52
  raw = load_yaml(abs_path)
52
53
 
53
- cfg = schema() # empty dataclass instance
54
+ values = {}
54
55
 
55
56
  for f in fields(schema):
56
57
  yaml_path = f.metadata.get("yaml_key")
@@ -61,7 +62,6 @@ def loadConfig(path, default_path, schema):
61
62
  if value is None:
62
63
  continue
63
64
 
64
- target, attr = ensure_nested(cfg, f.name)
65
- setattr(target, attr, value)
65
+ values[f.name] = value
66
66
 
67
- return cfg
67
+ return schema(**values)
@@ -0,0 +1,87 @@
1
+ import re
2
+ import inspect
3
+ import sys
4
+ from dataclasses import dataclass, fields, is_dataclass
5
+
6
+ # default maps
7
+ _default_maps = []
8
+
9
+ def set_default_maps(*maps):
10
+ global _default_maps
11
+ _default_maps = list(maps)
12
+
13
+ def get_default_maps():
14
+ return _default_maps
15
+
16
+ # syntax patterns
17
+ class Syntax:
18
+ braces = r"\{([^{}]+)\}"
19
+ dollars = r"\$\{([^{}]+)\}"
20
+ angles = r"<([^<>]+)>"
21
+ percents = r"%([^%]+)%"
22
+
23
+ # base datamap
24
+ @dataclass(frozen=True)
25
+ class DataMap:
26
+ __syntax__: str = Syntax.braces
27
+ def as_map(self):
28
+ return {
29
+ f.name: getattr(self, f.name)
30
+ for f in fields(self)
31
+ if f.init and f.name != "__syntax__"
32
+ }
33
+ @classmethod
34
+ def get_syntax(cls):
35
+ return getattr(cls, "__syntax__", Syntax.braces)
36
+
37
+ # decorator
38
+ def datamap(_cls=None, *, syntax=Syntax.braces):
39
+ def wrap(cls):
40
+ cls.__syntax__ = syntax
41
+ cls = type(cls.__name__, (DataMap,), dict(cls.__dict__))
42
+ return dataclass(frozen=True)(cls)
43
+
44
+ return wrap if _cls is None else wrap(_cls)
45
+
46
+ datamap.braces = datamap(syntax=Syntax.braces)
47
+ datamap.angles = datamap(syntax=Syntax.angles)
48
+ datamap.dollars = datamap(syntax=Syntax.dollars)
49
+ datamap.percents = datamap(syntax=Syntax.percents)
50
+
51
+ # core templating
52
+ def mapit(text: str, *maps, **runtime):
53
+ maps = (*get_default_maps(), *maps)
54
+ for m in maps:
55
+ if isinstance(m, type) and is_dataclass(m):
56
+ m = m()
57
+ if is_dataclass(m):
58
+ pattern = m.get_syntax()
59
+ d = m.as_map()
60
+ elif hasattr(m, "as_dict"):
61
+ pattern = Syntax.angles
62
+ d = m.as_dict()
63
+ elif isinstance(m, dict):
64
+ pattern = runtime.get("syntax")
65
+ if not pattern:
66
+ continue
67
+ d = m
68
+ else:
69
+ continue
70
+
71
+ def repl(match):
72
+ k = match.group(1)
73
+ return str(d.get(k, match.group(0)))
74
+
75
+ text = re.sub(pattern, repl, text)
76
+
77
+ if runtime:
78
+ pattern = Syntax.braces
79
+ d = runtime
80
+
81
+ def repl(match):
82
+ k = match.group(1)
83
+ return str(d.get(k, match.group(0)))
84
+
85
+ text = re.sub(pattern, repl, text)
86
+
87
+ return text
@@ -0,0 +1,148 @@
1
+ import inspect
2
+ import logging
3
+ from pathlib import Path
4
+ from datetime import datetime
5
+ from hungerlib.utils.colormaps import ASCII_COLOR_MAP, MC_COLOR_MAP
6
+ from hungerlib.datamap import DataMap, Syntax, mapit
7
+
8
+
9
+ class MessageRouter:
10
+ def __init__(
11
+ self,
12
+ name,
13
+ server,
14
+ log_path,
15
+ formatter=None,
16
+ console_backspaces=0,
17
+
18
+ origin_map=ASCII_COLOR_MAP,
19
+ destination_map=ASCII_COLOR_MAP,
20
+ broadcast_map=MC_COLOR_MAP,
21
+ log_map=None,
22
+
23
+ info_prefix="<white>[INFO]: ",
24
+ warn_prefix="<yellow>[WARN]: ",
25
+ error_prefix="<red>[ERROR]: "
26
+ ):
27
+ self.name = name
28
+ self.server = server
29
+ self.formatter = formatter
30
+
31
+ self.log_path = Path(log_path)
32
+ self.log_path.mkdir(parents=True, exist_ok=True)
33
+
34
+ self.console_backspaces = "\b" * console_backspaces
35
+
36
+ self._origin_map = origin_map
37
+ self._destination_map = destination_map
38
+ self._broadcast_map = broadcast_map
39
+ self._log_map = log_map
40
+
41
+ self.info_prefix = info_prefix
42
+ self.warn_prefix = warn_prefix
43
+ self.error_prefix = error_prefix
44
+
45
+ self.logger = logging.getLogger(name)
46
+ self.logger.setLevel(logging.DEBUG)
47
+ self._init_file_logger()
48
+
49
+ def _init_file_logger(self):
50
+ log_file = self.log_path / f"{self.name}_{datetime.now().strftime('%Y-%m-%d')}.log"
51
+ if not self.logger.handlers:
52
+ handler = logging.FileHandler(str(log_file))
53
+ formatter = logging.Formatter(
54
+ "[%(asctime)s] [%(levelname)s] %(message)s",
55
+ datefmt="%H:%M:%S"
56
+ )
57
+ handler.setFormatter(formatter)
58
+ self.logger.addHandler(handler)
59
+
60
+ # colormap getters/setters
61
+ def setOriginMap(self, cmap): self._origin_map = cmap
62
+ def getOriginMap(self): return self._origin_map
63
+
64
+ def setDestinationMap(self, cmap): self._destination_map = cmap
65
+ def getDestinationMap(self): return self._destination_map
66
+
67
+ def setBroadcastMap(self, cmap): self._broadcast_map = cmap
68
+ def getBroadcastMap(self): return self._broadcast_map
69
+
70
+ def setLogMap(self, cmap): self._log_map = cmap
71
+ def getLogMap(self): return self._log_map
72
+
73
+ # routing primatives
74
+ def origin(self, msg):
75
+ colored = mapit(msg, self._origin_map)
76
+ print(colored)
77
+
78
+ def destination(self, msg):
79
+ if not self.server or not hasattr(self.server, "_rcon_send"):
80
+ return
81
+ colored = mapit(msg, self._destination_map)
82
+ self.server._rcon_send(
83
+ f'logtellraw targetless "{self.console_backspaces}{colored}"'
84
+ )
85
+
86
+ def log(self, msg, level="INFO"):
87
+ clean = msg
88
+ if self._log_map:
89
+ for tag in self._log_map.as_dict().keys():
90
+ clean = clean.replace(tag, "")
91
+ {
92
+ "INFO": self.logger.info,
93
+ "WARN": self.logger.warning,
94
+ "ERROR": self.logger.error
95
+ }[level](clean)
96
+
97
+ def broadcast(self, msg):
98
+ if hasattr(self.server, "sendBroadcast"):
99
+ colored = mapit(msg, self._broadcast_map)
100
+ self.server.sendBroadcast(colored)
101
+
102
+ # high level router
103
+ def say(
104
+ self,
105
+ template,
106
+ level="info",
107
+ destination=False,
108
+ broadcast=False,
109
+ log=True,
110
+ origin=True,
111
+ **fmt
112
+ ):
113
+ if not template:
114
+ return
115
+
116
+ if level == "info":
117
+ template = self.info_prefix + template
118
+ elif level == "warn":
119
+ template = self.warn_prefix + template
120
+ elif level == "error":
121
+ template = self.error_prefix + template
122
+
123
+ if self.formatter:
124
+ msg = self.formatter(template, **fmt)
125
+ else:
126
+ msg = template
127
+
128
+ if origin:
129
+ self.origin(msg)
130
+
131
+ if destination:
132
+ self.destination(msg)
133
+
134
+ if log:
135
+ self.log(msg, level=level.upper())
136
+
137
+ if broadcast:
138
+ self.broadcast(msg)
139
+
140
+ # level helpers
141
+ def info(self, template, **fmt):
142
+ self.say(self.info_prefix + template, level="info", **fmt)
143
+
144
+ def warn(self, template, **fmt):
145
+ self.say(self.warn_prefix + template, level="warn", **fmt)
146
+
147
+ def error(self, template, **fmt):
148
+ self.say(self.error_prefix + template, level="error", **fmt)
@@ -1,11 +1,7 @@
1
+ import inspect
1
2
  import requests
2
3
 
3
- from hungerlib.api import CommandAPI
4
- from hungerlib.api import ScheduleAPI
5
- from hungerlib.api import FileManagerAPI
6
- from hungerlib.api import BackupsAPI
7
- from hungerlib.api import DatabasesAPI
8
- from hungerlib.api import StartupAPI
4
+ from hungerlib.api import CommandAPI, ScheduleAPI, FileManagerAPI, BackupsAPI, DatabasesAPI, StartupAPI
9
5
 
10
6
 
11
7
  class Panel:
@@ -4,4 +4,4 @@ from .minecraft import MinecraftServer
4
4
  __all__ = [
5
5
  "GenericServer",
6
6
  "MinecraftServer",
7
- ]
7
+ ]
@@ -1,6 +1,6 @@
1
1
  # Universal server class
2
- from hungerlib import Panel
3
- from hungerlib.addons import MC_COLOR_MAP, ASCII_COLOR_MAP
2
+ from hungerlib.panel import Panel
3
+ from hungerlib.utils.colormaps import MC_COLOR_MAP, ASCII_COLOR_MAP
4
4
 
5
5
 
6
6
  class GenericServer:
@@ -187,10 +187,6 @@ class GenericServer:
187
187
 
188
188
 
189
189
  def getSchedule(self, schedule_id: int):
190
- """
191
- Returns a dict containing all schedule attributes for the given schedule_id.
192
- Works with raw ScheduleAPI responses.
193
- """
194
190
  resp = self.panel.schedules.list(self.server_id)
195
191
  data = resp.json()
196
192
  for item in data.get("data", []):
@@ -1,10 +1,9 @@
1
- # Minecraft server class
2
1
  import time
3
2
  import re
4
3
  import mcrcon
5
- from hungerlib import Panel
4
+ from hungerlib.panel import Panel
5
+ from hungerlib.utils.colormaps import MC_COLOR_MAP, ASCII_COLOR_MAP
6
6
  from hungerlib.servers import GenericServer
7
- from hungerlib.addons import MC_COLOR_MAP, ASCII_COLOR_MAP
8
7
 
9
8
 
10
9
  class MinecraftServer(GenericServer):
@@ -0,0 +1,25 @@
1
+ from .colormaps import ColorMap, ASCII_COLOR_MAP, MC_COLOR_MAP
2
+ from .time import (
3
+ snapSchedule,
4
+ runCountdownEvents,
5
+ waitForOnline,
6
+ waitForOffline,
7
+ secsUntil,
8
+ minsUntil
9
+ )
10
+ from .utils import Snapshot, clearTerminal, validateAll
11
+
12
+ __all__ = [
13
+ 'ColorMap',
14
+ 'ASCII_COLOR_MAP',
15
+ 'MC_COLOR_MAP',
16
+ 'snapSchedule',
17
+ 'runCountdownEvents',
18
+ 'waitForOnline',
19
+ 'waitForOffline',
20
+ 'secsUntil',
21
+ 'minsUntil',
22
+ 'Snapshot',
23
+ 'clearTerminal',
24
+ 'validateAll',
25
+ ]
@@ -0,0 +1,52 @@
1
+ from hungerlib.datamap import datamap, Syntax, mapit, set_default_maps
2
+
3
+ @datamap(syntax=Syntax.angles) # produces <>
4
+ class ColorMap:
5
+ black: str = "\033[30m"
6
+ dark_blue: str = "\033[34m"
7
+ dark_green: str = "\033[32m"
8
+ dark_aqua: str = "\033[36m"
9
+ dark_red: str = "\033[31m"
10
+ dark_purple: str = "\033[35m"
11
+ gold: str = "\033[33m"
12
+ gray: str = "\033[37m"
13
+ dark_gray: str = "\033[90m"
14
+ blue: str = "\033[94m"
15
+ green: str = "\033[92m"
16
+ aqua: str = "\033[96m"
17
+ red: str = "\033[91m"
18
+ light_purple: str = "\033[95m"
19
+ yellow: str = "\033[93m"
20
+ white: str = "\033[97m"
21
+ reset: str = "\033[0m"
22
+ bold: str = "\033[1m"
23
+ italic: str = "\033[3m"
24
+
25
+ def as_dict(self):
26
+ return {f"<{k}>": getattr(self, k) for k in self.__dataclass_fields__}
27
+
28
+ # ASCII color map
29
+ ASCII_COLOR_MAP = ColorMap()
30
+
31
+ # Minecraft color map
32
+ MC_COLOR_MAP = ColorMap(
33
+ black="§0",
34
+ dark_blue="§1",
35
+ dark_green="§2",
36
+ dark_aqua="§3",
37
+ dark_red="§4",
38
+ dark_purple="§5",
39
+ gold="§6",
40
+ gray="§7",
41
+ dark_gray="§8",
42
+ blue="§9",
43
+ green="§a",
44
+ aqua="§b",
45
+ red="§c",
46
+ light_purple="§d",
47
+ yellow="§e",
48
+ white="§f",
49
+ reset="§r",
50
+ bold="§l",
51
+ italic="§o"
52
+ )
@@ -21,33 +21,12 @@ def snapSchedule(minimumMinutes=30, snapMinutes=(0, 30)):
21
21
  "formatted": scheduled.strftime("%I:%M %p")
22
22
  }
23
23
 
24
-
25
24
  def runCountdownEvents(
26
25
  target_time,
27
26
  minute_callbacks=None,
28
27
  second_callbacks=None,
29
28
  tick_interval=1
30
29
  ):
31
- """
32
- A simple, readable countdown engine.
33
-
34
- - Counts down until `target_time`
35
- - When the countdown hits a configured minute mark, it runs that callback
36
- - When the countdown hits a configured second mark, it runs that callback
37
-
38
- Parameters:
39
- target_time (datetime):
40
- The time we are counting down to.
41
-
42
- minute_callbacks (dict[int, callable]):
43
- Example: {5: func, 1: func}
44
-
45
- second_callbacks (dict[int, callable]):
46
- Example: {10: func, 5: func, 1: func}
47
-
48
- tick_interval (int):
49
- How often to check the countdown (in seconds).
50
- """
51
30
 
52
31
  # Default to empty dicts if none provided
53
32
  minute_callbacks = minute_callbacks or {}
@@ -78,13 +57,7 @@ def runCountdownEvents(
78
57
 
79
58
  time.sleep(tick_interval)
80
59
 
81
-
82
-
83
60
  def waitForOnline(server, timeout=60, interval=2):
84
- """
85
- Wait until the server reports status 'running'.
86
- Returns True if online before timeout.
87
- """
88
61
  elapsed = 0
89
62
  while elapsed < timeout:
90
63
  if server.isOnline():
@@ -93,12 +66,7 @@ def waitForOnline(server, timeout=60, interval=2):
93
66
  elapsed += interval
94
67
  return False
95
68
 
96
-
97
69
  def waitForOffline(server, timeout=60, interval=2):
98
- """
99
- Wait until the server reports status 'offline'.
100
- Returns True if offline before timeout.
101
- """
102
70
  elapsed = 0
103
71
  while elapsed < timeout:
104
72
  if server.isOffline():
@@ -107,13 +75,10 @@ def waitForOffline(server, timeout=60, interval=2):
107
75
  elapsed += interval
108
76
  return False
109
77
 
110
-
111
-
112
78
  def secsUntil(target):
113
79
  now = datetime.now()
114
80
  return int((target - now).total_seconds())
115
81
 
116
-
117
82
  def minsUntil(target):
118
83
  now = datetime.now()
119
84
  return int((target - now).total_seconds()) // 60
@@ -1,3 +1,5 @@
1
+ import os
2
+
1
3
  class Snapshot:
2
4
  def __init__(self, Server, rounding=2, gb=False):
3
5
  self.Server = Server
@@ -28,4 +30,14 @@ class Snapshot:
28
30
  if self.players is not None:
29
31
  base += f'\nOnline players: {self.players}'
30
32
 
31
- return base
33
+ return base
34
+
35
+ def clearTerminal():
36
+ os.system("clear" if os.name == "posix" else "cls")
37
+
38
+ def validateAll(panel, server):
39
+ return (
40
+ panel.ping() is True and
41
+ panel.validateAPI() is True and
42
+ server.getStatus() == "running"
43
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hungerlib
3
- Version: 2.19.dev1
3
+ Version: 3.0
4
4
  Summary: Powerful automation library for Pterodactyl.
5
5
  Author: iFamished
6
6
  License: MIT
@@ -2,20 +2,15 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/hungerlib/__init__.py
5
- src/hungerlib/logger.py
5
+ src/hungerlib/configloader.py
6
+ src/hungerlib/datamap.py
7
+ src/hungerlib/messagerouter.py
6
8
  src/hungerlib/panel.py
7
9
  src/hungerlib.egg-info/PKG-INFO
8
10
  src/hungerlib.egg-info/SOURCES.txt
9
11
  src/hungerlib.egg-info/dependency_links.txt
10
12
  src/hungerlib.egg-info/requires.txt
11
13
  src/hungerlib.egg-info/top_level.txt
12
- src/hungerlib/addons/__init__.py
13
- src/hungerlib/addons/colormap.py
14
- src/hungerlib/addons/configloader.py
15
- src/hungerlib/addons/mchelpers.py
16
- src/hungerlib/addons/scheduler.py
17
- src/hungerlib/addons/snapshot.py
18
- src/hungerlib/addons/utils.py
19
14
  src/hungerlib/api/__init__.py
20
15
  src/hungerlib/api/backups.py
21
16
  src/hungerlib/api/command.py
@@ -25,4 +20,8 @@ src/hungerlib/api/schedule.py
25
20
  src/hungerlib/api/startup.py
26
21
  src/hungerlib/servers/__init__.py
27
22
  src/hungerlib/servers/generic.py
28
- src/hungerlib/servers/minecraft.py
23
+ src/hungerlib/servers/minecraft.py
24
+ src/hungerlib/utils/__init__.py
25
+ src/hungerlib/utils/colormaps.py
26
+ src/hungerlib/utils/time.py
27
+ src/hungerlib/utils/utils.py
@@ -1,33 +0,0 @@
1
- from importlib.metadata import version as _pkg_version, PackageNotFoundError
2
-
3
- # Package version
4
- try:
5
- __version__ = _pkg_version("hungerlib")
6
- except PackageNotFoundError:
7
- __version__ = "0.0.0"
8
-
9
- # --- Core modules ---
10
- from .panel import Panel
11
- from .logger import HungerLogger
12
-
13
- # --- API endpoints ---
14
- from .api.schedule import ScheduleAPI
15
- from .api.filemanager import FileManagerAPI
16
- from .api.backups import BackupsAPI
17
- from .api.databases import DatabasesAPI
18
- from .api.startup import StartupAPI
19
-
20
- __all__ = [
21
- "__version__",
22
-
23
- # core utilities
24
- "Panel",
25
- "HungerLogger",
26
-
27
- # API endpoints
28
- "ScheduleAPI",
29
- "FileManagerAPI",
30
- "BackupsAPI",
31
- "DatabasesAPI",
32
- "StartupAPI",
33
- ]
@@ -1,29 +0,0 @@
1
- from .colormap import *
2
- from .snapshot import Snapshot
3
- from .scheduler import *
4
- from .utils import *
5
- from .configloader import *
6
-
7
- __all__ = [
8
- 'ColorMap',
9
- 'MC_COLOR_MAP',
10
- 'ASCII_COLOR_MAP',
11
- 'runCountdownEvents',
12
- 'waitForOnline',
13
- 'waitForOffline',
14
- 'Snapshot',
15
- 'snapSchedule',
16
- 'secsUntil',
17
- 'minsUntil',
18
- 'validateAll',
19
- 'mb_gb',
20
- 'gb_mb',
21
- 'mib_gib',
22
- 'gib_mib',
23
- 'clearTerminal',
24
- 'load_yaml',
25
- 'loadConfig',
26
- 'clrz',
27
- 'set_default_colormap',
28
- 'get_default_colormap',
29
- ]
@@ -1,96 +0,0 @@
1
- # Environment-based color translation and mapping
2
- from dataclasses import dataclass, field
3
-
4
-
5
- @dataclass(frozen=True)
6
- class ColorMap:
7
- black: str
8
- dark_blue: str
9
- dark_green: str
10
- dark_aqua: str
11
- dark_red: str
12
- dark_purple: str
13
- gold: str
14
- gray: str
15
- dark_gray: str
16
- blue: str
17
- green: str
18
- aqua: str
19
- red: str
20
- light_purple: str
21
- yellow: str
22
- white: str
23
- reset: str
24
- bold: str
25
- italic: str
26
-
27
- def as_dict(self):
28
- """Convert dataclass to dict with <tags> as keys."""
29
- return {
30
- f"<{field}>": getattr(self, field)
31
- for field in self.__dataclass_fields__
32
- }
33
-
34
-
35
- MC_COLOR_MAP = ColorMap(
36
- black="§0",
37
- dark_blue="§1",
38
- dark_green="§2",
39
- dark_aqua="§3",
40
- dark_red="§4",
41
- dark_purple="§5",
42
- gold="§6",
43
- gray="§7",
44
- dark_gray="§8",
45
- blue="§9",
46
- green="§a",
47
- aqua="§b",
48
- red="§c",
49
- light_purple="§d",
50
- yellow="§e",
51
- white="§f",
52
- reset="§r",
53
- bold="§l",
54
- italic="§o"
55
- )
56
-
57
- ASCII_COLOR_MAP = ColorMap(
58
- black="\033[30m",
59
- dark_blue="\033[34m",
60
- dark_green="\033[32m",
61
- dark_aqua="\033[36m",
62
- dark_red="\033[31m",
63
- dark_purple="\033[35m",
64
- gold="\033[33m",
65
- gray="\033[37m",
66
- dark_gray="\033[90m",
67
- blue="\033[94m",
68
- green="\033[92m",
69
- aqua="\033[96m",
70
- red="\033[91m",
71
- light_purple="\033[95m",
72
- yellow="\033[93m",
73
- white="\033[97m",
74
- reset="\033[0m",
75
- bold="\033[1m",
76
- italic="\033[3m"
77
- )
78
-
79
- _DEFAULT_COLORMAP = ASCII_COLOR_MAP
80
-
81
- def set_default_colormap(cmap):
82
- global _DEFAULT_COLORMAP
83
- _DEFAULT_COLORMAP = cmap
84
-
85
- def get_default_colormap():
86
- return _DEFAULT_COLORMAP
87
-
88
- def clrz(text: str, cmap=None):
89
- if cmap is None:
90
- cmap = get_default_colormap()
91
- if hasattr(cmap, "as_dict"):
92
- cmap = cmap.as_dict()
93
- for tag, code in cmap.items():
94
- text = text.replace(tag, code)
95
- reset = cmap.get("<reset>", "")
96
- return text + reset
@@ -1,107 +0,0 @@
1
- # Minecraft-specific helper functions that operate on a MinecraftServer instance.
2
-
3
- class MCHelpers:
4
- def __init__(self, server):
5
- """
6
- Helper utilities for a MinecraftServer instance.
7
- """
8
- self.server = server
9
-
10
- def run(self, command):
11
- """Send a raw RCON command."""
12
- return self.server._rcon_send(command)
13
-
14
- def tellraw(self, target, message):
15
- """Send a tellraw message with color translation."""
16
- translated = self.server._translate_mc_colors(message)
17
- safe = translated.replace('"', '\\"')
18
- cmd = f'tellraw {target} {{"text":"{safe}"}}'
19
- return self.run(cmd)
20
-
21
- def kick(self, player, message="Kicked"):
22
- safe = message.replace('"', '\\"')
23
- return self.run(f'kick {player} "{safe}"')
24
-
25
- def kickAll(self, message="Server restarting"):
26
- safe = message.replace('"', '\\"')
27
- return self.run(f'kick @a "{safe}"')
28
-
29
- def whitelistAdd(self, player):
30
- return self.run(f"whitelist add {player}")
31
-
32
- def whitelistRemove(self, player):
33
- return self.run(f"whitelist remove {player}")
34
-
35
- def whitelistList(self):
36
- return self.run("whitelist list")
37
-
38
- def runFunction(self, function_name):
39
- """Run a datapack function."""
40
- return self.run(f"function {function_name}")
41
-
42
- def enableDatapack(self, name):
43
- return self.run(f"datapack enable {name}")
44
-
45
- def disableDatapack(self, name):
46
- return self.run(f"datapack disable {name}")
47
-
48
- def listDatapacks(self):
49
- return self.run("datapack list")
50
-
51
- # ============================================================
52
- # GAMERULE HELPERS
53
- # ============================================================
54
-
55
- def setGamerule(self, rule, value):
56
- return self.run(f"gamerule {rule} {value}")
57
-
58
- def getGamerule(self, rule):
59
- return self.run(f"gamerule {rule}")
60
-
61
- # ============================================================
62
- # SCOREBOARD HELPERS
63
- # ============================================================
64
-
65
- def scoreboardAddObjective(self, name, criteria="dummy", display_name=None):
66
- if display_name:
67
- safe = display_name.replace('"', '\\"')
68
- return self.run(f'scoreboard objectives add {name} {criteria} "{safe}"')
69
- return self.run(f"scoreboard objectives add {name} {criteria}")
70
-
71
- def scoreboardRemoveObjective(self, name):
72
- return self.run(f"scoreboard objectives remove {name}")
73
-
74
- def scoreboardSetDisplay(self, slot, objective):
75
- return self.run(f"scoreboard objectives setdisplay {slot} {objective}")
76
-
77
- def scoreboardSet(self, player, objective, value):
78
- return self.run(f"scoreboard players set {player} {objective} {value}")
79
-
80
- def scoreboardAdd(self, player, objective, value):
81
- return self.run(f"scoreboard players add {player} {objective} {value}")
82
-
83
- def scoreboardGet(self, player, objective):
84
- return self.run(f"scoreboard players get {player} {objective}")
85
-
86
- # ============================================================
87
- # TIME & WEATHER HELPERS
88
- # ============================================================
89
-
90
- def setTime(self, value):
91
- return self.run(f"time set {value}")
92
-
93
- def addTime(self, value):
94
- return self.run(f"time add {value}")
95
-
96
- def setWeather(self, weather, duration=None):
97
- if duration:
98
- return self.run(f"weather {weather} {duration}")
99
- return self.run(f"weather {weather}")
100
-
101
- # ============================================================
102
- # BROADCAST HELPERS
103
- # ============================================================
104
-
105
- def broadcast(self, message):
106
- """Broadcast using tellraw @a."""
107
- return self.tellraw("@a", message)
@@ -1,26 +0,0 @@
1
- import os
2
-
3
- def mb_gb(mb, floor=False):
4
- return mb / 1000 if not floor else mb // 1000
5
-
6
- def gb_mb(gb):
7
- return gb * 1000
8
-
9
- def mib_gib(mib, floor=False):
10
- return mib / 1024 if not floor else mib // 1024
11
-
12
- def gib_mib(gib):
13
- return gib * 1024
14
-
15
- def clearTerminal():
16
- os.system("clear" if os.name == "posix" else "cls")
17
-
18
- def validateAll(panel, server):
19
- """
20
- Validate panel connectivity, API access, and server running state.
21
- """
22
- return (
23
- panel.ping() is True and
24
- panel.validateAPI() is True and
25
- server.getStatus() == "running"
26
- )
@@ -1,124 +0,0 @@
1
- # Logging system
2
-
3
- import os
4
- os.environ['TZ'] = 'America/Chicago'
5
- import time
6
- time.tzset()
7
-
8
- import logging
9
- from pathlib import Path
10
- from datetime import datetime
11
- from hungerlib.addons import ASCII_COLOR_MAP, MC_COLOR_MAP, clrz
12
-
13
-
14
- class HungerLogger:
15
- def __init__(
16
- self,
17
- name,
18
- server,
19
- log_path,
20
- log_destination_method='rcon',
21
-
22
- # backspaces
23
- console_backspaces=0,
24
-
25
- # color mapping
26
- file_color_map=None,
27
- origin_color_map=ASCII_COLOR_MAP,
28
- destination_color_map=ASCII_COLOR_MAP,
29
-
30
- # prefixes
31
- info_prefix='<white>[INFO]: ',
32
- warn_prefix='<yellow>[WARN]: ',
33
- error_prefix='<red>[ERROR]: '
34
- ):
35
-
36
- self.name = name
37
- self.server = server
38
- self.log_path = Path(f'{log_path}')
39
- self.log_destination_method = log_destination_method
40
-
41
- self.console_backspaces = '\b' * console_backspaces
42
-
43
- self.file_color_map = file_color_map
44
- self.origin_color_map = origin_color_map
45
- self.destination_color_map = destination_color_map
46
-
47
- self.info_prefix = info_prefix
48
- self.warn_prefix = warn_prefix
49
- self.error_prefix = error_prefix
50
-
51
- self.prefixes = {
52
- "INFO": self.info_prefix,
53
- "WARN": self.warn_prefix,
54
- "ERROR": self.error_prefix
55
- }
56
-
57
- self.log_path.mkdir(parents=True, exist_ok=True)
58
- self.logger = logging.getLogger(name)
59
- self.logger.setLevel(logging.DEBUG)
60
- self._initializeLogger()
61
-
62
- def _initializeLogger(self):
63
- logFile = self.log_path / f"{self.name}_{datetime.now().strftime('%Y-%m-%d')}.log"
64
-
65
- if not self.logger.handlers:
66
- handler = logging.FileHandler(str(logFile))
67
- formatter = logging.Formatter(
68
- "[%(asctime)s] [%(levelname)s] %(message)s",
69
- datefmt="%H:%M:%S"
70
- )
71
- handler.setFormatter(formatter)
72
- self.logger.addHandler(handler)
73
-
74
- self.logger.info("Logger initialized.")
75
-
76
- def _strip_colors(self, msg):
77
- if not self.file_color_map:
78
- return msg
79
- for tag in self.file_color_map.as_dict().keys():
80
- msg = msg.replace(tag, "")
81
- return msg
82
-
83
- def _destinationLog(self, msg):
84
- if not self.server:
85
- return
86
- if not hasattr(self.server, "_rcon_send"):
87
- return
88
- colored = clrz(msg, cmap=self.destination_color_map)
89
- if self.log_destination_method == 'rcon':
90
- self.server._rcon_send(f'logtellraw targetless \"{self.console_backspaces}{colored}\"')
91
- if self.log_destination_method == 'api':
92
- self.server.sendConsoleCommand(f'logtellraw targetless \"{self.console_backspaces}{colored}\"')
93
-
94
-
95
- def _log(self, level, msg, destination, origin, logs):
96
- prefix = self.prefixes[level] + f"[{self.name}] "
97
- full = prefix + msg
98
-
99
- if destination:
100
- self._destinationLog(full)
101
-
102
- if origin:
103
- colored = clrz(full + "<reset>", cmap=self.origin_color_map)
104
- print(colored)
105
-
106
- if logs:
107
- clean = self._strip_colors(full)
108
- {
109
- "INFO": self.logger.info,
110
- "WARN": self.logger.warning,
111
- "ERROR": self.logger.error
112
- }[level](clean)
113
-
114
- def info(self, msg, destination=False, origin=True, logs=True):
115
- '''Log to the INFO channel'''
116
- self._log("INFO", msg, destination, origin, logs)
117
-
118
- def warn(self, msg, destination=False, origin=True, logs=True):
119
- '''Log to the WARN channel'''
120
- self._log("WARN", msg, destination, origin, logs)
121
-
122
- def error(self, msg, destination=False, origin=True, logs=True):
123
- '''Log to the ERROR channel'''
124
- self._log("ERROR", msg, destination, origin, logs)
File without changes
File without changes
File without changes