nsarchive 3.0.0a1__py3-none-any.whl → 3.0.0a2__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.
nsarchive/__init__.py CHANGED
@@ -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: 2.0.0
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 et des exceptions
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
nsarchive/cls/base.py CHANGED
@@ -1,7 +1,8 @@
1
+ import io
1
2
  import json
3
+ import requests
2
4
  import typing
3
-
4
- from supabase import Client
5
+ import warnings
5
6
 
6
7
  class NSID(str):
7
8
  """
@@ -43,134 +44,175 @@ class Instance:
43
44
  """
44
45
  Instance qui servira de base à toutes les instances.
45
46
  """
46
- def __init__(self, client: Client):
47
- self.db = client
48
47
 
49
- def _select_from_db(self, table: str, key: str = None, value: str = None) -> list:
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:
50
71
  """
51
- Récupère des données JSON d'une table Supabase en fonction de l'ID.
72
+ Récupère des données JSON depuis l'API
52
73
 
53
74
  ## Paramètres
54
- table: `str`:\n
55
- Nom de la base
56
- key: `str`\n
57
- Clé à vérifier
58
- value: `str`\n
59
- Valeur de la clé à vérifier
75
+ endpoint: `str`:
76
+ Endpoint de l'URL
77
+ headers: `dict` (optional)
78
+ Headers à envoyer
79
+ body: `dict` (optional)
80
+ Données à envoyer
60
81
 
61
82
  ## Renvoie
62
83
  - `list` de tous les élements correspondants
63
84
  - `None` si aucune donnée n'est trouvée
64
85
  """
65
86
 
66
- if key and value:
67
- res = self.db.from_(table).select("*").eq(key, value).execute()
68
- else:
69
- res = self.db.from_(table).select("*").execute()
87
+ if not headers:
88
+ headers = self.default_headers
70
89
 
71
- if res.data:
72
- return res.data
73
- else:
74
- return None
90
+ res = requests.get(f"{self.url}/{endpoint}", headers = headers, json = body, timeout = 5)
75
91
 
76
- def _get_by_ID(self, table: str, id: NSID) -> dict:
77
- _data = self._select_from_db(table, 'id', id)
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']}")
78
100
 
79
- if _data is not None:
80
- _data = _data[0]
101
+ def _get_by_ID(self, _class: str, id: NSID) -> dict:
102
+ _data = self._get_item(f"/model/{_class}/{id}")
81
103
 
82
104
  return _data
83
105
 
84
- def _put_in_db(self, table: str, data: dict) -> None:
106
+ def _put_in_db(self, endpoint: str, body: dict, headers: dict = None, use_PUT: bool = False) -> None:
85
107
  """
86
- Publie des données JSON dans une table Supabase en utilisant le client Supabase.
108
+ Publie des données JSON dans une table nation-db.
87
109
 
88
- :param table: Nom de la table dans laquelle les données doivent être insérées
89
- :param data: Dictionnaire contenant les données à publier
90
- :return: Résultat de l'insertion
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
91
117
  """
92
118
 
93
- res = self.db.from_(table).upsert(data).execute()
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)
94
126
 
95
- return res
127
+ if 200 <= res.status_code < 300:
128
+ return res.json()
129
+ else:
130
+ print(res.text)
131
+ res.raise_for_status()
96
132
 
97
- def _delete_from_db(self, table: str, key: str, value: str):
133
+ def _delete(self, _class: str, ids: list[NSID]) -> None:
98
134
  """
99
- Supprime un enregistrement d'une table Supabase en fonction d'une clé et de sa valeur.
135
+ Supprime des données JSON dans une table nation-db.
100
136
 
101
137
  ## Paramètres
