nsarchive 3.0.0a1__tar.gz → 3.0.0a2__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.
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/PKG-INFO +1 -2
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/nsarchive/__init__.py +2 -5
- nsarchive-3.0.0a2/nsarchive/cls/base.py +237 -0
- nsarchive-3.0.0a2/nsarchive/cls/entities.py +392 -0
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/nsarchive/instances/_economy.py +51 -8
- nsarchive-3.0.0a2/nsarchive/instances/_entities.py +230 -0
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/nsarchive/instances/_republic.py +33 -7
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/pyproject.toml +1 -2
- nsarchive-3.0.0a1/nsarchive/cls/base.py +0 -187
- nsarchive-3.0.0a1/nsarchive/cls/entities.py +0 -299
- nsarchive-3.0.0a1/nsarchive/cls/exceptions.py +0 -25
- nsarchive-3.0.0a1/nsarchive/instances/_entities.py +0 -321
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/LICENSE +0 -0
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/README.md +0 -0
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/nsarchive/assets/default_avatar.png +0 -0
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/nsarchive/cls/archives.py +0 -0
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/nsarchive/cls/economy.py +0 -0
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/nsarchive/cls/republic.py +0 -0
- {nsarchive-3.0.0a1 → nsarchive-3.0.0a2}/nsarchive/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nsarchive
|
3
|
-
Version: 3.0.
|
3
|
+
Version: 3.0.0a2
|
4
4
|
Summary: API-wrapper pour récupérer des données liées à Nation
|
5
5
|
License: GPL-3.0
|
6
6
|
Author: happex
|
@@ -12,7 +12,6 @@ Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
14
14
|
Requires-Dist: pillow (>=10.4,<11.0)
|
15
|
-
Requires-Dist: supabase (>=2.9.1,<3.0.0)
|
16
15
|
Description-Content-Type: text/markdown
|
17
16
|
|
18
17
|
# NSArchive
|
@@ -1,27 +1,24 @@
|
|
1
1
|
"""
|
2
2
|
nsarchive - API-wrapper pour récupérer des données liées à Nation.
|
3
3
|
|
4
|
-
Version:
|
4
|
+
Version: 3.0.0a2
|
5
5
|
License: GPL-3.0
|
6
6
|
Auteur : happex <110610727+okayhappex@users.noreply.github.com>
|
7
7
|
|
8
8
|
Dependencies:
|
9
9
|
- Python ^3.10
|
10
|
-
- supabase ^2.9.1
|
11
10
|
- pillow ^10.4
|
12
11
|
|
13
12
|
Le fichier README.md fournit des détails supplémentaires pour l'utilisation.
|
14
13
|
"""
|
15
14
|
|
16
|
-
# Import des types
|
15
|
+
# Import des types
|
17
16
|
from .cls.base import NSID
|
18
17
|
from .cls.archives import *
|
19
18
|
from .cls.entities import *
|
20
19
|
from .cls.republic import *
|
21
20
|
from .cls.economy import *
|
22
21
|
|
23
|
-
from .cls.exceptions import *
|
24
|
-
|
25
22
|
# Import des instances
|
26
23
|
from .instances._economy import EconomyInstance
|
27
24
|
from .instances._entities import EntityInstance
|
@@ -0,0 +1,237 @@
|
|
1
|
+
import io
|
2
|
+
import json
|
3
|
+
import requests
|
4
|
+
import typing
|
5
|
+
import warnings
|
6
|
+
|
7
|
+
class NSID(str):
|
8
|
+
"""
|
9
|
+
Nation Server ID
|
10
|
+
|
11
|
+
ID unique et universel pour l'ensemble des entités et évènements. Il prend les `int`, les `str` et les autres instances `NSID` pour les convertir en un identifiant hexadécimal.
|
12
|
+
"""
|
13
|
+
unknown = "0"
|
14
|
+
admin = "1"
|
15
|
+
gov = "2"
|
16
|
+
court = "3"
|
17
|
+
assembly = "4"
|
18
|
+
office = "5"
|
19
|
+
hexabank = "6"
|
20
|
+
archives = "7"
|
21
|
+
|
22
|
+
maintenance_com = "101"
|
23
|
+
audiovisual_dept = "102"
|
24
|
+
interior_dept = "103"
|
25
|
+
justice_dept = "104"
|
26
|
+
egalitary_com = "105"
|
27
|
+
antifraud_dept = "106"
|
28
|
+
|
29
|
+
def __new__(cls, value):
|
30
|
+
if type(value) == int:
|
31
|
+
value = hex(value)
|
32
|
+
elif type(value) in (str, NSID):
|
33
|
+
value = hex(int(value, 16))
|
34
|
+
else:
|
35
|
+
raise TypeError(f"<{value}> is not NSID serializable")
|
36
|
+
|
37
|
+
if value.startswith("0x"):
|
38
|
+
value = value[2:]
|
39
|
+
|
40
|
+
instance = super(NSID, cls).__new__(cls, value.upper())
|
41
|
+
return instance
|
42
|
+
|
43
|
+
class Instance:
|
44
|
+
"""
|
45
|
+
Instance qui servira de base à toutes les instances.
|
46
|
+
"""
|
47
|
+
|
48
|
+
def __init__(self, url: str, token: str = None):
|
49
|
+
self.url = url
|
50
|
+
self.token = token
|
51
|
+
|
52
|
+
self.default_headers = {
|
53
|
+
"Authorization": f"Bearer {self.token}",
|
54
|
+
"Content-Type": "application/json",
|
55
|
+
}
|
56
|
+
|
57
|
+
def request_token(self, username: str, password: str) -> str | None:
|
58
|
+
res = requests.post(f"{self.url}/auth/login", json = {
|
59
|
+
"username": username,
|
60
|
+
"password": password
|
61
|
+
})
|
62
|
+
|
63
|
+
if res.status_code == 200:
|
64
|
+
return res.json()["token"]
|
65
|
+
elif res.status_code in (401, 403):
|
66
|
+
raise PermissionError(res.json()['message'])
|
67
|
+
else:
|
68
|
+
raise Exception(f"Error {res.status_code}: {res.json()['message']}")
|
69
|
+
|
70
|
+
def _get_item(self, endpoint: str, body: dict = None, headers: dict = None) -> dict:
|
71
|
+
"""
|
72
|
+
Récupère des données JSON depuis l'API
|
73
|
+
|
74
|
+
## Paramètres
|
75
|
+
endpoint: `str`:
|
76
|
+
Endpoint de l'URL
|
77
|
+
headers: `dict` (optional)
|
78
|
+
Headers à envoyer
|
79
|
+
body: `dict` (optional)
|
80
|
+
Données à envoyer
|
81
|
+
|
82
|
+
## Renvoie
|
83
|
+
- `list` de tous les élements correspondants
|
84
|
+
- `None` si aucune donnée n'est trouvée
|
85
|
+
"""
|
86
|
+
|
87
|
+
if not headers:
|
88
|
+
headers = self.default_headers
|
89
|
+
|
90
|
+
res = requests.get(f"{self.url}/{endpoint}", headers = headers, json = body, timeout = 5)
|
91
|
+
|
92
|
+
if 200 <= res.status_code < 300:
|
93
|
+
return res.json()
|
94
|
+
elif res.status_code == 404:
|
95
|
+
return
|
96
|
+
elif res.status_code in (403, 401):
|
97
|
+
raise PermissionError(res.json()['message'])
|
98
|
+
else:
|
99
|
+
raise Exception(f"Error {res.status_code}: {res.json()['message']}")
|
100
|
+
|
101
|
+
def _get_by_ID(self, _class: str, id: NSID) -> dict:
|
102
|
+
_data = self._get_item(f"/model/{_class}/{id}")
|
103
|
+
|
104
|
+
return _data
|
105
|
+
|
106
|
+
def _put_in_db(self, endpoint: str, body: dict, headers: dict = None, use_PUT: bool = False) -> None:
|
107
|
+
"""
|
108
|
+
Publie des données JSON dans une table nation-db.
|
109
|
+
|
110
|
+
## Paramètres
|
111
|
+
endpoint: `str`
|
112
|
+
Endpoint de l'URL
|
113
|
+
body: `dict`
|
114
|
+
Données à envoyer
|
115
|
+
headers: `dict` (optionnel)
|
116
|
+
Headers à envoyer
|
117
|
+
"""
|
118
|
+
|
119
|
+
if not headers:
|
120
|
+
headers = headers
|
121
|
+
|
122
|
+
if use_PUT:
|
123
|
+
res = requests.put(f"{self.url}/{endpoint}", headers = headers, json = body)
|
124
|
+
else:
|
125
|
+
res = requests.post(f"{self.url}/{endpoint}", headers = headers, json = body)
|
126
|
+
|
127
|
+
if 200 <= res.status_code < 300:
|
128
|
+
return res.json()
|
129
|
+
else:
|
130
|
+
print(res.text)
|
131
|
+
res.raise_for_status()
|
132
|
+
|
133
|
+
def _delete(self, _class: str, ids: list[NSID]) -> None:
|
134
|
+
"""
|
135
|
+
Supprime des données JSON dans une table nation-db.
|
136
|
+
|
137
|
+
## Paramètres
|
138
|
+
_class: `str`
|
139
|
+
Classe des entités à supprimer
|
140
|
+
ids: `list[NSID]`
|
141
|
+
ID des entités à supprimer
|
142
|
+
"""
|
143
|
+
|
144
|
+
res = requests.post(f"{self.url}/delete_{_class}", json = { "ids": ids })
|
145
|
+
|
146
|
+
if 200 <= res.status_code < 300:
|
147
|
+
return res.json()
|
148
|
+
elif res.status_code in (403, 401):
|
149
|
+
raise PermissionError(res.json()['message'])
|
150
|
+
else:
|
151
|
+
raise Exception(f"Error {res.status_code}: {res.json()['message']}")
|
152
|
+
|
153
|
+
def _delete_by_ID(self, _class: str, id: NSID):
|
154
|
+
warnings.showwarning("Method '_delete_by_id' is deprecated. Use '_delete' instead.")
|
155
|
+
self._delete(_class, id)
|
156
|
+
|
157
|
+
def fetch(self, _class: str, **query: typing.Any) -> list:
|
158
|
+
res = requests.get(f"{self.url}/fetch/{_class}", params = query)
|
159
|
+
|
160
|
+
if res.status_code == 200:
|
161
|
+
matches = res.json()
|
162
|
+
elif res.status_code in (401, 403):
|
163
|
+
matches = []
|
164
|
+
else:
|
165
|
+
res.raise_for_status()
|
166
|
+
|
167
|
+
return matches
|
168
|
+
|
169
|
+
|
170
|
+
def _upload_file(self, bucket: str, name: str, data: bytes, overwrite: bool = False, headers: dict = None) -> dict:
|
171
|
+
"""
|
172
|
+
Envoie un fichier dans un bucket nation-db.
|
173
|
+
|
174
|
+
## Paramètres
|
175
|
+
bucket: `str`
|
176
|
+
Nom du bucket où le fichier sera stocké
|
177
|
+
name: `str`
|
178
|
+
Nom du fichier dans le drive
|
179
|
+
data: `bytes`
|
180
|
+
Données à uploader
|
181
|
+
overwrite: `bool` (optional)
|
182
|
+
Overwrite ou non
|
183
|
+
headers: `dict` (optional)
|
184
|
+
Headers à envoyer
|
185
|
+
|
186
|
+
## Renvoie
|
187
|
+
- `dict` contenant les informations de l'upload si réussi
|
188
|
+
- `None` en cas d'échec
|
189
|
+
"""
|
190
|
+
|
191
|
+
if not headers:
|
192
|
+
headers = self.default_headers
|
193
|
+
headers['Content-Type'] = 'image/png'
|
194
|
+
|
195
|
+
body = {
|
196
|
+
"name": name,
|
197
|
+
"overwrite": json.dumps(overwrite)
|
198
|
+
}
|
199
|
+
|
200
|
+
file = ("file", "image/png", data)
|
201
|
+
|
202
|
+
res = requests.put(f"{self.url}/upload_file/{bucket}", headers = headers, json = body, files = [ file ])
|
203
|
+
|
204
|
+
if res.status_code == 200:
|
205
|
+
return res.json()
|
206
|
+
elif res.status_code in (403, 401):
|
207
|
+
raise PermissionError(res.json()['message'])
|
208
|
+
elif res.status_code == 409:
|
209
|
+
raise FileExistsError(res.json()['message'])
|
210
|
+
else:
|
211
|
+
raise Exception(f"Error {res.status_code}: {res.json()['message']}")
|
212
|
+
|
213
|
+
def _download_from_storage(self, bucket: str, path: str, headers: dict = None) -> bytes:
|
214
|
+
"""
|
215
|
+
Télécharge un fichier depuis le stockage nation-db.
|
216
|
+
|
217
|
+
## Paramètres
|
218
|
+
bucket: `str`\n
|
219
|
+
Nom du bucket où il faut chercher le fichier
|
220
|
+
path: `str`\n
|
221
|
+
Chemin du fichier dans le bucket
|
222
|
+
|
223
|
+
## Renvoie
|
224
|
+
- Le fichier demandé en `bytes`
|
225
|
+
"""
|
226
|
+
|
227
|
+
if not headers:
|
228
|
+
headers = self.default_headers
|
229
|
+
|
230
|
+
res = requests.get(f"{self.url}/drive/{bucket}/{path}", headers = headers)
|
231
|
+
|
232
|
+
if res.status_code == 200:
|
233
|
+
return res.json()
|
234
|
+
elif res.status_code in (403, 401):
|
235
|
+
raise PermissionError(res.json()['message'])
|
236
|
+
else:
|
237
|
+
raise Exception(f"Error {res.status_code}: {res.json()['message']}")
|
@@ -0,0 +1,392 @@
|
|
1
|
+
import requests
|
2
|
+
import time
|
3
|
+
import typing
|
4
|
+
import urllib.parse
|
5
|
+
|
6
|
+
from .base import NSID
|
7
|
+
|
8
|
+
from .. import utils
|
9
|
+
|
10
|
+
default_headers = {}
|
11
|
+
|
12
|
+
class Permission:
|
13
|
+
def __init__(self, initial: str = "----"):
|
14
|
+
self.append: bool
|
15
|
+
self.manage: bool
|
16
|
+
self.edit: bool
|
17
|
+
self.read: bool
|
18
|
+
|
19
|
+
self.load(initial)
|
20
|
+
|
21
|
+
def load(self, val: str) -> None:
|
22
|
+
if 'a' in val: self.append = True
|
23
|
+
if 'm' in val: self.manage = True
|
24
|
+
if 'e' in val: self.edit = True
|
25
|
+
if 'r' in val: self.read = True
|
26
|
+
|
27
|
+
class PositionPermissions:
|
28
|
+
"""
|
29
|
+
Permissions d'une position à l'échelle du serveur. Certaines sont attribuées selon l'appartenance à divers groupes ayant une position précise
|
30
|
+
"""
|
31
|
+
|
32
|
+
def __init__(self) -> None:
|
33
|
+
self.bank_accounts = Permission("a---") # APPEND = ouvrir un ou plusieurs comptes, MANAGE = voir les infos globales concernant les comptes en banque, EDIT = gérer des comptes en banque, READ = voir les infos d'un compte en banque individuel
|
34
|
+
self.bots = Permission() # APPEND = publier un message sous l'identité d'un bot, MANAGE = proposer d'héberger un bot, EDIT = changer les paramètres d'un bot, READ = /
|
35
|
+
self.constitution = Permission() # APPEND = laws.append, MANAGE = laws.manage, EDIT = modifier la constitution, READ = /
|
36
|
+
self.database = Permission() # APPEND = créer des sous-bases de données, MANAGE = gérer la abse de données, EDIT = modifier les éléments, READ = avoir accès à toutes les données sans exception
|
37
|
+
self.items = Permission("---r") # APPEND = vendre, MANAGE = gérer des items dont on n'est pas propriétaire (hors marketplace), EDIT = gérer des items dont on n'est pas propriétaire (dans le marketplace), READ = accéder au marketplace
|
38
|
+
self.laws = Permission() # APPEND = proposer un texte de loi, MANAGE = accepter ou refuser une proposition, EDIT = modifier un texte, READ = /
|
39
|
+
self.members = Permission("---r") # APPEND = créer des entités, MANAGE = modérer des entités (hors Discord), EDIT = modifier des entités, READ = voir le profil des entités
|
40
|
+
self.national_channel = Permission() # APPEND = prendre la parole sur la chaîne nationale, MANAGE = voir qui peut prendre la parole, EDIT = modifier le planning de la chaîne nationale, READ = /
|
41
|
+
self.organizations = Permission("---r") # APPEND = créer une nouvelle organisation, MANAGE = exécuter des actions administratives sur les organisations, EDIT = modifier des organisations, READ = voir le profil de n'importe quelle organisation
|
42
|
+
self.reports = Permission() # APPEND = déposer plainte, MANAGE = accépter ou refuser une plainte, EDIT = /, READ = accéder à des infos supplémentaires pour une plainte
|
43
|
+
self.state_budgets = Permission() # APPEND = débloquer un nouveau budget, MANAGE = gérer les budjets, EDIT = gérer les sommes pour chaque budjet, READ = accéder aux infos concernant les budgets
|
44
|
+
self.votes = Permission() # APPEND = déclencher un vote, MANAGE = fermer un vote, EDIT = /, READ = lire les propriétés d'un vote avant sa fermeture
|
45
|
+
|
46
|
+
def merge(self, permissions: dict[str, str] | typing.Self):
|
47
|
+
if isinstance(permissions, PositionPermissions):
|
48
|
+
permissions = permissions.__dict__
|
49
|
+
|
50
|
+
for key, val in permissions.items():
|
51
|
+
perm: Permission = self.__getattribute__(key)
|
52
|
+
perm.load(val)
|
53
|
+
|
54
|
+
|
55
|
+
class Position:
|
56
|
+
"""
|
57
|
+
Position légale d'une entité
|
58
|
+
|
59
|
+
## Attributs
|
60
|
+
- name: `str`\n
|
61
|
+
Titre de la position
|
62
|
+
- id: `str`\n
|
63
|
+
Identifiant de la position
|
64
|
+
- permissions: `.PositionPermissions`\n
|
65
|
+
Permissions accordées à l'utilisateur
|
66
|
+
"""
|
67
|
+
|
68
|
+
def __init__(self, id: str = 'inconnu') -> None:
|
69
|
+
self.name: str = "Inconnue"
|
70
|
+
self.id = id
|
71
|
+
self.permissions: PositionPermissions = PositionPermissions()
|
72
|
+
|
73
|
+
def __repr__(self):
|
74
|
+
return self.id
|
75
|
+
|
76
|
+
class Entity:
|
77
|
+
"""
|
78
|
+
Classe de référence pour les entités
|
79
|
+
|
80
|
+
## Attributs
|
81
|
+
- id: `NSID`\n
|
82
|
+
Identifiant de l'entité
|
83
|
+
- name: `str`\n
|
84
|
+
Nom d'usage de l'entité
|
85
|
+
- registerDate: `int`\n
|
86
|
+
Date d'enregistrement de l'entité
|
87
|
+
- position: `.Position`\n
|
88
|
+
Position légale de l'entité
|
89
|
+
- additional: `dict`\n
|
90
|
+
Infos supplémentaires exploitables par les bots
|
91
|
+
"""
|
92
|
+
|
93
|
+
def __init__(self, id: NSID) -> None:
|
94
|
+
self._url = "" # URL de l'entité pour une requête GET
|
95
|
+
|
96
|
+
self.id: NSID = NSID(id) # ID hexadécimal de l'entité
|
97
|
+
self.name: str = "Entité Inconnue"
|
98
|
+
self.registerDate: int = 0
|
99
|
+
self.position: Position = Position()
|
100
|
+
self.additional: dict = {}
|
101
|
+
|
102
|
+
def set_name(self, new_name: str) -> None:
|
103
|
+
if len(new_name) > 32:
|
104
|
+
raise ValueError(f"Name length mustn't exceed 32 characters.")
|
105
|
+
|
106
|
+
res = requests.post(f"{self._url}/rename?name={new_name}", headers = default_headers)
|
107
|
+
|
108
|
+
if res.status_code == 200:
|
109
|
+
self.name = new_name
|
110
|
+
else:
|
111
|
+
print(res.status_code)
|
112
|
+
res.raise_for_status()
|
113
|
+
|
114
|
+
def set_position(self, position: Position) -> None:
|
115
|
+
res = requests.post(f"{self._url}/change_position?position={position.id}", headers = default_headers)
|
116
|
+
|
117
|
+
if res.status_code == 200:
|
118
|
+
self.position = position
|
119
|
+
else:
|
120
|
+
res.raise_for_status()
|
121
|
+
|
122
|
+
def add_link(self, key: str, value: str | int) -> None:
|
123
|
+
if isinstance(value, str):
|
124
|
+
_class = "string"
|
125
|
+
elif isinstance(value, int):
|
126
|
+
_class = "integer"
|
127
|
+
else:
|
128
|
+
raise TypeError("Only strings and integers can be recorded as an additional link")
|
129
|
+
|
130
|
+
params = {
|
131
|
+
"link": key,
|
132
|
+
"value": value,
|
133
|
+
"type": _class
|
134
|
+
}
|
135
|
+
|
136
|
+
query = "&".join(f"{k}={ urllib.parse.quote(v) }" for k, v in params.items())
|
137
|
+
|
138
|
+
res = requests.post(f"{self._url}/add_link?{query}", headers = default_headers)
|
139
|
+
|
140
|
+
if res.status_code == 200:
|
141
|
+
self.additional[key] = value
|
142
|
+
else:
|
143
|
+
print(res.text)
|
144
|
+
res.raise_for_status()
|
145
|
+
|
146
|
+
def unlink(self, key: str) -> None:
|
147
|
+
res = requests.post(f"{self._url}/remove_link?link={urllib.parse.quote(key)}", headers = default_headers)
|
148
|
+
|
149
|
+
if res.status_code == 200:
|
150
|
+
del self.additional[key]
|
151
|
+
else:
|
152
|
+
res.raise_for_status()
|
153
|
+
|
154
|
+
class User(Entity):
|
155
|
+
"""
|
156
|
+
Entité individuelle
|
157
|
+
|
158
|
+
## Attributs
|
159
|
+
- Tous les attributs de la classe `.Entity`
|
160
|
+
- xp: `int`\n
|
161
|
+
Points d'expérience de l'entité
|
162
|
+
- boosts: `dict[str, int]`\n
|
163
|
+
Ensemble des boosts dont bénéficie l'entité
|
164
|
+
- votes: `list[NSID]`\n
|
165
|
+
Liste des votes auxquels a participé l'entité
|
166
|
+
"""
|
167
|
+
|
168
|
+
def __init__(self, id: NSID) -> None:
|
169
|
+
super().__init__(NSID(id))
|
170
|
+
|
171
|
+
self.xp: int = 0
|
172
|
+
self.boosts: dict[str, int] = {}
|
173
|
+
self.groups: list[NSID] = []
|
174
|
+
|
175
|
+
def get_level(self) -> None:
|
176
|
+
i = 0
|
177
|
+
while self.xp > int(round(25 * (i * 2.5) ** 2, -2)):
|
178
|
+
i += 1
|
179
|
+
|
180
|
+
return i
|
181
|
+
|
182
|
+
def add_xp(self, amount: int) -> None:
|
183
|
+
boost = 0 if 0 in self.boosts.values() or amount <= 0 else max(list(self.boosts.values()) + [ 1 ])
|
184
|
+
res = requests.post(f"{self._url}/add_xp?amount={amount * boost}", headers = default_headers)
|
185
|
+
|
186
|
+
if res.status_code == 200:
|
187
|
+
self.xp += amount * boost
|
188
|
+
else:
|
189
|
+
res.raise_for_status()
|
190
|
+
|
191
|
+
def edit_boost(self, name: str, multiplier: int = -1) -> None:
|
192
|
+
res = requests.post(f"{self._url}/edit_boost?boost={name}&multiplier={multiplier}", headers = default_headers)
|
193
|
+
|
194
|
+
if res.status_code == 200:
|
195
|
+
if multiplier >= 0:
|
196
|
+
self.boosts[name] = multiplier
|
197
|
+
else:
|
198
|
+
del self.boosts[name]
|
199
|
+
else:
|
200
|
+
res.raise_for_status()
|
201
|
+
|
202
|
+
class MemberPermissions:
|
203
|
+
"""
|
204
|
+
Permissions d'un utilisateur à l'échelle d'un groupe
|
205
|
+
"""
|
206
|
+
|
207
|
+
def __init__(self) -> None:
|
208
|
+
self.manage_organization = False # Renommer l'organisation, changer le logo
|
209
|
+
self.manage_shares = False # Revaloriser les actions
|
210
|
+
self.manage_roles = False # Changer les rôles des membres
|
211
|
+
self.manage_members = False # Virer quelqu'un d'une entreprise, l'y inviter
|
212
|
+
|
213
|
+
def edit(self, **permissions: bool) -> None:
|
214
|
+
for perm in permissions.values():
|
215
|
+
self.__setattr__(*perm)
|
216
|
+
|
217
|
+
class GroupMember:
|
218
|
+
"""
|
219
|
+
Membre au sein d'une entité collective
|
220
|
+
|
221
|
+
## Attributs
|
222
|
+
- permission_level: `dict[str, int]`\n
|
223
|
+
Niveau d'accréditation du membre (0 = salarié, 4 = administrateur)
|
224
|
+
"""
|
225
|
+
|
226
|
+
def __init__(self, id: NSID) -> None:
|
227
|
+
self.id = id
|
228
|
+
self.permission_level: dict = { # Échelle de permissions selon le groupe de travail
|
229
|
+
"general": 0
|
230
|
+
}
|
231
|
+
|
232
|
+
def group_permissions(self, team: str = "general") -> MemberPermissions:
|
233
|
+
p = MemberPermissions()
|
234
|
+
team_perms = self.permission_level[team]
|
235
|
+
|
236
|
+
if team_perms >= 1: # Responsable
|
237
|
+
p.manage_members = True
|
238
|
+
|
239
|
+
if team_perms >= 2: # Superviseur
|
240
|
+
p.manage_roles = True
|
241
|
+
|
242
|
+
if team_perms >= 3: # Chef d'équipe
|
243
|
+
pass
|
244
|
+
|
245
|
+
if team_perms >= 4: # Directeur
|
246
|
+
p.manage_shares = True
|
247
|
+
p.manage_organization = True
|
248
|
+
|
249
|
+
return p
|
250
|
+
|
251
|
+
class GroupInvite:
|
252
|
+
def __init__(self, id: NSID):
|
253
|
+
self.id: NSID = id
|
254
|
+
self.team: str = "general"
|
255
|
+
self.level: str = 0
|
256
|
+
self._expires: int = round(time.time()) + 604800
|
257
|
+
|
258
|
+
class Share:
|
259
|
+
"""
|
260
|
+
Action d'une entreprise
|
261
|
+
|
262
|
+
## Attributs
|
263
|
+
- owner: `NSID`\n
|
264
|
+
Identifiant du titulaire de l'action
|
265
|
+
- price: `int`\n
|
266
|
+
Prix de l'action
|
267
|
+
"""
|
268
|
+
|
269
|
+
def __getstate__(self) -> dict:
|
270
|
+
return {
|
271
|
+
"owner": self.owner,
|
272
|
+
"price": self.price
|
273
|
+
}
|
274
|
+
|
275
|
+
def __setstate__(self, state: dict):
|
276
|
+
self.owner: NSID = state['owner']
|
277
|
+
self.price: int = state['price']
|
278
|
+
|
279
|
+
def __init__(self, owner: NSID = NSID(0x0), price: int = 10):
|
280
|
+
self.owner: NSID = owner
|
281
|
+
self.price: int = price
|
282
|
+
|
283
|
+
def assign_owner(self, owner: NSID):
|
284
|
+
self.owner = owner
|
285
|
+
|
286
|
+
def set_price(self, price: int):
|
287
|
+
self.price = price
|
288
|
+
|
289
|
+
class Organization(Entity):
|
290
|
+
"""
|
291
|
+
Entité collective
|
292
|
+
|
293
|
+
## Attributs
|
294
|
+
- Tous les attributs de la classe `.Entity`
|
295
|
+
- owner: `.Entity`\n
|
296
|
+
Utilisateur ou entreprise propriétaire de l'entité collective
|
297
|
+
- avatar: `bytes`\n
|
298
|
+
Avatar/logo de l'entité collective
|
299
|
+
- certifications: `dict[str, int]`\n
|
300
|
+
Liste des certifications et de leur date d'ajout
|
301
|
+
- members: `list[.GroupMember]`\n
|
302
|
+
Liste des membres de l'entreprise
|
303
|
+
- parts: `list[.Share]`\n
|
304
|
+
Liste des actions émises par l'entreprise
|
305
|
+
"""
|
306
|
+
|
307
|
+
def __init__(self, id: NSID) -> None:
|
308
|
+
super().__init__(NSID(id))
|
309
|
+
|
310
|
+
self.owner: Entity = User(NSID(0x0))
|
311
|
+
self.avatar: bytes = utils.open_asset('default_avatar.png')
|
312
|
+
|
313
|
+
self.certifications: dict = {}
|
314
|
+
self.members: list[GroupMember] = []
|
315
|
+
self.invites: dict[GroupInvite] = []
|
316
|
+
|
317
|
+
self.parts: list[Share] = 50 * [ Share(self.owner.id, 0) ]
|
318
|
+
|
319
|
+
def add_certification(self, certification: str, __expires: int = 2419200) -> None:
|
320
|
+
res = requests.post(f"{self._url}/add_certification?name={certification}&duration={__expires}", headers = default_headers)
|
321
|
+
|
322
|
+
if res.status_code == 200:
|
323
|
+
self.certifications[certification] = int(round(time.time()) + __expires)
|
324
|
+
else:
|
325
|
+
res.raise_for_status()
|
326
|
+
|
327
|
+
def has_certification(self, certification: str) -> bool:
|
328
|
+
return certification in self.certifications.keys()
|
329
|
+
|
330
|
+
def remove_certification(self, certification: str) -> None:
|
331
|
+
res = requests.post(f"{self._url}/remove_certification?name={certification}", headers = default_headers)
|
332
|
+
|
333
|
+
if res.status_code == 200:
|
334
|
+
del self.certifications[certification]
|
335
|
+
else:
|
336
|
+
res.raise_for_status()
|
337
|
+
|
338
|
+
def invite_member(self, member: NSID, level: int = 0, team: str = "general") -> None:
|
339
|
+
if not isinstance(member, NSID):
|
340
|
+
raise TypeError("L'entrée membre doit être de type NSID")
|
341
|
+
|
342
|
+
res = requests.post(f"{self._url}/invite_member?id={member}&level={level}&team={team}", headers = default_headers)
|
343
|
+
|
344
|
+
if res.status_code == 200:
|
345
|
+
invite = GroupInvite(member)
|
346
|
+
invite.team = team
|
347
|
+
invite.level = level
|
348
|
+
|
349
|
+
self.invites.append(invite)
|
350
|
+
else:
|
351
|
+
res.raise_for_status()
|
352
|
+
|
353
|
+
def remove_member(self, member: GroupMember) -> None:
|
354
|
+
for _member in self.members:
|
355
|
+
if _member.id == member.id:
|
356
|
+
self.members.remove(_member)
|
357
|
+
|
358
|
+
def remove(self, member: GroupMember) -> None:
|
359
|
+
self.remove_member(member)
|
360
|
+
|
361
|
+
def set_owner(self, member: User) -> None:
|
362
|
+
self.owner = member
|
363
|
+
|
364
|
+
def get_members_by_attr(self, attribute: str = "id") -> list[str]:
|
365
|
+
return [ member.__getattribute__(attribute) for member in self.members ]
|
366
|
+
|
367
|
+
def get_shares(self, include_worth: bool = False) -> dict[str, int] | dict[str, dict[str, int]]:
|
368
|
+
shares = {}
|
369
|
+
|
370
|
+
for share in self.parts:
|
371
|
+
if include_worth:
|
372
|
+
if share.owner in shares.keys():
|
373
|
+
shares[share.owner]['count'] += 1
|
374
|
+
shares[share.owner]['worth'] += share.price
|
375
|
+
else:
|
376
|
+
shares[share.owner] = {
|
377
|
+
'count': 1,
|
378
|
+
'worth': share.price
|
379
|
+
}
|
380
|
+
else:
|
381
|
+
if share.owner in shares.keys():
|
382
|
+
shares[share.owner] += 1
|
383
|
+
else:
|
384
|
+
shares[share.owner] = 1
|
385
|
+
|
386
|
+
return shares
|
387
|
+
|
388
|
+
def save_avatar(self, data: bytes = None):
|
389
|
+
if not data:
|
390
|
+
return
|
391
|
+
|
392
|
+
self.avatar = data
|