mc5-api-client 1.0.0__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.
- mc5_api_client/__init__.py +31 -0
- mc5_api_client/auth.py +281 -0
- mc5_api_client/cli.py +463 -0
- mc5_api_client/client.py +1220 -0
- mc5_api_client/exceptions.py +78 -0
- mc5_api_client/py.typed +0 -0
- mc5_api_client-1.0.0.dist-info/LICENSE +21 -0
- mc5_api_client-1.0.0.dist-info/METADATA +1150 -0
- mc5_api_client-1.0.0.dist-info/RECORD +12 -0
- mc5_api_client-1.0.0.dist-info/WHEEL +5 -0
- mc5_api_client-1.0.0.dist-info/entry_points.txt +2 -0
- mc5_api_client-1.0.0.dist-info/top_level.txt +1 -0
mc5_api_client/client.py
ADDED
|
@@ -0,0 +1,1220 @@
|
|
|
1
|
+
# ────────────[ CHIZOBA ]────────────────────────────
|
|
2
|
+
# | Email : chizoba2026@hotmail.com
|
|
3
|
+
# | File : client.py
|
|
4
|
+
# | License : MIT License © 2026 Chizoba
|
|
5
|
+
# | Brief : Main MC5 API client with comprehensive functionality
|
|
6
|
+
# ────────────────★─────────────────────────────────
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
Main MC5 API client providing comprehensive access to Modern Combat 5 API endpoints.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
import json
|
|
14
|
+
from typing import Optional, Dict, Any, List
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
import requests
|
|
18
|
+
from requests.adapters import HTTPAdapter
|
|
19
|
+
from urllib3.util.retry import Retry
|
|
20
|
+
|
|
21
|
+
from .auth import TokenGenerator
|
|
22
|
+
from .exceptions import (
|
|
23
|
+
MC5APIError,
|
|
24
|
+
AuthenticationError,
|
|
25
|
+
TokenExpiredError,
|
|
26
|
+
RateLimitError,
|
|
27
|
+
InsufficientScopeError,
|
|
28
|
+
NetworkError
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MC5Client:
|
|
33
|
+
"""
|
|
34
|
+
Comprehensive MC5 API client with support for all major endpoints.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# API endpoints
|
|
38
|
+
BASE_URLS = {
|
|
39
|
+
"auth": "https://eur-janus.gameloft.com:443",
|
|
40
|
+
"osiris": "https://eur-osiris.gameloft.com:443",
|
|
41
|
+
"olympus": "https://eur-olympus.gameloft.com:443",
|
|
42
|
+
"iris": "https://eur-iris.gameloft.com:443",
|
|
43
|
+
"hermes": "https://eur-hermes.gameloft.com",
|
|
44
|
+
"pandora": "https://vgold-eur.gameloft.com",
|
|
45
|
+
"game_portal": "https://app-468561b3-9ecd-4d21-8241-30ed288f4d8b.gold0009.gameloft.com"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
username: Optional[str] = None,
|
|
51
|
+
password: Optional[str] = None,
|
|
52
|
+
client_id: str = "1875:55979:6.0.0a:windows:windows",
|
|
53
|
+
auto_refresh: bool = True,
|
|
54
|
+
timeout: int = 30,
|
|
55
|
+
max_retries: int = 3
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Initialize the MC5 API client.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
username: MC5 username
|
|
62
|
+
password: MC5 password
|
|
63
|
+
client_id: Game client identifier
|
|
64
|
+
auto_refresh: Automatically refresh expired tokens
|
|
65
|
+
timeout: Request timeout in seconds
|
|
66
|
+
max_retries: Maximum number of retry attempts
|
|
67
|
+
"""
|
|
68
|
+
self.client_id = client_id
|
|
69
|
+
self.auto_refresh = auto_refresh
|
|
70
|
+
self.timeout = timeout
|
|
71
|
+
self.max_retries = max_retries
|
|
72
|
+
|
|
73
|
+
# Initialize token generator
|
|
74
|
+
self.token_generator = TokenGenerator(
|
|
75
|
+
client_id=client_id,
|
|
76
|
+
timeout=timeout,
|
|
77
|
+
max_retries=max_retries
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Setup HTTP session
|
|
81
|
+
self.session = requests.Session()
|
|
82
|
+
retry_strategy = Retry(
|
|
83
|
+
total=max_retries,
|
|
84
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
85
|
+
allowed_methods=["HEAD", "GET", "POST", "PUT", "DELETE"],
|
|
86
|
+
backoff_factor=1
|
|
87
|
+
)
|
|
88
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
89
|
+
self.session.mount("http://", adapter)
|
|
90
|
+
self.session.mount("https://", adapter)
|
|
91
|
+
|
|
92
|
+
# Default headers
|
|
93
|
+
self.session.headers.update({
|
|
94
|
+
"User-Agent": "MC5-API-Client/1.0.0",
|
|
95
|
+
"Accept": "*/*",
|
|
96
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
97
|
+
"Connection": "close"
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
# Token storage
|
|
101
|
+
self._token_data = None
|
|
102
|
+
self._username = username
|
|
103
|
+
self._password = password
|
|
104
|
+
|
|
105
|
+
# Auto-authenticate if credentials provided
|
|
106
|
+
if username and password:
|
|
107
|
+
self.authenticate(username, password)
|
|
108
|
+
|
|
109
|
+
def authenticate(self, username: str, password: str, device_id: Optional[str] = None) -> Dict[str, Any]:
|
|
110
|
+
"""
|
|
111
|
+
Authenticate and obtain access token.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
username: MC5 username
|
|
115
|
+
password: MC5 password
|
|
116
|
+
device_id: Device ID (generated if not provided)
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Token data dictionary
|
|
120
|
+
"""
|
|
121
|
+
self._username = username
|
|
122
|
+
self._password = password
|
|
123
|
+
|
|
124
|
+
self._token_data = self.token_generator.generate_token(
|
|
125
|
+
username=username,
|
|
126
|
+
password=password,
|
|
127
|
+
device_id=device_id
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return self._token_data
|
|
131
|
+
|
|
132
|
+
def authenticate_admin(self, device_id: Optional[str] = None) -> Dict[str, Any]:
|
|
133
|
+
"""
|
|
134
|
+
Authenticate as admin.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
device_id: Device ID (generated if not provided)
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Admin token data dictionary
|
|
141
|
+
"""
|
|
142
|
+
self._token_data = self.token_generator.generate_admin_token(device_id=device_id)
|
|
143
|
+
self._username = "game:mc5_system"
|
|
144
|
+
self._password = "admin"
|
|
145
|
+
|
|
146
|
+
return self._token_data
|
|
147
|
+
|
|
148
|
+
def _ensure_valid_token(self):
|
|
149
|
+
"""Ensure we have a valid access token."""
|
|
150
|
+
if not self._token_data:
|
|
151
|
+
raise AuthenticationError("No authentication token available. Call authenticate() first.")
|
|
152
|
+
|
|
153
|
+
if not self.token_generator.validate_token(self._token_data):
|
|
154
|
+
if self.auto_refresh and self._username and self._password:
|
|
155
|
+
print("🟠 Token expired, refreshing...")
|
|
156
|
+
self._token_data = self.token_generator.refresh_token_if_needed(
|
|
157
|
+
self._token_data,
|
|
158
|
+
self._username,
|
|
159
|
+
self._password
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
raise TokenExpiredError("Access token has expired")
|
|
163
|
+
|
|
164
|
+
def _make_request(
|
|
165
|
+
self,
|
|
166
|
+
method: str,
|
|
167
|
+
url: str,
|
|
168
|
+
params: Optional[Dict[str, Any]] = None,
|
|
169
|
+
data: Optional[Dict[str, Any]] = None,
|
|
170
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
171
|
+
headers: Optional[Dict[str, str]] = None,
|
|
172
|
+
require_token: bool = True
|
|
173
|
+
) -> Dict[str, Any]:
|
|
174
|
+
"""
|
|
175
|
+
Make an HTTP request with proper error handling.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
method: HTTP method (GET, POST, PUT, DELETE)
|
|
179
|
+
url: Request URL
|
|
180
|
+
params: Query parameters
|
|
181
|
+
data: Form data
|
|
182
|
+
json_data: JSON data
|
|
183
|
+
headers: Additional headers
|
|
184
|
+
require_token: Whether authentication token is required
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Response JSON data
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
MC5APIError: For API-related errors
|
|
191
|
+
NetworkError: For network-related errors
|
|
192
|
+
"""
|
|
193
|
+
if require_token:
|
|
194
|
+
self._ensure_valid_token()
|
|
195
|
+
# Add access token to params
|
|
196
|
+
if params is None:
|
|
197
|
+
params = {}
|
|
198
|
+
params["access_token"] = self._token_data["access_token"]
|
|
199
|
+
|
|
200
|
+
# Prepare request
|
|
201
|
+
request_headers = self.session.headers.copy()
|
|
202
|
+
if headers:
|
|
203
|
+
request_headers.update(headers)
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
response = self.session.request(
|
|
207
|
+
method=method,
|
|
208
|
+
url=url,
|
|
209
|
+
params=params,
|
|
210
|
+
data=data,
|
|
211
|
+
json=json_data,
|
|
212
|
+
headers=request_headers,
|
|
213
|
+
timeout=self.timeout
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Handle different response types
|
|
217
|
+
if response.status_code == 200:
|
|
218
|
+
try:
|
|
219
|
+
return response.json()
|
|
220
|
+
except json.JSONDecodeError:
|
|
221
|
+
return {"response": response.text}
|
|
222
|
+
|
|
223
|
+
elif response.status_code == 401:
|
|
224
|
+
raise AuthenticationError("Unauthorized - invalid or expired token")
|
|
225
|
+
|
|
226
|
+
elif response.status_code == 403:
|
|
227
|
+
try:
|
|
228
|
+
error_data = response.json()
|
|
229
|
+
raise InsufficientScopeError(error_data.get("error", "Insufficient permissions"))
|
|
230
|
+
except:
|
|
231
|
+
raise InsufficientScopeError("Insufficient permissions")
|
|
232
|
+
|
|
233
|
+
elif response.status_code == 429:
|
|
234
|
+
retry_after = response.headers.get("Retry-After")
|
|
235
|
+
raise RateLimitError(
|
|
236
|
+
"Rate limit exceeded",
|
|
237
|
+
retry_after=int(retry_after) if retry_after else None
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
else:
|
|
241
|
+
try:
|
|
242
|
+
error_data = response.json()
|
|
243
|
+
error_msg = error_data.get("error", error_data.get("message", "Unknown error"))
|
|
244
|
+
except:
|
|
245
|
+
error_msg = response.text or "Unknown error"
|
|
246
|
+
|
|
247
|
+
raise MC5APIError(
|
|
248
|
+
f"Request failed with status {response.status_code}: {error_msg}",
|
|
249
|
+
status_code=response.status_code,
|
|
250
|
+
response_data=error_data if 'error_data' in locals() else {}
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
except requests.exceptions.Timeout:
|
|
254
|
+
raise NetworkError("Request timed out")
|
|
255
|
+
except requests.exceptions.ConnectionError:
|
|
256
|
+
raise NetworkError("Connection failed")
|
|
257
|
+
except requests.exceptions.RequestException as e:
|
|
258
|
+
raise NetworkError(f"Network error: {str(e)}")
|
|
259
|
+
|
|
260
|
+
# Profile Management
|
|
261
|
+
|
|
262
|
+
def get_profile(self, credential: Optional[str] = None) -> Dict[str, Any]:
|
|
263
|
+
"""
|
|
264
|
+
Get player profile information.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
credential: Player credential (uses current user if not provided)
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Profile data
|
|
271
|
+
"""
|
|
272
|
+
if credential:
|
|
273
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me"
|
|
274
|
+
params = {"credential": credential}
|
|
275
|
+
else:
|
|
276
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me"
|
|
277
|
+
params = {}
|
|
278
|
+
|
|
279
|
+
return self._make_request("GET", url, params=params)
|
|
280
|
+
|
|
281
|
+
def update_profile(self, profile_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
282
|
+
"""
|
|
283
|
+
Update player profile.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
profile_data: Profile data to update
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Updated profile data
|
|
290
|
+
"""
|
|
291
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me"
|
|
292
|
+
return self._make_request("PUT", url, data=profile_data)
|
|
293
|
+
|
|
294
|
+
# Clan Management
|
|
295
|
+
|
|
296
|
+
def get_clan_settings(self, clan_id: str) -> Dict[str, Any]:
|
|
297
|
+
"""
|
|
298
|
+
Get clan settings and information.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
clan_id: Clan ID
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Clan settings data including name, description, members, etc.
|
|
305
|
+
"""
|
|
306
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}"
|
|
307
|
+
return self._make_request("GET", url)
|
|
308
|
+
|
|
309
|
+
def update_clan_settings(self, clan_id: str, settings: Dict[str, Any]) -> Dict[str, Any]:
|
|
310
|
+
"""
|
|
311
|
+
Update clan settings.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
clan_id: Clan ID
|
|
315
|
+
settings: Clan settings to update (name, description, membership_type, etc.)
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Updated clan settings
|
|
319
|
+
"""
|
|
320
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}"
|
|
321
|
+
return self._make_request("PUT", url, data=settings)
|
|
322
|
+
|
|
323
|
+
def get_clan_members(self, clan_id: str) -> List[Dict[str, Any]]:
|
|
324
|
+
"""
|
|
325
|
+
Get all members of a clan.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
clan_id: Clan ID
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
List of clan members with their roles and stats
|
|
332
|
+
"""
|
|
333
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/members"
|
|
334
|
+
response = self._make_request("GET", url)
|
|
335
|
+
return response.get("members", [])
|
|
336
|
+
|
|
337
|
+
def invite_clan_member(self, clan_id: str, credential: str, role: str = "member") -> Dict[str, Any]:
|
|
338
|
+
"""
|
|
339
|
+
Invite a player to join the clan.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
clan_id: Clan ID
|
|
343
|
+
credential: Player credential to invite
|
|
344
|
+
role: Role to assign (member, officer, etc.)
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Invitation result
|
|
348
|
+
"""
|
|
349
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/invitations"
|
|
350
|
+
data = {
|
|
351
|
+
"credential": credential,
|
|
352
|
+
"role": role
|
|
353
|
+
}
|
|
354
|
+
return self._make_request("POST", url, data=data)
|
|
355
|
+
|
|
356
|
+
def accept_clan_invitation(self, clan_id: str) -> Dict[str, Any]:
|
|
357
|
+
"""
|
|
358
|
+
Accept a clan invitation.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
clan_id: Clan ID
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
Acceptance result
|
|
365
|
+
"""
|
|
366
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/invitations/me"
|
|
367
|
+
return self._make_request("POST", url)
|
|
368
|
+
|
|
369
|
+
def decline_clan_invitation(self, clan_id: str) -> Dict[str, Any]:
|
|
370
|
+
"""
|
|
371
|
+
Decline a clan invitation.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
clan_id: Clan ID
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Decline result
|
|
378
|
+
"""
|
|
379
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/invitations/me"
|
|
380
|
+
return self._make_request("DELETE", url)
|
|
381
|
+
|
|
382
|
+
def kick_clan_member(self, clan_id: str, member_credential: str) -> Dict[str, Any]:
|
|
383
|
+
"""
|
|
384
|
+
Kick a member from the clan.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
clan_id: Clan ID
|
|
388
|
+
member_credential: Credential of member to kick
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Operation result
|
|
392
|
+
"""
|
|
393
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/members/{member_credential}"
|
|
394
|
+
return self._make_request("DELETE", url)
|
|
395
|
+
|
|
396
|
+
def promote_clan_member(self, clan_id: str, member_credential: str, new_role: str) -> Dict[str, Any]:
|
|
397
|
+
"""
|
|
398
|
+
Promote a clan member to a new role.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
clan_id: Clan ID
|
|
402
|
+
member_credential: Credential of member to promote
|
|
403
|
+
new_role: New role (officer, leader, etc.)
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Promotion result
|
|
407
|
+
"""
|
|
408
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/members/{member_credential}/role"
|
|
409
|
+
data = {"role": new_role}
|
|
410
|
+
return self._make_request("PUT", url, data=data)
|
|
411
|
+
|
|
412
|
+
def demote_clan_member(self, clan_id: str, member_credential: str, new_role: str) -> Dict[str, Any]:
|
|
413
|
+
"""
|
|
414
|
+
Demote a clan member to a new role.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
clan_id: Clan ID
|
|
418
|
+
member_credential: Credential of member to demote
|
|
419
|
+
new_role: New role (member, etc.)
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
Demotion result
|
|
423
|
+
"""
|
|
424
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/members/{member_credential}/role"
|
|
425
|
+
data = {"role": new_role}
|
|
426
|
+
return self._make_request("PUT", url, data=data)
|
|
427
|
+
|
|
428
|
+
def leave_clan(self, clan_id: str) -> Dict[str, Any]:
|
|
429
|
+
"""
|
|
430
|
+
Leave the current clan.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
clan_id: Clan ID
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
Leave result
|
|
437
|
+
"""
|
|
438
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/members/me"
|
|
439
|
+
return self._make_request("DELETE", url)
|
|
440
|
+
|
|
441
|
+
def get_clan_applications(self, clan_id: str) -> List[Dict[str, Any]]:
|
|
442
|
+
"""
|
|
443
|
+
Get pending clan applications.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
clan_id: Clan ID
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
List of pending applications
|
|
450
|
+
"""
|
|
451
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/applications"
|
|
452
|
+
response = self._make_request("GET", url)
|
|
453
|
+
return response.get("applications", [])
|
|
454
|
+
|
|
455
|
+
def accept_clan_application(self, clan_id: str, applicant_credential: str, role: str = "member") -> Dict[str, Any]:
|
|
456
|
+
"""
|
|
457
|
+
Accept a clan application.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
clan_id: Clan ID
|
|
461
|
+
applicant_credential: Credential of applicant
|
|
462
|
+
role: Role to assign (member, officer, etc.)
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Acceptance result
|
|
466
|
+
"""
|
|
467
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/applications/{applicant_credential}"
|
|
468
|
+
data = {"role": role}
|
|
469
|
+
return self._make_request("POST", url, data=data)
|
|
470
|
+
|
|
471
|
+
def reject_clan_application(self, clan_id: str, applicant_credential: str) -> Dict[str, Any]:
|
|
472
|
+
"""
|
|
473
|
+
Reject a clan application.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
clan_id: Clan ID
|
|
477
|
+
applicant_credential: Credential of applicant
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
Rejection result
|
|
481
|
+
"""
|
|
482
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/applications/{applicant_credential}"
|
|
483
|
+
return self._make_request("DELETE", url)
|
|
484
|
+
|
|
485
|
+
def apply_to_clan(self, clan_id: str, message: str = "") -> Dict[str, Any]:
|
|
486
|
+
"""
|
|
487
|
+
Apply to join a clan.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
clan_id: Clan ID
|
|
491
|
+
message: Application message
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
Application result
|
|
495
|
+
"""
|
|
496
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/applications"
|
|
497
|
+
data = {"message": message}
|
|
498
|
+
return self._make_request("POST", url, data=data)
|
|
499
|
+
|
|
500
|
+
def get_my_clan_applications(self) -> List[Dict[str, Any]]:
|
|
501
|
+
"""
|
|
502
|
+
Get your own clan applications.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
List of your applications
|
|
506
|
+
"""
|
|
507
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me/clan-applications"
|
|
508
|
+
response = self._make_request("GET", url)
|
|
509
|
+
return response.get("applications", [])
|
|
510
|
+
|
|
511
|
+
def cancel_clan_application(self, clan_id: str) -> Dict[str, Any]:
|
|
512
|
+
"""
|
|
513
|
+
Cancel a clan application.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
clan_id: Clan ID
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
Cancellation result
|
|
520
|
+
"""
|
|
521
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me/clan-applications/{clan_id}"
|
|
522
|
+
return self._make_request("DELETE", url)
|
|
523
|
+
|
|
524
|
+
def get_clan_statistics(self, clan_id: str) -> Dict[str, Any]:
|
|
525
|
+
"""
|
|
526
|
+
Get clan statistics and performance data.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
clan_id: Clan ID
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
Clan statistics
|
|
533
|
+
"""
|
|
534
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/statistics"
|
|
535
|
+
return self._make_request("GET", url)
|
|
536
|
+
|
|
537
|
+
def get_clan_leaderboard(self, clan_id: str) -> List[Dict[str, Any]]:
|
|
538
|
+
"""
|
|
539
|
+
Get clan internal leaderboard.
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
clan_id: Clan ID
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
Clan leaderboard data
|
|
546
|
+
"""
|
|
547
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/leaderboard"
|
|
548
|
+
response = self._make_request("GET", url)
|
|
549
|
+
return response.get("leaderboard", [])
|
|
550
|
+
|
|
551
|
+
def search_clans(self, query: str, limit: int = 20) -> List[Dict[str, Any]]:
|
|
552
|
+
"""
|
|
553
|
+
Search for clans.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
query: Search query
|
|
557
|
+
limit: Maximum number of results
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
List of matching clans
|
|
561
|
+
"""
|
|
562
|
+
url = f"{self.BASE_URLS['osiris']}/clans/search"
|
|
563
|
+
params = {
|
|
564
|
+
"q": query,
|
|
565
|
+
"limit": limit
|
|
566
|
+
}
|
|
567
|
+
response = self._make_request("GET", url, params=params)
|
|
568
|
+
return response.get("clans", [])
|
|
569
|
+
|
|
570
|
+
def create_clan(self, name: str, tag: str, description: str = "", membership_type: str = "open") -> Dict[str, Any]:
|
|
571
|
+
"""
|
|
572
|
+
Create a new clan.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
name: Clan name
|
|
576
|
+
tag: Clan tag (short identifier)
|
|
577
|
+
description: Clan description
|
|
578
|
+
membership_type: Membership type (open, closed, invite_only)
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
Created clan data
|
|
582
|
+
"""
|
|
583
|
+
url = f"{self.BASE_URLS['osiris']}/clans"
|
|
584
|
+
data = {
|
|
585
|
+
"name": name,
|
|
586
|
+
"tag": tag,
|
|
587
|
+
"description": description,
|
|
588
|
+
"membership_type": membership_type
|
|
589
|
+
}
|
|
590
|
+
return self._make_request("POST", url, data=data)
|
|
591
|
+
|
|
592
|
+
def disband_clan(self, clan_id: str) -> Dict[str, Any]:
|
|
593
|
+
"""
|
|
594
|
+
Disband a clan (leader only).
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
clan_id: Clan ID
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
Disband result
|
|
601
|
+
"""
|
|
602
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}"
|
|
603
|
+
return self._make_request("DELETE", url)
|
|
604
|
+
|
|
605
|
+
def transfer_clan_ownership(self, clan_id: str, new_leader_credential: str) -> Dict[str, Any]:
|
|
606
|
+
"""
|
|
607
|
+
Transfer clan ownership to another member.
|
|
608
|
+
|
|
609
|
+
Args:
|
|
610
|
+
clan_id: Clan ID
|
|
611
|
+
new_leader_credential: Credential of new leader
|
|
612
|
+
|
|
613
|
+
Returns:
|
|
614
|
+
Transfer result
|
|
615
|
+
"""
|
|
616
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/ownership"
|
|
617
|
+
data = {"new_leader": new_leader_credential}
|
|
618
|
+
return self._make_request("POST", url, data=data)
|
|
619
|
+
|
|
620
|
+
# Group/Squad Management
|
|
621
|
+
|
|
622
|
+
def get_group_members(self, group_id: str, offset: int = 0) -> List[Dict[str, Any]]:
|
|
623
|
+
"""
|
|
624
|
+
Get all members of a group/squad.
|
|
625
|
+
|
|
626
|
+
Args:
|
|
627
|
+
group_id: Group/Squad ID
|
|
628
|
+
offset: Pagination offset (default: 0)
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
List of group members with their stats and status
|
|
632
|
+
"""
|
|
633
|
+
url = f"{self.BASE_URLS['osiris']}/groups/{group_id}/members"
|
|
634
|
+
params = {
|
|
635
|
+
"group_id": group_id,
|
|
636
|
+
"offset": offset
|
|
637
|
+
}
|
|
638
|
+
response = self._make_request("GET", url, params=params)
|
|
639
|
+
return response if isinstance(response, list) else []
|
|
640
|
+
|
|
641
|
+
def update_group_member_stats(self, group_id: str, credential: str, **stats) -> Dict[str, Any]:
|
|
642
|
+
"""
|
|
643
|
+
Update a group member's stats (score, XP, kill signature, etc.).
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
group_id: Group/Squad ID
|
|
647
|
+
credential: Member credential to update
|
|
648
|
+
**stats: Stats to update (_score, _xp, _killsig_id, _killsig_color, etc.)
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
Update result
|
|
652
|
+
"""
|
|
653
|
+
url = f"{self.BASE_URLS['osiris']}/groups/{group_id}/members/{credential}"
|
|
654
|
+
|
|
655
|
+
# Prepare data for form-encoded request
|
|
656
|
+
data = {
|
|
657
|
+
"operation": "update",
|
|
658
|
+
"credential": credential
|
|
659
|
+
}
|
|
660
|
+
data.update(stats)
|
|
661
|
+
|
|
662
|
+
return self._make_request("POST", url, data=data)
|
|
663
|
+
|
|
664
|
+
def update_member_score(self, group_id: str, credential: str, score: int) -> Dict[str, Any]:
|
|
665
|
+
"""
|
|
666
|
+
Update a member's score in the group.
|
|
667
|
+
|
|
668
|
+
Args:
|
|
669
|
+
group_id: Group/Squad ID
|
|
670
|
+
credential: Member credential
|
|
671
|
+
score: New score value
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
Update result
|
|
675
|
+
"""
|
|
676
|
+
return self.update_group_member_stats(group_id, credential, _score=str(score))
|
|
677
|
+
|
|
678
|
+
def update_member_xp(self, group_id: str, credential: str, xp: int) -> Dict[str, Any]:
|
|
679
|
+
"""
|
|
680
|
+
Update a member's XP in the group.
|
|
681
|
+
|
|
682
|
+
Args:
|
|
683
|
+
group_id: Group/Squad ID
|
|
684
|
+
credential: Member credential
|
|
685
|
+
xp: New XP value
|
|
686
|
+
|
|
687
|
+
Returns:
|
|
688
|
+
Update result
|
|
689
|
+
"""
|
|
690
|
+
return self.update_group_member_stats(group_id, credential, _xp=str(xp))
|
|
691
|
+
|
|
692
|
+
def update_member_killsig(self, group_id: str, credential: str, killsig_id: str, killsig_color: str) -> Dict[str, Any]:
|
|
693
|
+
"""
|
|
694
|
+
Update a member's kill signature.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
group_id: Group/Squad ID
|
|
698
|
+
credential: Member credential
|
|
699
|
+
killsig_id: Kill signature ID
|
|
700
|
+
killsig_color: Kill signature color
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
Update result
|
|
704
|
+
"""
|
|
705
|
+
return self.update_group_member_stats(
|
|
706
|
+
group_id,
|
|
707
|
+
credential,
|
|
708
|
+
_killsig_id=killsig_id,
|
|
709
|
+
_killsig_color=killsig_color
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
def get_member_by_credential(self, group_id: str, credential: str) -> Optional[Dict[str, Any]]:
|
|
713
|
+
"""
|
|
714
|
+
Get a specific member's information by credential.
|
|
715
|
+
|
|
716
|
+
Args:
|
|
717
|
+
group_id: Group/Squad ID
|
|
718
|
+
credential: Member credential
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
Member information or None if not found
|
|
722
|
+
"""
|
|
723
|
+
members = self.get_group_members(group_id)
|
|
724
|
+
for member in members:
|
|
725
|
+
if member.get("credential") == credential:
|
|
726
|
+
return member
|
|
727
|
+
return None
|
|
728
|
+
|
|
729
|
+
def get_online_members(self, group_id: str) -> List[Dict[str, Any]]:
|
|
730
|
+
"""
|
|
731
|
+
Get all online members in a group.
|
|
732
|
+
|
|
733
|
+
Args:
|
|
734
|
+
group_id: Group/Squad ID
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
List of online members
|
|
738
|
+
"""
|
|
739
|
+
members = self.get_group_members(group_id)
|
|
740
|
+
return [member for member in members if member.get("online", False)]
|
|
741
|
+
|
|
742
|
+
def get_group_statistics(self, group_id: str) -> Dict[str, Any]:
|
|
743
|
+
"""
|
|
744
|
+
Calculate group statistics from member data.
|
|
745
|
+
|
|
746
|
+
Args:
|
|
747
|
+
group_id: Group/Squad ID
|
|
748
|
+
|
|
749
|
+
Returns:
|
|
750
|
+
Group statistics (total members, online count, average score, etc.)
|
|
751
|
+
"""
|
|
752
|
+
members = self.get_group_members(group_id)
|
|
753
|
+
|
|
754
|
+
if not members:
|
|
755
|
+
return {
|
|
756
|
+
"total_members": 0,
|
|
757
|
+
"online_members": 0,
|
|
758
|
+
"offline_members": 0,
|
|
759
|
+
"average_score": 0,
|
|
760
|
+
"total_score": 0,
|
|
761
|
+
"average_xp": 0,
|
|
762
|
+
"total_xp": 0
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
online_count = sum(1 for member in members if member.get("online", False))
|
|
766
|
+
scores = [int(member.get("_score", 0)) for member in members if member.get("_score")]
|
|
767
|
+
xp_values = [int(member.get("_xp", 0)) for member in members if member.get("_xp")]
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
"total_members": len(members),
|
|
771
|
+
"online_members": online_count,
|
|
772
|
+
"offline_members": len(members) - online_count,
|
|
773
|
+
"average_score": sum(scores) / len(scores) if scores else 0,
|
|
774
|
+
"total_score": sum(scores),
|
|
775
|
+
"average_xp": sum(xp_values) / len(xp_values) if xp_values else 0,
|
|
776
|
+
"total_xp": sum(xp_values)
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
def send_squad_wall_message(self, clan_id: str, message: str, msg_type: int = 0,
|
|
780
|
+
player_killsig: str = None, player_killsig_color: str = None,
|
|
781
|
+
language: str = "en", activity_type: str = "user_post") -> Dict[str, Any]:
|
|
782
|
+
"""
|
|
783
|
+
Send a message to the squad/group wall.
|
|
784
|
+
|
|
785
|
+
Args:
|
|
786
|
+
clan_id: Group/Squad ID
|
|
787
|
+
message: Message content
|
|
788
|
+
msg_type: Message type (0 for regular message)
|
|
789
|
+
player_killsig: Player kill signature ID (optional)
|
|
790
|
+
player_killsig_color: Player kill signature color (optional)
|
|
791
|
+
language: Message language (default: "en")
|
|
792
|
+
activity_type: Activity type (default: "user_post")
|
|
793
|
+
|
|
794
|
+
Returns:
|
|
795
|
+
Message post result with message ID
|
|
796
|
+
"""
|
|
797
|
+
url = f"{self.BASE_URLS['osiris']}/groups/{clan_id}/wall"
|
|
798
|
+
|
|
799
|
+
# Create message data
|
|
800
|
+
message_data = {
|
|
801
|
+
"msg_body": message,
|
|
802
|
+
"msg_type": msg_type
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
# Add kill signature if provided
|
|
806
|
+
if player_killsig:
|
|
807
|
+
message_data["playerKillSign"] = player_killsig
|
|
808
|
+
if player_killsig_color is not None:
|
|
809
|
+
message_data["playerKillSignColor"] = player_killsig_color
|
|
810
|
+
|
|
811
|
+
# Convert to JSON string as required by API
|
|
812
|
+
import json
|
|
813
|
+
text_json = json.dumps(message_data)
|
|
814
|
+
|
|
815
|
+
# Prepare form data
|
|
816
|
+
data = {
|
|
817
|
+
"text": text_json + "\n",
|
|
818
|
+
"language": language,
|
|
819
|
+
"activity_type": activity_type,
|
|
820
|
+
"alert_kairos": "false"
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
return self._make_request("POST", url, data=data)
|
|
824
|
+
|
|
825
|
+
def get_squad_wall_messages(self, clan_id: str, limit: int = 20) -> List[Dict[str, Any]]:
|
|
826
|
+
"""
|
|
827
|
+
Get messages from the squad wall.
|
|
828
|
+
|
|
829
|
+
Args:
|
|
830
|
+
clan_id: Group/Squad ID
|
|
831
|
+
limit: Maximum number of messages to retrieve
|
|
832
|
+
|
|
833
|
+
Returns:
|
|
834
|
+
List of wall messages
|
|
835
|
+
"""
|
|
836
|
+
url = f"{self.BASE_URLS['osiris']}/groups/{clan_id}/wall"
|
|
837
|
+
params = {
|
|
838
|
+
"limit": limit
|
|
839
|
+
}
|
|
840
|
+
response = self._make_request("GET", url, params=params)
|
|
841
|
+
return response if isinstance(response, list) else []
|
|
842
|
+
|
|
843
|
+
def delete_squad_wall_message(self, clan_id: str, message_id: str) -> Dict[str, Any]:
|
|
844
|
+
"""
|
|
845
|
+
Delete a message from the squad wall.
|
|
846
|
+
|
|
847
|
+
Args:
|
|
848
|
+
clan_id: Group/Squad ID
|
|
849
|
+
message_id: Message ID to delete
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
Deletion result
|
|
853
|
+
"""
|
|
854
|
+
url = f"{self.BASE_URLS['osiris']}/groups/{clan_id}/wall/{message_id}"
|
|
855
|
+
return self._make_request("DELETE", url)
|
|
856
|
+
|
|
857
|
+
# Private Messaging
|
|
858
|
+
|
|
859
|
+
def send_private_message(self, credential: str, message: str, reply_to: str = None,
|
|
860
|
+
kill_sign_color: str = None, kill_sign_name: str = None,
|
|
861
|
+
message_type: str = "inbox", alert_kairos: bool = False) -> Dict[str, Any]:
|
|
862
|
+
"""
|
|
863
|
+
Send a private message to a player.
|
|
864
|
+
|
|
865
|
+
Args:
|
|
866
|
+
credential: Recipient credential
|
|
867
|
+
message: Message content
|
|
868
|
+
reply_to: Message ID to reply to (optional)
|
|
869
|
+
kill_sign_color: Kill signature color (optional)
|
|
870
|
+
kill_sign_name: Kill signature name (optional)
|
|
871
|
+
message_type: Message type (default: "inbox")
|
|
872
|
+
alert_kairos: Whether to send alert notification (default: False)
|
|
873
|
+
|
|
874
|
+
Returns:
|
|
875
|
+
Message send result
|
|
876
|
+
"""
|
|
877
|
+
url = f"{self.BASE_URLS['hermes']}/messages/inbox/{credential}"
|
|
878
|
+
|
|
879
|
+
# Create message data
|
|
880
|
+
message_data = {
|
|
881
|
+
"body": message,
|
|
882
|
+
"_type": message_type
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
# Add reply_to if provided
|
|
886
|
+
if reply_to:
|
|
887
|
+
message_data["reply_to"] = reply_to
|
|
888
|
+
|
|
889
|
+
# Add kill signature if provided
|
|
890
|
+
if kill_sign_color is not None:
|
|
891
|
+
message_data["_killSignColor"] = kill_sign_color
|
|
892
|
+
if kill_sign_name:
|
|
893
|
+
message_data["_killSignName"] = kill_sign_name
|
|
894
|
+
|
|
895
|
+
# Convert to JSON string as required by API
|
|
896
|
+
import json
|
|
897
|
+
body_json = json.dumps(message_data)
|
|
898
|
+
|
|
899
|
+
# Prepare form data
|
|
900
|
+
data = {
|
|
901
|
+
"from": "RxZ Saitama", # Replace with actual sender name
|
|
902
|
+
"body": body_json + "\n",
|
|
903
|
+
"alert_kairos": str(alert_kairos).lower()
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
return self._make_request("POST", url, data=data)
|
|
907
|
+
|
|
908
|
+
def get_inbox_messages(self, limit: int = 20) -> List[Dict[str, Any]]:
|
|
909
|
+
"""
|
|
910
|
+
Get messages from your inbox.
|
|
911
|
+
|
|
912
|
+
Args:
|
|
913
|
+
limit: Maximum number of messages to retrieve
|
|
914
|
+
|
|
915
|
+
Returns:
|
|
916
|
+
List of inbox messages
|
|
917
|
+
"""
|
|
918
|
+
url = f"{self.BASE_URLS['hermes']}/messages/inbox"
|
|
919
|
+
params = {
|
|
920
|
+
"limit": limit
|
|
921
|
+
}
|
|
922
|
+
response = self._make_request("GET", url, params=params)
|
|
923
|
+
return response if isinstance(response, list) else []
|
|
924
|
+
|
|
925
|
+
def delete_inbox_message(self, message_id: str) -> Dict[str, Any]:
|
|
926
|
+
"""
|
|
927
|
+
Delete a message from your inbox.
|
|
928
|
+
|
|
929
|
+
Args:
|
|
930
|
+
message_id: Message ID to delete
|
|
931
|
+
|
|
932
|
+
Returns:
|
|
933
|
+
Deletion result
|
|
934
|
+
"""
|
|
935
|
+
url = f"{self.BASE_URLS['hermes']}/messages/inbox/me"
|
|
936
|
+
params = {
|
|
937
|
+
"msgids": message_id
|
|
938
|
+
}
|
|
939
|
+
return self._make_request("DELETE", url, params=params)
|
|
940
|
+
|
|
941
|
+
def delete_multiple_inbox_messages(self, message_ids: List[str]) -> Dict[str, Any]:
|
|
942
|
+
"""
|
|
943
|
+
Delete multiple messages from your inbox at once.
|
|
944
|
+
|
|
945
|
+
Args:
|
|
946
|
+
message_ids: List of message IDs to delete
|
|
947
|
+
|
|
948
|
+
Returns:
|
|
949
|
+
Deletion result
|
|
950
|
+
"""
|
|
951
|
+
url = f"{self.BASE_URLS['hermes']}/messages/inbox/me"
|
|
952
|
+
params = {
|
|
953
|
+
"msgids": ",".join(message_ids)
|
|
954
|
+
}
|
|
955
|
+
return self._make_request("DELETE", url, params=params)
|
|
956
|
+
|
|
957
|
+
def clear_inbox(self) -> Dict[str, Any]:
|
|
958
|
+
"""
|
|
959
|
+
Clear all messages from your inbox.
|
|
960
|
+
|
|
961
|
+
Returns:
|
|
962
|
+
Deletion result
|
|
963
|
+
"""
|
|
964
|
+
# Get all messages first
|
|
965
|
+
messages = self.get_inbox_messages(limit=1000) # Get up to 1000 messages
|
|
966
|
+
if messages:
|
|
967
|
+
message_ids = [msg.get('id', '') for msg in messages if msg.get('id')]
|
|
968
|
+
return self.delete_multiple_inbox_messages(message_ids)
|
|
969
|
+
return {"status": "success", "message": "No messages to delete"}
|
|
970
|
+
|
|
971
|
+
def send_squad_wall_message(self, clan_id: str, message: str, msg_type: int = 0,
|
|
972
|
+
player_killsig: str = None, player_killsig_color: str = None,
|
|
973
|
+
language: str = "en", activity_type: str = "user_post") -> Dict[str, Any]:
|
|
974
|
+
"""
|
|
975
|
+
Send a message to the squad/group wall.
|
|
976
|
+
|
|
977
|
+
Args:
|
|
978
|
+
clan_id: Group/Squad ID
|
|
979
|
+
message: Message content
|
|
980
|
+
msg_type: Message type (0 for regular message)
|
|
981
|
+
player_killsig: Player kill signature ID (optional)
|
|
982
|
+
player_killsig_color: Player kill signature color (optional)
|
|
983
|
+
language: Message language (default: "en")
|
|
984
|
+
activity_type: Activity type (default: "user_post")
|
|
985
|
+
|
|
986
|
+
Returns:
|
|
987
|
+
Message post result with message ID
|
|
988
|
+
"""
|
|
989
|
+
url = f"{self.BASE_URLS['osiris']}/groups/{clan_id}/wall"
|
|
990
|
+
|
|
991
|
+
# Create message data
|
|
992
|
+
message_data = {
|
|
993
|
+
"msg_body": message,
|
|
994
|
+
"msg_type": msg_type
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
# Add kill signature if provided
|
|
998
|
+
if player_killsig:
|
|
999
|
+
message_data["playerKillSign"] = player_killsig
|
|
1000
|
+
if player_killsig_color is not None:
|
|
1001
|
+
message_data["playerKillSignColor"] = player_killsig_color
|
|
1002
|
+
|
|
1003
|
+
# Convert to JSON string as required by API
|
|
1004
|
+
import json
|
|
1005
|
+
text_json = json.dumps(message_data)
|
|
1006
|
+
|
|
1007
|
+
# Prepare form data
|
|
1008
|
+
data = {
|
|
1009
|
+
"text": text_json + "\n",
|
|
1010
|
+
"language": language,
|
|
1011
|
+
"activity_type": activity_type,
|
|
1012
|
+
"alert_kairos": "false"
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return self._make_request("POST", url, data=data)
|
|
1016
|
+
|
|
1017
|
+
# Friend Management
|
|
1018
|
+
|
|
1019
|
+
def get_friends(self) -> List[Dict[str, Any]]:
|
|
1020
|
+
"""
|
|
1021
|
+
Get friends list.
|
|
1022
|
+
|
|
1023
|
+
Returns:
|
|
1024
|
+
List of friends
|
|
1025
|
+
"""
|
|
1026
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me/friends"
|
|
1027
|
+
response = self._make_request("GET", url)
|
|
1028
|
+
return response.get("friends", [])
|
|
1029
|
+
|
|
1030
|
+
def send_friend_request(self, credential: str) -> Dict[str, Any]:
|
|
1031
|
+
"""
|
|
1032
|
+
Send friend request.
|
|
1033
|
+
|
|
1034
|
+
Args:
|
|
1035
|
+
credential: Target player credential
|
|
1036
|
+
|
|
1037
|
+
Returns:
|
|
1038
|
+
Friend request result
|
|
1039
|
+
"""
|
|
1040
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me/friends"
|
|
1041
|
+
data = {"credential": credential}
|
|
1042
|
+
return self._make_request("POST", url, data=data)
|
|
1043
|
+
|
|
1044
|
+
def check_friend_status(self, credential: str) -> Dict[str, Any]:
|
|
1045
|
+
"""
|
|
1046
|
+
Check friend connection status.
|
|
1047
|
+
|
|
1048
|
+
Args:
|
|
1049
|
+
credential: Target player credential
|
|
1050
|
+
|
|
1051
|
+
Returns:
|
|
1052
|
+
Friend status information
|
|
1053
|
+
"""
|
|
1054
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me/connections/friend/{credential}"
|
|
1055
|
+
return self._make_request("GET", url)
|
|
1056
|
+
|
|
1057
|
+
# Messaging
|
|
1058
|
+
|
|
1059
|
+
def send_squad_wall_message(self, clan_id: str, message: str) -> Dict[str, Any]:
|
|
1060
|
+
"""
|
|
1061
|
+
Send message to squad wall.
|
|
1062
|
+
|
|
1063
|
+
Args:
|
|
1064
|
+
clan_id: Clan ID
|
|
1065
|
+
message: Message content
|
|
1066
|
+
|
|
1067
|
+
Returns:
|
|
1068
|
+
Message send result
|
|
1069
|
+
"""
|
|
1070
|
+
url = f"{self.BASE_URLS['osiris']}/clans/{clan_id}/wall"
|
|
1071
|
+
data = {"message": message}
|
|
1072
|
+
return self._make_request("POST", url, data=data)
|
|
1073
|
+
|
|
1074
|
+
# Events
|
|
1075
|
+
|
|
1076
|
+
def get_events(self) -> List[Dict[str, Any]]:
|
|
1077
|
+
"""
|
|
1078
|
+
Get list of active events.
|
|
1079
|
+
|
|
1080
|
+
Returns:
|
|
1081
|
+
List of events
|
|
1082
|
+
"""
|
|
1083
|
+
url = f"{self.BASE_URLS['osiris']}/events"
|
|
1084
|
+
response = self._make_request("GET", url)
|
|
1085
|
+
return response if isinstance(response, list) else response.get("events", [])
|
|
1086
|
+
|
|
1087
|
+
def get_event_details(self, event_name: str) -> Dict[str, Any]:
|
|
1088
|
+
"""
|
|
1089
|
+
Get details for a specific event.
|
|
1090
|
+
|
|
1091
|
+
Args:
|
|
1092
|
+
event_name: Event name
|
|
1093
|
+
|
|
1094
|
+
Returns:
|
|
1095
|
+
Event details
|
|
1096
|
+
"""
|
|
1097
|
+
events = self.get_events()
|
|
1098
|
+
for event in events:
|
|
1099
|
+
if event.get("name") == event_name:
|
|
1100
|
+
return event
|
|
1101
|
+
raise MC5APIError(f"Event '{event_name}' not found")
|
|
1102
|
+
|
|
1103
|
+
# Leaderboard
|
|
1104
|
+
|
|
1105
|
+
def get_leaderboard(self, leaderboard_type: str = "ro") -> Dict[str, Any]:
|
|
1106
|
+
"""
|
|
1107
|
+
Get leaderboard data.
|
|
1108
|
+
|
|
1109
|
+
Args:
|
|
1110
|
+
leaderboard_type: Leaderboard type (ro for read-only, admin for admin)
|
|
1111
|
+
|
|
1112
|
+
Returns:
|
|
1113
|
+
Leaderboard data
|
|
1114
|
+
"""
|
|
1115
|
+
if leaderboard_type == "admin":
|
|
1116
|
+
url = f"{self.BASE_URLS['olympus']}/leaderboards/desc"
|
|
1117
|
+
else:
|
|
1118
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/leaderboard_{leaderboard_type}"
|
|
1119
|
+
|
|
1120
|
+
return self._make_request("GET", url)
|
|
1121
|
+
|
|
1122
|
+
# Game Configuration
|
|
1123
|
+
|
|
1124
|
+
def get_game_object_catalog(self) -> List[Dict[str, Any]]:
|
|
1125
|
+
"""
|
|
1126
|
+
Get game object catalog.
|
|
1127
|
+
|
|
1128
|
+
Returns:
|
|
1129
|
+
List of game objects
|
|
1130
|
+
"""
|
|
1131
|
+
url = f"{self.BASE_URLS['iris']}/1875/game_object_{int(time.time())}"
|
|
1132
|
+
response = self._make_request("GET", url, require_token=False)
|
|
1133
|
+
return response if isinstance(response, list) else []
|
|
1134
|
+
|
|
1135
|
+
def get_asset_hash_metadata(self, asset_path: str) -> Dict[str, Any]:
|
|
1136
|
+
"""
|
|
1137
|
+
Get asset hash metadata.
|
|
1138
|
+
|
|
1139
|
+
Args:
|
|
1140
|
+
asset_path: Asset path
|
|
1141
|
+
|
|
1142
|
+
Returns:
|
|
1143
|
+
Asset metadata
|
|
1144
|
+
"""
|
|
1145
|
+
url = f"{self.BASE_URLS['iris']}/assets/{asset_path}/metadata/hash"
|
|
1146
|
+
return self._make_request("GET", url, require_token=False)
|
|
1147
|
+
|
|
1148
|
+
# Alias and Dogtags
|
|
1149
|
+
|
|
1150
|
+
def get_alias_info(self, alias_id: str) -> Dict[str, Any]:
|
|
1151
|
+
"""
|
|
1152
|
+
Get alias information.
|
|
1153
|
+
|
|
1154
|
+
Args:
|
|
1155
|
+
alias_id: Player alias ID
|
|
1156
|
+
|
|
1157
|
+
Returns:
|
|
1158
|
+
Alias information
|
|
1159
|
+
"""
|
|
1160
|
+
url = f"{self.BASE_URLS['auth']}/games/mygame/alias/{alias_id}"
|
|
1161
|
+
return self._make_request("GET", url)
|
|
1162
|
+
|
|
1163
|
+
def convert_dogtag_to_alias(self, dogtag: str) -> str:
|
|
1164
|
+
"""
|
|
1165
|
+
Convert dogtag to alias.
|
|
1166
|
+
|
|
1167
|
+
Args:
|
|
1168
|
+
dogtag: Dogtag string
|
|
1169
|
+
|
|
1170
|
+
Returns:
|
|
1171
|
+
Alias string
|
|
1172
|
+
"""
|
|
1173
|
+
alias = ""
|
|
1174
|
+
for char in dogtag.lower():
|
|
1175
|
+
if char.isdigit():
|
|
1176
|
+
# Digit conversion: (digit - 2) modulo 10
|
|
1177
|
+
alias_char = str((int(char) - 2) % 10)
|
|
1178
|
+
elif char.isalpha():
|
|
1179
|
+
# Letter conversion: subtract 2 from ASCII value
|
|
1180
|
+
alias_char = chr(ord(char) - 2)
|
|
1181
|
+
else:
|
|
1182
|
+
alias_char = char
|
|
1183
|
+
alias += alias_char
|
|
1184
|
+
return alias
|
|
1185
|
+
|
|
1186
|
+
# Utility Methods
|
|
1187
|
+
|
|
1188
|
+
def get_token_info(self) -> Dict[str, Any]:
|
|
1189
|
+
"""
|
|
1190
|
+
Get current token information.
|
|
1191
|
+
|
|
1192
|
+
Returns:
|
|
1193
|
+
Token data
|
|
1194
|
+
"""
|
|
1195
|
+
self._ensure_valid_token()
|
|
1196
|
+
return self._token_data.copy()
|
|
1197
|
+
|
|
1198
|
+
def is_authenticated(self) -> bool:
|
|
1199
|
+
"""
|
|
1200
|
+
Check if client is authenticated with valid token.
|
|
1201
|
+
|
|
1202
|
+
Returns:
|
|
1203
|
+
True if authenticated, False otherwise
|
|
1204
|
+
"""
|
|
1205
|
+
return self._token_data is not None and self.token_generator.validate_token(self._token_data)
|
|
1206
|
+
|
|
1207
|
+
def close(self):
|
|
1208
|
+
"""Close the HTTP session and cleanup resources."""
|
|
1209
|
+
if self.session:
|
|
1210
|
+
self.session.close()
|
|
1211
|
+
if self.token_generator:
|
|
1212
|
+
self.token_generator.close()
|
|
1213
|
+
|
|
1214
|
+
def __enter__(self):
|
|
1215
|
+
"""Context manager entry."""
|
|
1216
|
+
return self
|
|
1217
|
+
|
|
1218
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
1219
|
+
"""Context manager exit."""
|
|
1220
|
+
self.close()
|