hungerlib 4.23.dev9__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.
Files changed (35) hide show
  1. {hungerlib-4.23.dev9/src/hungerlib.egg-info → hungerlib-4.24.dev1}/PKG-INFO +1 -1
  2. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/pyproject.toml +1 -1
  3. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/__init__.py +17 -0
  4. hungerlib-4.24.dev1/src/hungerlib/discord_bot_webhook.py +14 -0
  5. hungerlib-4.24.dev1/src/hungerlib/email/__init__.py +9 -0
  6. hungerlib-4.24.dev1/src/hungerlib/email/manager.py +55 -0
  7. hungerlib-4.24.dev1/src/hungerlib/email/message.py +33 -0
  8. hungerlib-4.24.dev1/src/hungerlib/email/user.py +35 -0
  9. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/messagerouter.py +58 -58
  10. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/utils/__init__.py +13 -1
  11. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1/src/hungerlib.egg-info}/PKG-INFO +1 -1
  12. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib.egg-info/SOURCES.txt +5 -0
  13. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/LICENSE +0 -0
  14. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/README.md +0 -0
  15. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/setup.cfg +0 -0
  16. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/api/__init__.py +0 -0
  17. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/api/backups.py +0 -0
  18. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/api/command.py +0 -0
  19. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/api/databases.py +0 -0
  20. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/api/filemanager.py +0 -0
  21. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/api/schedule.py +0 -0
  22. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/api/startup.py +0 -0
  23. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/bridgeclient.py +0 -0
  24. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/configloader.py +0 -0
  25. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/panel.py +0 -0
  26. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/servers/__init__.py +0 -0
  27. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/servers/generic.py +0 -0
  28. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/servers/minecraft.py +0 -0
  29. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/utils/exceptions.py +0 -0
  30. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/utils/time.py +0 -0
  31. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/utils/utils.py +0 -0
  32. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib/validator.py +0 -0
  33. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib.egg-info/dependency_links.txt +0 -0
  34. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/src/hungerlib.egg-info/requires.txt +0 -0
  35. {hungerlib-4.23.dev9 → hungerlib-4.24.dev1}/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: 4.23.dev9
3
+ Version: 4.24.dev1
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 = "4.23.dev9"
10
+ version = "4.24-dev.1"
11
11
  description = "Powerful automation library for Pterodactyl."
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.9"
@@ -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,9 @@
1
+ from .message import Email
2
+ from .manager import EmailManager
3
+ from .user import EmailUser
4
+
5
+ __all__ = [
6
+ 'Email',
7
+ 'EmailManager',
8
+ 'EmailUser',
9
+ ]
@@ -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 make_method(level_name):
63
- def _method(text, **ctx):
64
- cfg = self.custom_levels[level_name]
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
- msg = self.origin(text, 'info', extra_maps, override_maps, **ctx)
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
- msg = self.origin(text, 'warn', extra_maps, override_maps, **ctx)
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
- msg = self.origin(text, 'error', extra_maps, override_maps, **ctx)
174
- self.filelog(text, 'error', extra_maps, override_maps, **ctx)
175
- return msg
175
+ self._route('error', text, extra_maps, override_maps, ctx)
@@ -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
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hungerlib
3
- Version: 4.23.dev9
3
+ Version: 4.24.dev1
4
4
  Summary: Powerful automation library for Pterodactyl.
5
5
  Author: iFamished
6
6
  License: MIT
@@ -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