102
- table: `str`
103
- Nom de la table dans laquelle les données doivent être supprimées
104
- key: `str`
105
- Clé à vérifier (par exemple "id" ou autre clé unique)
106
- value: `str`
107
- Valeur de la clé à vérifier pour trouver l'enregistrement à supprimer
108
-
109
- ## Renvoie
110
- - `True` si la suppression a réussi
111
- - `False` si aucune donnée n'a été trouvée ou si la suppression a échoué
138
+ _class: `str`
139
+ Classe des entités à supprimer
140
+ ids: `list[NSID]`
141
+ ID des entités à supprimer
112
142
  """
113
143
 
114
- res = self.db.from_(table).delete().eq(key, value).execute()
144
+ res = requests.post(f"{self.url}/delete_{_class}", json = { "ids": ids })
115
145
 
116
- return res
117
-
118
- def _delete_by_ID(self, table: str, id: NSID):
119
- res = self._delete_from_db(table, 'id', id)
120
-
121
- return res
122
-
123
- def fetch(self, table: str, **query: typing.Any) -> list:
124
- matches = []
125
-
126
- for key, value in query.items():
127
- entity = self._select_from_db(table, key, value)
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']}")
128
152
 
129
- if entity is not None:
130
- matches.append(entity)
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)
131
156
 
132
- if query == {}:
133
- matches = [ self._select_from_db(table) ]
157
+ def fetch(self, _class: str, **query: typing.Any) -> list:
158
+ res = requests.get(f"{self.url}/fetch/{_class}", params = query)
134
159
 
135
- if not matches or (len(matches) != len(query) and query != {}):
136
- return []
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()
137
166
 
138
- _res = [ item for item in matches[0] if all(item in match for match in matches[1:]) ]
167
+ return matches
139
168
 
140
- return _res
141
169
 
142
- def _upload_to_storage(self, bucket: str, data: bytes, path: str, overwrite: bool = False, options: dict = {'content-type': 'image/png'}) -> dict:
170
+ def _upload_file(self, bucket: str, name: str, data: bytes, overwrite: bool = False, headers: dict = None) -> dict:
143
171
  """
144
- Envoie un fichier dans un bucket Supabase.
172
+ Envoie un fichier dans un bucket nation-db.
145
173
 
146
174
  ## Paramètres
147
- bucket: `str`\n
175
+ bucket: `str`
148
176
  Nom du bucket où le fichier sera stocké
149
- data: `bytes`\n
177
+ name: `str`
178
+ Nom du fichier dans le drive
179
+ data: `bytes`
150
180
  Données à uploader
151
- path: `str`\n
152
- Chemin dans le bucket où le fichier sera stocké
181
+ overwrite: `bool` (optional)
182
+ Overwrite ou non
183
+ headers: `dict` (optional)
184
+ Headers à envoyer
153
185
 
154
186
  ## Renvoie
155
187
  - `dict` contenant les informations de l'upload si réussi
156
188
  - `None` en cas d'échec
157
189
  """
158
190
 
159
- options["upsert"] = json.dumps(overwrite)
191
+ if not headers:
192
+ headers = self.default_headers
193
+ headers['Content-Type'] = 'image/png'
160
194
 
161
- if len(data) > 5 * 1000 ** 3:
162
- raise ValueError("La limite d'un fichier à upload est de 1Mo")
195
+ body = {
196
+ "name": name,
197
+ "overwrite": json.dumps(overwrite)
198
+ }
163
199
 
164
- res = self.db.storage.from_(bucket).upload(path, data, options)
200
+ file = ("file", "image/png", data)
165
201
 
166
- if res.json().get("error"):
167
- print("Erreur lors de l'upload:", res["error"])
202
+ res = requests.put(f"{self.url}/upload_file/{bucket}", headers = headers, json = body, files = [ file ])
168
203
 
169
- return res
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']}")
170
212
 
171
- def _download_from_storage(self, bucket: str, path: str) -> bytes:
213
+ def _download_from_storage(self, bucket: str, path: str, headers: dict = None) -> bytes:
172
214
  """
173
- Télécharge un fichier depuis le stockage Supabase.
215
+ Télécharge un fichier depuis le stockage nation-db.
174
216
 
175
217
  ## Paramètres
176
218
  bucket: `str`\n
@@ -182,6 +224,14 @@ class Instance:
182
224
  - Le fichier demandé en `bytes`
183
225
  """
184
226
 
185
- res = self.db.storage.from_(bucket).download(path)
227
+ if not headers:
228
+ headers = self.default_headers
186
229
 
187
- return res
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']}")
nsarchive/cls/entities.py CHANGED
@@ -1,36 +1,56 @@
1
+ import requests
1
2
  import time
3
+ import typing
4
+ import urllib.parse
2
5
 
3
- from .exceptions import *
4
6
  from .base import NSID
5
7
 
6
8
  from .. import utils
7
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
+
8
27
  class PositionPermissions:
9
28
  """
10
29
  Permissions d'une position à l'échelle du serveur. Certaines sont attribuées selon l'appartenance à divers groupes ayant une position précise
