calagoapi 1.0.dev1__py3-none-any.whl

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 (71) hide show
  1. calagoapi/__init__.py +190 -0
  2. calagoapi/account/__init__.py +14 -0
  3. calagoapi/account/account.py +292 -0
  4. calagoapi/account/auth.py +72 -0
  5. calagoapi/account/models.py +221 -0
  6. calagoapi/admin/__init__.py +48 -0
  7. calagoapi/admin/admin.py +341 -0
  8. calagoapi/admin/managers.py +490 -0
  9. calagoapi/admin/resources.py +970 -0
  10. calagoapi/aio/__init__.py +9 -0
  11. calagoapi/aio/console.py +289 -0
  12. calagoapi/aio/panel.py +176 -0
  13. calagoapi/api/__init__.py +63 -0
  14. calagoapi/api/account.py +195 -0
  15. calagoapi/api/admin.py +402 -0
  16. calagoapi/api/attachments.py +27 -0
  17. calagoapi/api/auth.py +52 -0
  18. calagoapi/api/backups.py +99 -0
  19. calagoapi/api/client.py +198 -0
  20. calagoapi/api/databases.py +217 -0
  21. calagoapi/api/files.py +210 -0
  22. calagoapi/api/network.py +84 -0
  23. calagoapi/api/public.py +13 -0
  24. calagoapi/api/schedules.py +79 -0
  25. calagoapi/api/servers.py +114 -0
  26. calagoapi/api/users.py +27 -0
  27. calagoapi/api/versions.py +29 -0
  28. calagoapi/backups/__init__.py +8 -0
  29. calagoapi/backups/backup.py +292 -0
  30. calagoapi/backups/manager.py +177 -0
  31. calagoapi/console/__init__.py +6 -0
  32. calagoapi/console/console.py +392 -0
  33. calagoapi/databases/__init__.py +12 -0
  34. calagoapi/databases/database.py +445 -0
  35. calagoapi/databases/explorer.py +159 -0
  36. calagoapi/databases/manager.py +122 -0
  37. calagoapi/files/__init__.py +9 -0
  38. calagoapi/files/file.py +196 -0
  39. calagoapi/files/manager.py +437 -0
  40. calagoapi/network/__init__.py +8 -0
  41. calagoapi/network/allocation.py +99 -0
  42. calagoapi/network/manager.py +132 -0
  43. calagoapi/nodes/__init__.py +6 -0
  44. calagoapi/nodes/node.py +477 -0
  45. calagoapi/panel.py +162 -0
  46. calagoapi/schedules/__init__.py +14 -0
  47. calagoapi/schedules/builders.py +461 -0
  48. calagoapi/schedules/manager.py +107 -0
  49. calagoapi/schedules/schedule.py +214 -0
  50. calagoapi/servers/__init__.py +13 -0
  51. calagoapi/servers/admin.py +430 -0
  52. calagoapi/servers/generic.py +785 -0
  53. calagoapi/servers/manager.py +167 -0
  54. calagoapi/servers/minecraft.py +207 -0
  55. calagoapi/tunnels/__init__.py +5 -0
  56. calagoapi/tunnels/tunnel.py +112 -0
  57. calagoapi/users/__init__.py +11 -0
  58. calagoapi/users/activity.py +45 -0
  59. calagoapi/users/subuser.py +127 -0
  60. calagoapi/users/user.py +267 -0
  61. calagoapi/utils/__init__.py +43 -0
  62. calagoapi/utils/convert.py +113 -0
  63. calagoapi/utils/exceptions.py +72 -0
  64. calagoapi/utils/model.py +142 -0
  65. calagoapi/utils/resource.py +100 -0
  66. calagoapi/utils/time.py +68 -0
  67. calagoapi-1.0.dev1.dist-info/METADATA +266 -0
  68. calagoapi-1.0.dev1.dist-info/RECORD +71 -0
  69. calagoapi-1.0.dev1.dist-info/WHEEL +5 -0
  70. calagoapi-1.0.dev1.dist-info/licenses/LICENSE +674 -0
  71. calagoapi-1.0.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,167 @@
