hungerlib 4.23.dev8__tar.gz → 4.24.dev1__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-4.23.dev8/src/hungerlib.egg-info → hungerlib-4.24.dev1}/PKG-INFO +1 -1
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/pyproject.toml +1 -1
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/__init__.py +17 -0
- hungerlib-4.24.dev1/src/hungerlib/discord_bot_webhook.py +14 -0
- hungerlib-4.24.dev1/src/hungerlib/email/__init__.py +9 -0
- hungerlib-4.24.dev1/src/hungerlib/email/manager.py +55 -0
- hungerlib-4.24.dev1/src/hungerlib/email/message.py +33 -0
- hungerlib-4.24.dev1/src/hungerlib/email/user.py +35 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/messagerouter.py +58 -58
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/servers/minecraft.py +0 -4
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/utils/__init__.py +13 -1
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1/src/hungerlib.egg-info}/PKG-INFO +1 -1
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib.egg-info/SOURCES.txt +5 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/LICENSE +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/README.md +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/setup.cfg +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/api/__init__.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/api/backups.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/api/command.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/api/databases.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/api/filemanager.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/api/schedule.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/api/startup.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/bridgeclient.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/configloader.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/panel.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/servers/__init__.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/servers/generic.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/utils/exceptions.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/utils/time.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/utils/utils.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib/validator.py +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib.egg-info/dependency_links.txt +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib.egg-info/requires.txt +0 -0
- {hungerlib-4.23.dev8 → hungerlib-4.24.dev1}/src/hungerlib.egg-info/top_level.txt +0 -0
|
@@ -13,6 +13,8 @@ from .messagerouter import MessageRouter
|
|
|
13
13
|
from .panel import Panel
|
|
14
14
|
from .servers import GenericServer, MinecraftServer
|
|
15
15
|
from .bridgeclient import BridgeClient
|
|
16
|
+
from .discord_bot_webhook import DiscordBotWebhook
|
|
17
|
+
from .email import Email, EmailManager, EmailUser
|
|
16
18
|
from .validator import (
|
|
17
19
|
Validator,
|
|
18
20
|
ValidationError,
|
|
@@ -32,6 +34,12 @@ from .utils import (
|
|
|
32
34
|
Snapshot,
|
|
33
35
|
clearTerminal,
|
|
34
36
|
validateAll,
|
|
37
|
+
|
|
38
|
+
# exceptions
|
|
39
|
+
HungerLibError,
|
|
40
|
+
InvalidLevelError,
|
|
41
|
+
InvalidModeError,
|
|
42
|
+
HungerBridgeError
|
|
35
43
|
)
|
|
36
44
|
|
|
37
45
|
# namespaces
|
|
@@ -65,7 +73,11 @@ __all__ = [
|
|
|
65
73
|
'GenericServer',
|
|
66
74
|
'MinecraftServer',
|
|
67
75
|
'BridgeClient',
|
|
76
|
+
'DiscordBotWebhook',
|
|
68
77
|
'Validator',
|
|
78
|
+
'Email',
|
|
79
|
+
'EmailManager',
|
|
80
|
+
'EmailUser',
|
|
69
81
|
|
|
70
82
|
# sub-modules
|
|
71
83
|
'snapSchedule',
|
|
@@ -85,6 +97,11 @@ __all__ = [
|
|
|
85
97
|
'FallbackError',
|
|
86
98
|
'RecommendedError',
|
|
87
99
|
|
|
100
|
+
'HungerLibError',
|
|
101
|
+
'InvalidLevelError',
|
|
102
|
+
'InvalidModeError',
|
|
103
|
+
'HungerBridgeError',
|
|
104
|
+
|
|
88
105
|
# namespaces
|
|
89
106
|
'utils',
|
|
90
107
|
'servers',
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
class DiscordBotWebhook:
|
|
4
|
+
def __init__(self, url: str, token: str):
|
|
5
|
+
self.url = url
|
|
6
|
+
self.token = token
|
|
7
|
+
|
|
8
|
+
def send(self, event: str, **ctx):
|
|
9
|
+
requests.post(
|
|
10
|
+
self.url,
|
|
11
|
+
json={"event": event, **ctx},
|
|
12
|
+
headers={"X-Webhook-Token": self.token},
|
|
13
|
+
timeout=5
|
|
14
|
+
).raise_for_status()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pathlib
|
|
3
|
+
from cloudflare import Cloudflare
|
|
4
|
+
from mapres import res
|
|
5
|
+
from .message import Email
|
|
6
|
+
|
|
7
|
+
TEMPLATE_DIR = pathlib.Path(__file__).resolve().parent.parent / 'email_templates'
|
|
8
|
+
|
|
9
|
+
class EmailManager:
|
|
10
|
+
'''
|
|
11
|
+
Backend email system.
|
|
12
|
+
Holds API token and account ID.
|
|
13
|
+
Sends emails created by EmailUser.
|
|
14
|
+
'''
|
|
15
|
+
def __init__(self, api_token: str, account_id: str):
|
|
16
|
+
self.client = Cloudflare(api_token=api_token)
|
|
17
|
+
self.account_id = account_id
|
|
18
|
+
|
|
19
|
+
# -------------------------
|
|
20
|
+
# Template loader
|
|
21
|
+
# -------------------------
|
|
22
|
+
def load_template(self, name: str) -> str:
|
|
23
|
+
path = TEMPLATE_DIR / f'{name}.html'
|
|
24
|
+
return path.read_text(encoding='utf8')
|
|
25
|
+
|
|
26
|
+
# -------------------------
|
|
27
|
+
# Template renderer
|
|
28
|
+
# -------------------------
|
|
29
|
+
def render_template(self, template: str, ctx: dict) -> str:
|
|
30
|
+
raw = self.load_template(template)
|
|
31
|
+
return res(raw, **ctx)
|
|
32
|
+
|
|
33
|
+
# -------------------------
|
|
34
|
+
# Actual sending
|
|
35
|
+
# -------------------------
|
|
36
|
+
def send_email(self, user, email: Email) -> bool:
|
|
37
|
+
to_str = ','.join(email.to)
|
|
38
|
+
cc_str = ','.join(email.cc) if email.cc else None
|
|
39
|
+
bcc_str = ','.join(email.bcc) if email.bcc else None
|
|
40
|
+
|
|
41
|
+
if email.template and not email.html:
|
|
42
|
+
email.html = self.render_template(email.template, email.template_ctx)
|
|
43
|
+
|
|
44
|
+
resp = self.client.email_sending.send(
|
|
45
|
+
account_id=self.account_id,
|
|
46
|
+
from_=f'{user.name} <{user.address}>',
|
|
47
|
+
to=to_str,
|
|
48
|
+
cc=cc_str,
|
|
49
|
+
bcc=bcc_str,
|
|
50
|
+
subject=email.subject,
|
|
51
|
+
text=email.text,
|
|
52
|
+
html=email.html
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return bool(getattr(resp, 'delivered', None))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
class Email:
|
|
4
|
+
'''
|
|
5
|
+
Simple email data container.
|
|
6
|
+
'''
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
subject: str,
|
|
10
|
+
text: str | None = None,
|
|
11
|
+
html: str | None = None,
|
|
12
|
+
to: list | str | None = None,
|
|
13
|
+
cc: list | str | None = None,
|
|
14
|
+
bcc: list | str | None = None,
|
|
15
|
+
template: str | None = None,
|
|
16
|
+
template_ctx: dict | None = None
|
|
17
|
+
):
|
|
18
|
+
self.subject = subject
|
|
19
|
+
self.text = text or ''
|
|
20
|
+
self.html = html or ''
|
|
21
|
+
self.to = self._normalize(to)
|
|
22
|
+
self.cc = self._normalize(cc)
|
|
23
|
+
self.bcc = self._normalize(bcc)
|
|
24
|
+
self.template = template
|
|
25
|
+
self.template_ctx = template_ctx or {}
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def _normalize(value) -> list[str]:
|
|
29
|
+
if value is None:
|
|
30
|
+
return []
|
|
31
|
+
if isinstance(value, str):
|
|
32
|
+
return [s.strip() for s in value.split(',') if s.strip()]
|
|
33
|
+
return [str(x).strip() for x in value]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from .message import Email
|
|
3
|
+
|
|
4
|
+
class EmailUser:
|
|
5
|
+
'''
|
|
6
|
+
Represents a sender identity.
|
|
7
|
+
The frontend API for sending emails.
|
|
8
|
+
'''
|
|
9
|
+
def __init__(self, name: str, address: str, manager):
|
|
10
|
+
self.name = name
|
|
11
|
+
self.address = address
|
|
12
|
+
self.manager = manager # backend EmailManager
|
|
13
|
+
|
|
14
|
+
def send(
|
|
15
|
+
self,
|
|
16
|
+
subject: str,
|
|
17
|
+
text: str | None = None,
|
|
18
|
+
html: str | None = None,
|
|
19
|
+
to: list | str | None = None,
|
|
20
|
+
cc: list | str | None = None,
|
|
21
|
+
bcc: list | str | None = None,
|
|
22
|
+
template: str | None = None,
|
|
23
|
+
template_ctx: dict | None = None
|
|
24
|
+
):
|
|
25
|
+
email = Email(
|
|
26
|
+
subject=subject,
|
|
27
|
+
text=text,
|
|
28
|
+
html=html,
|
|
29
|
+
to=to,
|
|
30
|
+
cc=cc,
|
|
31
|
+
bcc=bcc,
|
|
32
|
+
template=template,
|
|
33
|
+
template_ctx=template_ctx
|
|
34
|
+
)
|
|
35
|
+
return self.manager.send_email(self, email)
|
|
@@ -51,6 +51,14 @@ class MessageRouter:
|
|
|
51
51
|
# custom level registry
|
|
52
52
|
self.custom_levels = {}
|
|
53
53
|
|
|
54
|
+
# routing table
|
|
55
|
+
self.ROUTES = {
|
|
56
|
+
'origin': self.origin,
|
|
57
|
+
'destination': self.destination,
|
|
58
|
+
'broadcast': self.broadcast,
|
|
59
|
+
'filelog': self.filelog
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
def registerLevel(self, name, prefix, file_method=None, routes=None):
|
|
55
63
|
lvl = name.lower()
|
|
56
64
|
self.custom_levels[lvl] = {
|
|
@@ -59,21 +67,9 @@ class MessageRouter:
|
|
|
59
67
|
'routes': routes or ['origin', 'filelog']
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
rts = cfg['routes']
|
|
66
|
-
if 'origin' in rts:
|
|
67
|
-
self.origin(text, level=level_name, **ctx)
|
|
68
|
-
if 'destination' in rts:
|
|
69
|
-
self.destination(text, level=level_name, **ctx)
|
|
70
|
-
if 'broadcast' in rts:
|
|
71
|
-
self.broadcast(text, level=level_name, **ctx)
|
|
72
|
-
if 'filelog' in rts:
|
|
73
|
-
self.filelog(text, level=level_name, **ctx)
|
|
74
|
-
return _method
|
|
75
|
-
|
|
76
|
-
setattr(self, lvl, make_method(lvl))
|
|
70
|
+
def _method(text, **ctx):
|
|
71
|
+
self._route(lvl, text, None, None, ctx)
|
|
72
|
+
setattr(self, lvl, _method)
|
|
77
73
|
|
|
78
74
|
def _init_file_logger(self):
|
|
79
75
|
log_file = self.log_path / f'{self.name}_{datetime.now().strftime("%Y-%m-%d")}.log'
|
|
@@ -95,29 +91,53 @@ class MessageRouter:
|
|
|
95
91
|
maps.extend(extra)
|
|
96
92
|
return maps
|
|
97
93
|
|
|
94
|
+
# prefix resolver
|
|
95
|
+
def _resolve_prefix(self, level, ctx):
|
|
96
|
+
lvl = level.lower() if isinstance(level, str) else None
|
|
97
|
+
|
|
98
|
+
if lvl == 'info':
|
|
99
|
+
return self.res(self.info_prefix, override_maps=self.prefix_maps)
|
|
100
|
+
if lvl == 'warn':
|
|
101
|
+
return self.res(self.warn_prefix, override_maps=self.prefix_maps)
|
|
102
|
+
if lvl == 'error':
|
|
103
|
+
return self.res(self.error_prefix, override_maps=self.prefix_maps)
|
|
104
|
+
if lvl in self.custom_levels:
|
|
105
|
+
return self.res(self.custom_levels[lvl]['prefix'], override_maps=self.prefix_maps)
|
|
106
|
+
if level is None:
|
|
107
|
+
return ''
|
|
108
|
+
if lvl == 'custom':
|
|
109
|
+
return ctx.get('prefix', '')
|
|
110
|
+
|
|
111
|
+
return self.res(level, override_maps=self.prefix_maps)
|
|
112
|
+
|
|
113
|
+
# routing engine
|
|
114
|
+
def _route(self, level, text, extra_maps, override_maps, ctx):
|
|
115
|
+
cfg = self.custom_levels.get(level.lower())
|
|
116
|
+
routes = cfg['routes'] if cfg else ['origin', 'filelog']
|
|
117
|
+
|
|
118
|
+
for r in routes:
|
|
119
|
+
self.ROUTES[r](text, level=level, extra_maps=extra_maps, override_maps=override_maps, **ctx)
|
|
120
|
+
|
|
121
|
+
# file logging
|
|
122
|
+
def _log_file(self, level, msg):
|
|
123
|
+
lvl = level.lower()
|
|
124
|
+
if lvl in self.custom_levels:
|
|
125
|
+
method = self.custom_levels[lvl]['file_method']
|
|
126
|
+
if method:
|
|
127
|
+
getattr(self.logger, method)(msg)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
{
|
|
131
|
+
'info': self.logger.info,
|
|
132
|
+
'warn': self.logger.warning,
|
|
133
|
+
'error': self.logger.error
|
|
134
|
+
}[lvl](msg)
|
|
135
|
+
|
|
98
136
|
# routing primitives
|
|
99
137
|
def origin(self, text, level='info', extra_maps=None, override_maps=None, **ctx):
|
|
100
|
-
level_norm = level.lower() if isinstance(level, str) else None
|
|
101
|
-
|
|
102
138
|
maps = self._merge_maps(override_maps or self.origin_maps, extra_maps)
|
|
103
139
|
mapped = self._format(text, maps, **ctx)
|
|
104
|
-
|
|
105
|
-
# Prefix resolution
|
|
106
|
-
if level_norm == 'info':
|
|
107
|
-
prefix = self.res(self.info_prefix, override_maps=self.prefix_maps)
|
|
108
|
-
elif level_norm == 'warn':
|
|
109
|
-
prefix = self.res(self.warn_prefix, override_maps=self.prefix_maps)
|
|
110
|
-
elif level_norm == 'error':
|
|
111
|
-
prefix = self.res(self.error_prefix, override_maps=self.prefix_maps)
|
|
112
|
-
elif level_norm in self.custom_levels:
|
|
113
|
-
prefix = self.res(self.custom_levels[level_norm]['prefix'], override_maps=self.prefix_maps)
|
|
114
|
-
elif level is None:
|
|
115
|
-
prefix = ''
|
|
116
|
-
elif level_norm == 'custom':
|
|
117
|
-
prefix = ctx.get('prefix', '')
|
|
118
|
-
else:
|
|
119
|
-
prefix = self.res(level, override_maps=self.prefix_maps)
|
|
120
|
-
|
|
140
|
+
prefix = self._resolve_prefix(level, ctx)
|
|
121
141
|
msg = prefix + mapped
|
|
122
142
|
print(msg)
|
|
123
143
|
return msg
|
|
@@ -125,7 +145,6 @@ class MessageRouter:
|
|
|
125
145
|
def destination(self, text, level='info', extra_maps=None, override_maps=None, **ctx):
|
|
126
146
|
maps = self._merge_maps(override_maps or self.destination_maps, extra_maps)
|
|
127
147
|
msg = self._format(text, maps, **ctx)
|
|
128
|
-
|
|
129
148
|
for server in self.Servers:
|
|
130
149
|
if hasattr(server, 'bridge'):
|
|
131
150
|
server.bridge.log(msg, level)
|
|
@@ -134,7 +153,6 @@ class MessageRouter:
|
|
|
134
153
|
def broadcast(self, text, extra_maps=None, override_maps=None, **ctx):
|
|
135
154
|
maps = self._merge_maps(override_maps or self.broadcast_maps, extra_maps)
|
|
136
155
|
msg = self._format(text, maps, **ctx)
|
|
137
|
-
|
|
138
156
|
for server in self.Servers:
|
|
139
157
|
if hasattr(server, 'sendBroadcast'):
|
|
140
158
|
server.sendBroadcast(msg)
|
|
@@ -143,33 +161,15 @@ class MessageRouter:
|
|
|
143
161
|
def filelog(self, text, level='info', extra_maps=None, override_maps=None, **ctx):
|
|
144
162
|
maps = self._merge_maps(override_maps or self.file_maps, extra_maps)
|
|
145
163
|
msg = self._format(text, maps, **ctx)
|
|
146
|
-
|
|
147
|
-
# custom file logging
|
|
148
|
-
if level in self.custom_levels:
|
|
149
|
-
method = self.custom_levels[level]['file_method']
|
|
150
|
-
if method:
|
|
151
|
-
getattr(self.logger, method)(msg)
|
|
152
|
-
return msg
|
|
153
|
-
|
|
154
|
-
{
|
|
155
|
-
'info': self.logger.info,
|
|
156
|
-
'warn': self.logger.warning,
|
|
157
|
-
'error': self.logger.error
|
|
158
|
-
}[level](msg)
|
|
164
|
+
self._log_file(level, msg)
|
|
159
165
|
return msg
|
|
160
166
|
|
|
161
167
|
# passthrough helpers
|
|
162
168
|
def info(self, text, extra_maps=None, override_maps=None, **ctx):
|
|
163
|
-
|
|
164
|
-
self.filelog(text, 'info', extra_maps, override_maps, **ctx)
|
|
165
|
-
return msg
|
|
169
|
+
self._route('info', text, extra_maps, override_maps, ctx)
|
|
166
170
|
|
|
167
171
|
def warn(self, text, extra_maps=None, override_maps=None, **ctx):
|
|
168
|
-
|
|
169
|
-
self.filelog(text, 'warn', extra_maps, override_maps, **ctx)
|
|
170
|
-
return msg
|
|
172
|
+
self._route('warn', text, extra_maps, override_maps, ctx)
|
|
171
173
|
|
|
172
174
|
def error(self, text, extra_maps=None, override_maps=None, **ctx):
|
|
173
|
-
|
|
174
|
-
self.filelog(text, 'error', extra_maps, override_maps, **ctx)
|
|
175
|
-
return msg
|
|
175
|
+
self._route('error', text, extra_maps, override_maps, ctx)
|
|
@@ -69,11 +69,7 @@ class MinecraftServer(GenericServer):
|
|
|
69
69
|
There are 0 of a max of 20 players online:
|
|
70
70
|
'''
|
|
71
71
|
try:
|
|
72
|
-
raw = self.bridge.runCommand('list', show_console=True, silent=False, normalize=False)
|
|
73
|
-
print("RAW OUTPUT:", repr(raw))
|
|
74
|
-
|
|
75
72
|
output = self.bridge.runCommand('list', show_console=False, silent=False, normalize=True)
|
|
76
|
-
print("NORMALIZED OUTPUT:", repr(output))
|
|
77
73
|
except Exception:
|
|
78
74
|
return 0
|
|
79
75
|
if not output:
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
from .utils import Snapshot, clearTerminal, validateAll
|
|
2
|
+
from .exceptions import (
|
|
3
|
+
HungerLibError,
|
|
4
|
+
InvalidLevelError,
|
|
5
|
+
InvalidModeError,
|
|
6
|
+
HungerBridgeError
|
|
7
|
+
)
|
|
1
8
|
from .time import (
|
|
2
9
|
snapSchedule,
|
|
3
10
|
runCountdownEvents,
|
|
@@ -6,7 +13,6 @@ from .time import (
|
|
|
6
13
|
secsUntil,
|
|
7
14
|
minsUntil
|
|
8
15
|
)
|
|
9
|
-
from .utils import Snapshot, clearTerminal, validateAll
|
|
10
16
|
|
|
11
17
|
__all__ = [
|
|
12
18
|
'snapSchedule',
|
|
@@ -18,4 +24,10 @@ __all__ = [
|
|
|
18
24
|
'Snapshot',
|
|
19
25
|
'clearTerminal',
|
|
20
26
|
'validateAll',
|
|
27
|
+
|
|
28
|
+
# exceptions
|
|
29
|
+
'HungerLibError',
|
|
30
|
+
'InvalidLevelError',
|
|
31
|
+
'InvalidModeError',
|
|
32
|
+
'HungerBridgeError',
|
|
21
33
|
]
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
src/hungerlib/__init__.py
|
|
5
5
|
src/hungerlib/bridgeclient.py
|
|
6
6
|
src/hungerlib/configloader.py
|
|
7
|
+
src/hungerlib/discord_bot_webhook.py
|
|
7
8
|
src/hungerlib/messagerouter.py
|
|
8
9
|
src/hungerlib/panel.py
|
|
9
10
|
src/hungerlib/validator.py
|
|
@@ -19,6 +20,10 @@ src/hungerlib/api/databases.py
|
|
|
19
20
|
src/hungerlib/api/filemanager.py
|
|
20
21
|
src/hungerlib/api/schedule.py
|
|
21
22
|
src/hungerlib/api/startup.py
|
|
23
|
+
src/hungerlib/email/__init__.py
|
|
24
|
+
src/hungerlib/email/manager.py
|
|
25
|
+
src/hungerlib/email/message.py
|
|
26
|
+
src/hungerlib/email/user.py
|
|
22
27
|
src/hungerlib/servers/__init__.py
|
|
23
28
|
src/hungerlib/servers/generic.py
|
|
24
29
|
src/hungerlib/servers/minecraft.py
|
|
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
|