11
30
  """
12
31
 
13
32
  def __init__(self) -> None:
14
- # Membres
15
- self.approve_laws = False # Approuver ou désapprouver les lois proposées (vaut aussi pour la Constitution)
16
- self.buy_items = False # Acheter des items depuis le marketplace
17
- self.create_organizations = False # Créer une organisation
18
- self.edit_constitution = False # Proposer une modification de la Constitution
19
- self.edit_laws = False # Proposer une modification des différents textes de loi
20
- self.manage_entities = False # Gérer les membres et les organisations
21
- self.manage_national_channel = False # Prendre la parole sur la chaîne nationale et avoir une priorité de passage sur les autres chaînes
22
- self.manage_reports = False # Accepter ou refuser une plainte
23
- self.manage_state_budgets = False # Gérer les différents budgets de l'État
24
- self.moderate_members = False # Envoyer des membres en garde à vue, en détention ou toute autre sanction non présente sur le client Discord
25
- self.propose_new_laws = self.edit_constitution # Proposer un nouveau texte de loi pris en charge par la Constitution
26
- self.publish_official_messages = False # Publier un message sous l'identité du bot Serveur
27
- self.sell_items = False # Vendre des objets ou services sur le marketplace
28
- self.vote_president = False # Participer aux élections présidentielles
29
- self.vote_representatives = False # Participer aux élections législatives
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)
30
53
 
31
- def edit(self, **permissions: bool) -> None:
32
- for perm in permissions.items():
33
- self.__setattr__(*perm)
34
54
 
35
55
  class Position:
36
56
  """
@@ -71,7 +91,9 @@ class Entity:
71
91
  """
72
92
 
73
93
  def __init__(self, id: NSID) -> None:
74
- self.id: NSID = NSID(id) # ID hexadécimal de l'entité (ou nom dans le cas de l'entreprise)
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é
75
97
  self.name: str = "Entité Inconnue"
76
98
  self.registerDate: int = 0
77
99
  self.position: Position = Position()
@@ -79,19 +101,55 @@ class Entity:
79
101
 
80
102
  def set_name(self, new_name: str) -> None:
81
103
  if len(new_name) > 32:
82
- raise NameTooLongError(f"Name length mustn't exceed 32 characters.")
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)
83
107
 
84
- self.name = new_name
108
+ if res.status_code == 200:
109
+ self.name = new_name
110
+ else:
111
+ print(res.status_code)
112
+ res.raise_for_status()
85
113
 
86
114
  def set_position(self, position: Position) -> None:
87
- self.position = position
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()
88
121
 
89
122
  def add_link(self, key: str, value: str | int) -> None:
90
- if isinstance(value, str) or isinstance(value, int):
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:
91
141
  self.additional[key] = value
142
+ else:
143
+ print(res.text)
144
+ res.raise_for_status()
92
145
 
93
146
  def unlink(self, key: str) -> None:
94
- del self.additional[key]
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()
95
153
 
96
154
  class User(Entity):
97
155
  """
@@ -102,9 +160,7 @@ class User(Entity):
102
160
  - xp: `int`\n
103
161
  Points d'expérience de l'entité
104
162
  - boosts: `dict[str, int]`\n
105
- Ensemble des boosts dont bénéficie l'entité
106
- - permissions: `.PositionPermissions`\n
107
- Fusion des permissions offertes par la position et les groupes
163
+ Ensemble des boosts dont bénéficie l'entité
108
164
  - votes: `list[NSID]`\n
109
165
  Liste des votes auxquels a participé l'entité
110
166
  """
@@ -114,11 +170,7 @@ class User(Entity):
114
170
 
115
171
  self.xp: int = 0
116
172
  self.boosts: dict[str, int] = {}
117
- self.permissions: PositionPermissions = PositionPermissions() # Elles seront définies en récupérant les permissions de sa position
118
- self.votes: list[str] = []
119
-
120
- def add_vote(self, id: NSID):
121
- self.votes.append(NSID(id))
173
+ self.groups: list[NSID] = []
122
174
 
123
175
  def get_level(self) -> None:
124
176
  i = 0
@@ -128,15 +180,24 @@ class User(Entity):
128
180
  return i
129
181
 
130
182
  def add_xp(self, amount: int) -> None:
131
- boost = 0 if 0 in self.boosts.values() else max(list(self.boosts.values()) + [ 1 ])
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)
132
185
 
133
- self.xp += amount * boost
186
+ if res.status_code == 200:
187
+ self.xp += amount * boost
188
+ else:
189
+ res.raise_for_status()
134
190
 
135
191
  def edit_boost(self, name: str, multiplier: int = -1) -> None:
136
- if multiplier >= 0:
137
- self.boosts[name] = multiplier
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]
138
199
  else:
139
- del self.boosts[name]
200
+ res.raise_for_status()
140
201
 
141
202
  class MemberPermissions:
142
203
  """