1
+ from calagoapi.servers.generic import GenericServer
2
+ from calagoapi.servers.minecraft import MinecraftServer
3
+ from calagoapi.utils.model import Model, idOf
4
+
5
+
6
+ class ServerGroup(Model):
7
+ '''
8
+ A personal folder of servers in the panel sidebar.
9
+
10
+ Example:
11
+ group = panel.servers.createGroup('Minigames', [lobby, bedwars])
12
+ group.addServer(skywars)
13
+ '''
14
+ def _api(self):
15
+ return self.panel.api.servers
16
+
17
+ def getName(self) -> str:
18
+ '''Returns the group name.'''
19
+ return self.data.get('name')
20
+
21
+ def getOrder(self) -> int:
22
+ '''Returns the group's position in the sidebar.'''
23
+ return self.data.get('order')
24
+
25
+ def getServerIds(self) -> list:
26
+ '''Returns the UUIDs of the servers in the group, in display order.'''
27
+ return self.data.get('server_order', [])
28
+
29
+ def getServers(self, search: str = None, page: int = None, per_page: int = None):
30
+ '''Returns the servers in the group as GenericServer objects.'''
31
+ return self._api().groupServers(self.getUUID(), search, page, per_page).map(
32
+ lambda data: GenericServer(self.panel, data=data)
33
+ )
34
+
35
+ def rename(self, name: str) -> 'ServerGroup':
36
+ '''Renames the group.'''
37
+ self._api().updateGroup(self.getUUID(), name)
38
+ return self._update({'name': name})
39
+
40
+ def setServers(self, servers: list) -> 'ServerGroup':
41
+ '''Replaces the servers in the group (objects or ids), in display order.'''
42
+ server_order = [idOf(s) for s in servers]
43
+ self._api().updateGroup(self.getUUID(), self.getName(), server_order)
44
+ return self._update({'server_order': server_order})
45
+
46
+ def addServer(self, server) -> 'ServerGroup':
47
+ '''Adds a server to the group.'''
48
+ return self.setServers(self.getServerIds() + [idOf(server)])
49
+
50
+ def removeServer(self, server) -> 'ServerGroup':
51
+ '''Removes a server from the group.'''
52
+ return self.setServers([s for s in self.getServerIds() if s != idOf(server)])
53
+
54
+ def delete(self) -> None:
55
+ '''Deletes the group (the servers are not affected).'''
56
+ return self._api().deleteGroup(self.getUUID())
57
+
58
+
59
+ class ServerManager:
60
+ '''
61
+ Access to the servers the API key can see, available as `panel.servers`.
62
+
63
+ Example:
64
+ server = panel.servers.getServer('abc123')
65
+ for server in panel.servers.getServers():
66
+ print(server.getName(), server.getStatus())
67
+ '''
68
+ def __init__(self, panel):
69
+ self.panel = panel
70
+ self.api = panel.api.servers
71
+
72
+ def getServer(self, server_id: str, name: str = None) -> GenericServer:
73
+ '''
74
+ Returns a server.
75
+
76
+ Args:
77
+ server_id: Server UUID or short id.
78
+ name: Optional display name used in logs and repr().
79
+
80
+ Example:
81
+ server = panel.servers.getServer('abc123')
82
+ '''
83
+ server = GenericServer(self.panel, server_id, name)
84
+ server.refreshDetails()
85
+ if name is None:
86
+ server.name = server.getName()
87
+ return server
88
+
89
+ def getMinecraftServer(self, server_id: str, name: str = None, server_domain: str = None, server_port: int = None) -> MinecraftServer:
90
+ '''
91
+ Returns a server with the Minecraft helpers of MinecraftServer.
92
+
93
+ Example:
94
+ server = panel.servers.getMinecraftServer('abc123')
95
+ server.say('Hello')
96
+ '''
97
+ server = MinecraftServer(self.panel, server_id, name, server_domain, server_port)
98
+ server.refreshDetails()
99
+ if name is None:
100
+ server.name = server.getName()
101
+ return server
102
+
103
+ def getServers(self, search: str = None, page: int = None, per_page: int = None, all_servers: bool = False):
104
+ '''
105
+ Lists servers.
106
+
107
+ Args:
108
+ search: Filter by name.
109
+ page: Only fetch this page. By default every server is returned.
110
+ per_page: Page size.
111
+ all_servers: Admins only, include servers the user does not own or have access to.
112
+
113
+ Returns:
114
+ A Page (list) of GenericServer objects.
115
+ '''
116
+ return self.api.list(search, all_servers or None, page, per_page).map(
117
+ lambda data: GenericServer(self.panel, data=data)
118
+ )
119
+
120
+ def listServers(self, search: str = None):
121
+ '''Alias of getServers().'''
122
+ return self.getServers(search)
123
+
124
+ def getServerByName(self, name: str) -> GenericServer:
125
+ '''Returns the first server whose name matches exactly, or None.'''
126
+ for server in self.getServers(search=name):
127
+ if server.getName() == name:
128
+ return server
129
+ return None
130
+
131
+ def getEggs(self, search: str = None, page: int = None, per_page: int = None):
132
+ '''Returns the eggs (server types) used by the user's servers.'''
133
+ return self.api.eggs(search, page, per_page)
134
+
135
+ def getCommandSnippets(self, egg) -> list:
136
+ '''Returns the user's command snippets that apply to an egg.'''
137
+ return self.api.eggCommandSnippets(egg.get('uuid') if isinstance(egg, dict) else idOf(egg))
138
+
139
+ # groups
140
+ def getGroups(self) -> list:
141
+ '''Returns the user's server groups.'''
142
+ return [ServerGroup(self.panel, data) for data in self.api.groups()]
143
+
144
+ def getGroup(self, group_id_or_name: str) -> ServerGroup:
145
+ '''Returns a server group by UUID or name, or None.'''
146
+ for group in self.getGroups():
147
+ if group_id_or_name in (group.getUUID(), group.getName()):
148
+ return group
149
+ return None
150
+
151
+ def createGroup(self, name: str, servers: list = None) -> ServerGroup:
152
+ '''
153
+ Creates a server group.
154
+
155
+ Args:
156
+ name: Group name.
157
+ servers: Servers (objects or ids) to put in it.
158
+ '''
159
+ return ServerGroup(self.panel, self.api.createGroup(name, [idOf(s) for s in servers or []]))
160
+
161
+ def deleteGroup(self, group) -> None:
162
+ '''Deletes a server group.'''
163
+ return self.api.deleteGroup(idOf(group))
164
+
165
+ def orderGroups(self, groups: list) -> None:
166
+ '''Sets the order of the server groups.'''
167
+ return self.api.orderGroups([idOf(g) for g in groups])
@@ -0,0 +1,207 @@
1
+ import json
2
+
3
+ from calagoapi.servers.generic import GenericServer
4
+ from calagoapi.utils.model import idOf
5
+ from calagoapi.utils.exceptions import NotFoundError
6
+
7
+
8
+ class MinecraftServer(GenericServer):
9
+ '''
10
+ A Minecraft server.
11
+
12
+ Adds Minecraft helpers on top of GenericServer: console shortcuts,
13
+ server.properties editing, EULA handling and the Calagopus Minecraft
14
+ Version Changer (install any Paper, Purpur, Fabric, Forge, ... build).
15
+
16
+ Args:
17
+ panel: The Panel the server lives on.
18
+ server_id: Server UUID (or short id).
19
+ name: Optional display name.
20
+ server_domain: Optional address players use, overrides getAddress().
21
+ server_port: Optional port players use.
22
+ data: Optional server data, if already fetched.
23
+
24
+ Example:
25
+ server = panel.servers.getMinecraftServer('abc123')
26
+ server.say('Restarting in 5 minutes!')
27
+ server.setProperty('max-players', 50)
28
+
29
+ build = server.getBuilds('PAPER', '1.21.1')[0]
30
+ server.installVersion(build)
31
+ '''
32
+ def __init__(self, panel, server_id: str = None, name: str = None, server_domain: str = None,
33
+ server_port: int = None, data: dict = None):
34
+ super().__init__(panel, server_id, name, data)
35
+
36
+ # Minecraft-specific fields
37
+ self.server_domain = server_domain
38
+ self.server_port = server_port
39
+
40
+ def getAddress(self) -> str:
41
+ '''Returns server_domain:server_port if set, otherwise the primary allocation address.'''
42
+ if self.server_domain:
43
+ return f'{self.server_domain}:{self.server_port or 25565}'
44
+ return super().getAddress()
45
+
46
+
47
+ # console helpers
48
+ def say(self, message: str) -> None:
49
+ '''Broadcasts a chat message as the server.'''
50
+ return self.sendCommand(f'say {message}')
51
+
52
+ def broadcast(self, message: str) -> None:
53
+ '''Alias of say().'''
54
+ return self.say(message)
55
+
56
+ def tellraw(self, target: str, component) -> None:
57
+ '''
58
+ Sends a JSON text component.
59
+
60
+ Example:
61
+ server.tellraw('@a', {'text': 'Hello', 'color': 'gold'})
62
+ '''
63
+ if not isinstance(component, str):
64
+ component = json.dumps(component)
65
+ return self.sendCommand(f'tellraw {target} {component}')
66
+
67
+ def kick(self, player: str, reason: str = None) -> None:
68
+ '''Kicks a player.'''
69
+ return self.sendCommand(f'kick {player} {reason}' if reason else f'kick {player}')
70
+
71
+ def ban(self, player: str, reason: str = None) -> None:
72
+ '''Bans a player.'''
73
+ return self.sendCommand(f'ban {player} {reason}' if reason else f'ban {player}')
74
+
75
+ def pardon(self, player: str) -> None:
76
+ '''Unbans a player.'''
77
+ return self.sendCommand(f'pardon {player}')
78
+
79
+ def op(self, player: str) -> None:
80
+ '''Gives a player operator status.'''
81
+ return self.sendCommand(f'op {player}')
82
+
83
+ def deop(self, player: str) -> None:
84
+ '''Removes operator status from a player.'''
85
+ return self.sendCommand(f'deop {player}')
86
+
87
+ def whitelistAdd(self, player: str) -> None:
88
+ '''Adds a player to the whitelist.'''
89
+ return self.sendCommand(f'whitelist add {player}')
90
+
91
+ def whitelistRemove(self, player: str) -> None:
92
+ '''Removes a player from the whitelist.'''
93
+ return self.sendCommand(f'whitelist remove {player}')
94
+
95
+ def saveAll(self) -> None:
96
+ '''Saves every world.'''
97
+ return self.sendCommand('save-all')
98
+
99
+
100
+ # server.properties
101
+ def getProperties(self) -> dict:
102
+ '''
103
+ Reads server.properties.
104
+
105
+ Returns:
106
+ dict of property name -> string value.
107
+
108
+ Example:
109
+ server.getProperties()['max-players']
110
+ '''
111
+ properties = {}
112
+ for line in self.files.readFile('/server.properties').splitlines():
113
+ line = line.strip()
114
+ if not line or line.startswith('#') or '=' not in line:
115
+ continue
116
+ key, value = line.split('=', 1)
117
+ properties[key.strip()] = value.strip()
118
+ return properties
119
+
120
+ def getProperty(self, key: str, default=None) -> str:
121
+ '''Returns one server.properties value.'''
122
+ return self.getProperties().get(key, default)
123
+
124
+ def setProperty(self, key: str, value) -> None:
125
+ '''Changes one server.properties value. Takes effect after a restart.'''
126
+ return self.setProperties({key: value})
127
+
128
+ def setProperties(self, values: dict) -> None:
129
+ '''
130
+ Changes several server.properties values, keeping comments and order.
131
+
132
+ Example:
133
+ server.setProperties({'motd': 'Welcome!', 'max-players': 100})
134
+ '''
135
+ values = {k: _propertyValue(v) for k, v in values.items()}
136
+ lines = []
137
+ for line in self.files.readFile('/server.properties').splitlines():
138
+ key = line.split('=', 1)[0].strip()
139
+ if '=' in line and not line.lstrip().startswith('#') and key in values:
140
+ line = f'{key}={values.pop(key)}'
141
+ lines.append(line)
142
+ lines.extend(f'{k}={v}' for k, v in values.items())
143
+ return self.files.writeFile('/server.properties', '\n'.join(lines) + '\n')
144
+
145
+ def getMOTD(self) -> str:
146
+ '''Returns the message of the day from server.properties.'''
147
+ return self.getProperty('motd')
148
+
149
+ def setMOTD(self, motd: str) -> None:
150
+ '''Sets the message of the day.'''
151
+ return self.setProperty('motd', motd)
152
+
153
+ def acceptEula(self) -> None:
154
+ '''Writes eula=true to eula.txt.'''
155
+ return self.files.writeFile('/eula.txt', 'eula=true\n')
156
+
157
+ def isEulaAccepted(self) -> bool:
158
+ '''Returns True if eula.txt contains eula=true.'''
159
+ try:
160
+ return 'eula=true' in self.files.readFile('/eula.txt').replace(' ', '').lower()
161
+ except NotFoundError:
162
+ return False
163
+
164
+
165
+ # version changer
166
+ def getInstalledVersion(self) -> dict:
167
+ '''Returns the installed build (type, version, build), or None if unknown.'''
168
+ return self.panel.api.versions.installed(self.server_id)
169
+
170
+ def getVersionTypes(self) -> dict:
171
+ '''Returns the available server types (PAPER, PURPUR, FABRIC, VANILLA, ...).'''
172
+ return self.panel.api.versions.types(self.server_id)
173
+
174
+ def getVersions(self, type: str, search: str = None, page: int = None, per_page: int = None):
175
+ '''
176
+ Returns the Minecraft versions available for a server type.
177
+
178
+ Example:
179
+ server.getVersions('PAPER', page=1, per_page=10)
180
+ '''
181
+ return self.panel.api.versions.versions(self.server_id, type, search, page, per_page)
182
+
183
+ def getBuilds(self, type: str, version: str, search: str = None, page: int = None, per_page: int = None):
184
+ '''Returns the builds of a version, newest first.'''
185
+ return self.panel.api.versions.builds(self.server_id, type, version, search, page, per_page)
186
+
187
+ def installVersion(self, build, truncate_directory: bool = False, accept_eula: bool = True) -> None:
188
+ '''
189
+ Installs a server build.
190
+
191
+ Args:
192
+ build: Build dict from getBuilds() or build UUID.
193
+ truncate_directory: Delete every file first.
194
+ accept_eula: Accept the Minecraft EULA.
195
+
196
+ Example:
197
+ latest = server.getBuilds('PAPER', '1.21.1', page=1, per_page=1)[0]
198
+ server.installVersion(latest)
199
+ '''
200
+ build_id = build.get('uuid') if isinstance(build, dict) else idOf(build)
201
+ return self.panel.api.versions.install(self.server_id, build_id, truncate_directory, accept_eula)
202
+
203
+
204
+ def _propertyValue(value):
205
+ if isinstance(value, bool):
206
+ return 'true' if value else 'false'
207
+ return str(value)
@@ -0,0 +1,5 @@
1
+ from .tunnel import TunnelManager
2
+
3
+ __all__ = [
4
+ 'TunnelManager',
5
+ ]
@@ -0,0 +1,112 @@
1
+ from calagoapi.utils.model import idOf
2
+
3
+
4
+ class TunnelManager:
5
+ '''
6
+ The server's network tunnel, available as `server.tunnel`.
7
+
8
+ A tunnel exposes server ports through the node's tunnel without opening
9
+ them publicly, and lets servers reach each other privately.
10
+
11
+ Example:
12
+ if server.tunnel.isSupported() and not server.tunnel.exists():
13
+ server.tunnel.createTunnel('survival')
14
+ server.tunnel.setPorts([25565])
15
+ server.tunnel.connect(proxy_server)
16
+ print(server.tunnel.getAddress())
17
+ '''
18
+ def __init__(self, server):
19
+ self.server = server
20
+ self.api = server.panel.api.network
21
+
22
+ @property
23
+ def server_id(self):
24
+ return self.server.server_id
25
+
26
+ def getInfo(self) -> dict:
27
+ '''
28
+ Returns the full tunnel state.
29
+
30
+ Returns:
31
+ dict with supported, tunnel, ports, allocation_ports, outgoing and incoming.
32
+ '''
33
+ return self.api.tunnel(self.server_id)
34
+
35
+ def getTunnel(self) -> dict:
36
+ '''Returns the tunnel (name, alias, address), or None if none exists.'''
37
+ return self.getInfo().get('tunnel')
38
+
39
+ def isSupported(self) -> bool:
40
+ '''Returns True if the server's node supports tunnels.'''
41
+ return bool(self.getInfo().get('supported'))
42
+
43
+ def exists(self) -> bool:
44
+ '''Returns True if the server has a tunnel.'''
45
+ return self.getTunnel() is not None
46
+
47
+ def getName(self) -> str:
48
+ '''Returns the tunnel name.'''
49
+ return (self.getTunnel() or {}).get('name')
50
+
51
+ def getAlias(self) -> str:
52
+ '''Returns the tunnel alias (the hostname other servers use).'''
53
+ return (self.getTunnel() or {}).get('alias')
54
+
55
+ def getAddress(self) -> str:
56
+ '''Returns the public tunnel address, if any.'''
57
+ return (self.getTunnel() or {}).get('address')
58
+
59
+ def getPorts(self) -> list:
60
+ '''Returns the ports exposed through the tunnel.'''
61
+ return self.getInfo().get('ports', [])
62
+
63
+ def getOutgoing(self) -> list:
64
+ '''Returns the servers this server can reach through the tunnel.'''
65
+ return self.getInfo().get('outgoing', [])
66
+
67
+ def getIncoming(self) -> list:
68
+ '''Returns the servers that can reach this server through the tunnel.'''
69
+ return self.getInfo().get('incoming', [])
70
+
71
+ def createTunnel(self, name: str = None) -> dict:
72
+ '''Creates the tunnel. Returns the tunnel info.'''
73
+ return self.api.createTunnel(self.server_id, name)
74
+
75
+ def rename(self, name: str) -> dict:
76
+ '''Renames the tunnel.'''
77
+ return self.api.renameTunnel(self.server_id, name)
78
+
79
+ def deleteTunnel(self) -> None:
80
+ '''Deletes the tunnel.'''
81
+ return self.api.deleteTunnel(self.server_id)
82
+
83
+ def setPorts(self, ports: list) -> None:
84
+ '''
85
+ Sets the exposed ports.
86
+
87
+ Args:
88
+ ports: List of port numbers (tcp + udp), or {'port': 25565, 'protocols': ['tcp']} dicts.
89
+
90
+ Example:
91
+ server.tunnel.setPorts([25565, {'port': 19132, 'protocols': ['udp']}])
92
+ '''
93
+ ports = [p if isinstance(p, dict) else {'port': p, 'protocols': ['tcp', 'udp']} for p in ports]
94
+ return self.api.setTunnelPorts(self.server_id, ports)
95
+
96
+ def getConnectableServers(self, search: str = None, page: int = None, per_page: int = None):
97
+ '''Returns servers this tunnel can connect to.'''
98
+ return self.api.tunnelConnectable(self.server_id, search, None, page, per_page)
99
+
100
+ def connect(self, server) -> None:
101
+ '''Lets this server reach another server (object or id) through the tunnel.'''
102
+ return self.api.connectTunnel(self.server_id, idOf(server))
103
+
104
+ def disconnect(self, server, incoming: bool = False) -> None:
105
+ '''
106
+ Removes a tunnel connection.
107
+
108
+ Args:
109
+ server: The connected server (object or id).
110
+ incoming: Remove the connection that reaches this server instead of the outgoing one.
111
+ '''
112
+ return self.api.disconnectTunnel(self.server_id, idOf(server), incoming or None)
@@ -0,0 +1,11 @@
1
+ from .activity import Activity
2
+ from .subuser import Subuser, SubuserManager
3
+ from .user import User, UserManager
4
+
5
+ __all__ = [
6
+ 'Activity',
7
+ 'Subuser',
8
+ 'SubuserManager',
9
+ 'User',
10
+ 'UserManager',
11
+ ]
@@ -0,0 +1,45 @@
1
+ from calagoapi.utils.model import Model
2
+
3
+
4
+ class Activity(Model):
5
+ '''
6
+ One entry of an activity log (server, account or panel-wide).
7
+
8
+ Example:
9
+ for entry in server.getActivity(page=1, per_page=10):
10
+ print(entry.getCreated(), entry.getUsername(), entry.getEvent())
11
+ '''
12
+ def __repr__(self):
13
+ return f'<Activity {self.getEvent()!r} by {self.getUsername()!r}>'
14
+
15
+ def getEvent(self) -> str:
16
+ '''Returns the event name, e.g. "server:power.start".'''
17
+ return self.data.get('event')
18
+
19
+ def getUser(self) -> dict:
20
+ '''Returns the user who caused the event, or None for system events.'''
21
+ return self.data.get('user')
22
+
23
+ def getUsername(self) -> str:
24
+ '''Returns the username who caused the event, or None.'''
25
+ return (self.data.get('user') or {}).get('username')
26
+
27
+ def getImpersonator(self) -> dict:
28
+ '''Returns the admin who acted on behalf of the user, if any.'''
29
+ return self.data.get('impersonator')
30
+
31
+ def getIP(self) -> str:
32
+ '''Returns the IP address the event came from.'''
33
+ return self.data.get('ip')
34
+
35
+ def getData(self) -> dict:
36
+ '''Returns event specific details.'''
37
+ return self.data.get('data')
38
+
39
+ def isApi(self) -> bool:
40
+ '''Returns True if the event was caused through the API.'''
41
+ return bool(self.data.get('is_api'))
42
+
43
+ def isSchedule(self) -> bool:
44
+ '''Returns True if the event was caused by a schedule.'''
45
+ return bool(self.data.get('is_schedule'))
@@ -0,0 +1,127 @@
1
+ from calagoapi.utils.model import Model, idOf
2
+
3
+
4
+ class Subuser(Model):
5
+ '''
6
+ A user who has been given access to a server.
7
+
8
+ Args:
9
+ server: The server.
10
+ data: Raw subuser data from Calagopus.
11
+
12
+ Example:
13
+ subuser = server.users.addSubuser('friend@example.com', ['control.console', 'control.start'])
14
+ subuser.addPermissions(['file.read'])
15
+ print(subuser.getUsername(), subuser.getPermissions())
16
+ '''
17
+ def __init__(self, server, data: dict):
18
+ super().__init__(server.panel, data)
19
+ self.server = server
20
+
21
+ def __repr__(self):
22
+ return f'<Subuser {self.getUsername()!r}>'
23
+
24
+ def getUUID(self) -> str:
25
+ '''Returns the user's UUID.'''
26
+ return (self.data.get('user') or {}).get('uuid')
27
+
28
+ def getUser(self) -> dict:
29
+ '''Returns the raw user info (uuid, username, avatar, totp_enabled).'''
30
+ return self.data.get('user', {})
31
+
32
+ def getUsername(self) -> str:
33
+ '''Returns the username.'''
34
+ return self.getUser().get('username')
35
+
36
+ def getAvatar(self) -> str:
37
+ '''Returns the avatar URL, if any.'''
38
+ return self.getUser().get('avatar')
39
+
40
+ def getPermissions(self) -> list:
41
+ '''Returns the granted permissions, e.g. ["control.console", "file.read"].'''
42
+ return self.data.get('permissions', [])
43
+
44
+ def getIgnoredFiles(self) -> list:
45
+ '''Returns the file patterns hidden from this user.'''
46
+ return self.data.get('ignored_files', [])
47
+
48
+ def hasPermission(self, permission: str) -> bool:
49
+ '''Returns True if the subuser has the permission.'''
50
+ return permission in self.getPermissions()
51
+
52
+ def hasTwoFactor(self) -> bool:
53
+ '''Returns True if the user has two-factor authentication enabled.'''
54
+ return bool(self.getUser().get('totp_enabled'))
55
+
56
+ def setPermissions(self, permissions: list) -> 'Subuser':
57
+ '''Replaces the subuser's permissions.'''
58
+ self.panel.api.users.update(self.server.server_id, self.getUUID(), permissions=permissions)
59
+ return self._update({'permissions': list(permissions)})
60
+
61
+ def addPermissions(self, permissions: list) -> 'Subuser':
62
+ '''Grants additional permissions.'''
63
+ current = self.getPermissions()
64
+ return self.setPermissions(current + [p for p in permissions if p not in current])
65
+
66
+ def removePermissions(self, permissions: list) -> 'Subuser':
67
+ '''Revokes permissions.'''
68
+ return self.setPermissions([p for p in self.getPermissions() if p not in permissions])
69
+
70
+ def setIgnoredFiles(self, ignored_files: list) -> 'Subuser':
71
+ '''Sets file patterns the subuser cannot see.'''
72
+ self.panel.api.users.update(self.server.server_id, self.getUUID(), ignored_files=ignored_files)
73
+ return self._update({'ignored_files': list(ignored_files)})
74
+
75
+ def delete(self) -> None:
76
+ '''Removes the subuser from the server.'''
77
+ return self.panel.api.users.delete(self.server.server_id, self.getUUID())
78
+
79
+
80
+ class SubuserManager:
81
+ '''
82
+ Subuser management for one server, available as `server.users`.
83
+
84
+ Use `panel.account.getPermissions()` to list every permission that exists.
85
+
86
+ Example:
87
+ server.users.addSubuser('friend@example.com', ['control.console'])
88
+ for subuser in server.users.getSubusers():
89
+ print(subuser.getUsername())
90
+ '''
91
+ def __init__(self, server):
92
+ self.server = server
93
+ self.api = server.panel.api.users
94
+
95
+ def getSubusers(self, search: str = None, page: int = None, per_page: int = None):
96
+ '''Lists the server's subusers as a Page of Subuser objects.'''
97
+ return self.api.list(self.server.server_id, search, page, per_page).map(lambda data: Subuser(self.server, data))
98
+
99
+ def getSubuser(self, uuid_or_username: str) -> Subuser:
100
+ '''Returns a subuser by user UUID or username, or None.'''
101
+ for subuser in self.getSubusers():
102
+ if uuid_or_username in (subuser.getUUID(), subuser.getUsername()):
103
+ return subuser
104
+ return None
105
+
106
+ def addSubuser(self, email: str, permissions: list, ignored_files: list = None) -> Subuser:
107
+ '''
108
+ Invites a user to the server.
109
+
110
+ Args:
111
+ email: Email of the user. An account is created if needed.
112
+ permissions: Permissions to grant.
113
+ ignored_files: File patterns to hide from the user.
114
+ '''
115
+ return Subuser(self.server, self.api.create(self.server.server_id, email, permissions, ignored_files))
116
+
117
+ def createSubuser(self, email: str, permissions: list, ignored_files: list = None) -> Subuser:
118
+ '''Alias of addSubuser().'''
119
+ return self.addSubuser(email, permissions, ignored_files)
120
+
121
+ def updateSubuser(self, subuser, permissions: list = None, ignored_files: list = None) -> None:
122
+ '''Changes a subuser's permissions and/or ignored files.'''
123
+ return self.api.update(self.server.server_id, idOf(subuser), permissions, ignored_files)
124
+
125
+ def deleteSubuser(self, subuser) -> None:
126
+ '''Removes a subuser from the server.'''
127
+ return self.api.delete(self.server.server_id, idOf(subuser))