@@ -145,46 +206,55 @@ class MemberPermissions:
145
206
 
146
207
  def __init__(self) -> None:
147
208
  self.manage_organization = False # Renommer l'organisation, changer le logo
148
- self.manage_roles = False # Changer les rôles des membres
149
209
  self.manage_shares = False # Revaloriser les actions
210
+ self.manage_roles = False # Changer les rôles des membres
150
211
  self.manage_members = False # Virer quelqu'un d'une entreprise, l'y inviter
151
212
 
152
213
  def edit(self, **permissions: bool) -> None:
153
214
  for perm in permissions.values():
154
215
  self.__setattr__(*perm)
155
216
 
156
- class GroupMember(User):
217
+ class GroupMember:
157
218
  """
158
219
  Membre au sein d'une entité collective
159
220
 
160
221
  ## Attributs
161
- - Tous les attributs de la classe `.User`
162
- - permission_level: `int`\n
222
+ - permission_level: `dict[str, int]`\n
163
223
  Niveau d'accréditation du membre (0 = salarié, 4 = administrateur)
164
224
  """
165
225
 
166
226
  def __init__(self, id: NSID) -> None:
167
- super().__init__(id)
168
-
169
- self.permission_level: int = 0
227
+ self.id = id
228
+ self.permission_level: dict = { # Échelle de permissions selon le groupe de travail
229
+ "general": 0
230
+ }
170
231
 
171
- def group_permissions(self) -> MemberPermissions:
232
+ def group_permissions(self, team: str = "general") -> MemberPermissions:
172
233
  p = MemberPermissions()
234
+ team_perms = self.permission_level[team]
173
235
 
174
- if self.permission_level >= 1:
236
+ if team_perms >= 1: # Responsable
175
237
  p.manage_members = True
176
238
 
177
- if self.permission_level >= 2:
178
- p.manage_shares = True
179
-
180
- if self.permission_level >= 3:
239
+ if team_perms >= 2: # Superviseur
181
240
  p.manage_roles = True
182
241
 
183
- if self.permission_level >= 4:
242
+ if team_perms >= 3: # Chef d'équipe
243
+ pass
244
+
245
+ if team_perms >= 4: # Directeur
246
+ p.manage_shares = True
184
247
  p.manage_organization = True
185
248
 
186
249
  return p
187
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
+
188
258
  class Share:
189
259
  """
190
260
  Action d'une entreprise
@@ -242,32 +312,49 @@ class Organization(Entity):
242
312
 
243
313
  self.certifications: dict = {}
244
314
  self.members: list[GroupMember] = []
315
+ self.invites: dict[GroupInvite] = []
245
316
 
246
317
  self.parts: list[Share] = 50 * [ Share(self.owner.id, 0) ]
247
318
 
248
- def add_certification(self, certification: str) -> None:
249
- self.certifications[certification] = round(time.time())
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()
250
326
 
251
327
  def has_certification(self, certification: str) -> bool:
252
328
  return certification in self.certifications.keys()
253
329
 
254
330
  def remove_certification(self, certification: str) -> None:
255
- del self.certifications[certification]
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")
256
341
 
257
- def add_member(self, member: GroupMember) -> None:
258
- if not isinstance(member, GroupMember):
259
- raise TypeError("Le membre doit être de type GroupMember")
342
+ res = requests.post(f"{self._url}/invite_member?id={member}&level={level}&team={team}", headers = default_headers)
260
343
 
261
- self.members.append(member)
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()
262
352
 
263
353
  def remove_member(self, member: GroupMember) -> None:
264
354
  for _member in self.members:
265
355
  if _member.id == member.id:
266
356
  self.members.remove(_member)
267
357
 
268
- def append(self, member: GroupMember) -> None:
269
- self.add_member(member)
270
-
271
358
  def remove(self, member: GroupMember) -> None:
272
359
  self.remove_member(member)
273
360
 
@@ -296,4 +383,10 @@ class Organization(Entity):
296
383
  else:
297
384
  shares[share.owner] = 1
298
385
 
299
- return shares
386
+ return shares
387
+
388
+ def save_avatar(self, data: bytes = None):
389
+ if not data:
390
+ return
391
+
392
+ self.avatar = data