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.
@@ -0,0 +1,31 @@
1
+ # ────────────[ CHIZOBA ]────────────────────────────
2
+ # | Email : chizoba2026@hotmail.com
3
+ # | File : __init__.py
4
+ # | License : MIT License © 2026 Chizoba
5
+ # | Brief : Modern Combat 5 API Client Package
6
+ # ────────────────★─────────────────────────────────
7
+
8
+ """
9
+ Modern Combat 5 API Client
10
+
11
+ A comprehensive Python library for interacting with the Modern Combat 5 API.
12
+ Provides easy access to authentication, profile management, clan operations,
13
+ messaging, and more.
14
+ """
15
+
16
+ __version__ = "1.0.0"
17
+ __author__ = "Chizoba"
18
+ __email__ = "chizoba2026@hotmail.com"
19
+ __license__ = "MIT"
20
+
21
+ from .client import MC5Client
22
+ from .auth import TokenGenerator
23
+ from .exceptions import MC5APIError, AuthenticationError, RateLimitError
24
+
25
+ __all__ = [
26
+ "MC5Client",
27
+ "TokenGenerator",
28
+ "MC5APIError",
29
+ "AuthenticationError",
30
+ "RateLimitError"
31
+ ]
mc5_api_client/auth.py ADDED
@@ -0,0 +1,281 @@
1
+ # ────────────[ CHIZOBA ]────────────────────────────
2
+ # | Email : chizoba2026@hotmail.com
3
+ # | File : auth.py
4
+ # | License : MIT License © 2026 Chizoba
5
+ # | Brief : Authentication and token management for MC5 API
6
+ # ────────────────★─────────────────────────────────
7
+
8
+ """
9
+ Authentication and token management for Modern Combat 5 API.
10
+ """
11
+
12
+ import time
13
+ import hashlib
14
+ import secrets
15
+ from typing import Optional, Dict, Any
16
+ from datetime import datetime, timedelta
17
+
18
+ import requests
19
+ from requests.adapters import HTTPAdapter
20
+ from urllib3.util.retry import Retry
21
+
22
+ from .exceptions import (
23
+ AuthenticationError,
24
+ InvalidCredentialsError,
25
+ TokenExpiredError,
26
+ NetworkError
27
+ )
28
+
29
+
30
+ class TokenGenerator:
31
+ """
32
+ Handles authentication and token generation for MC5 API.
33
+
34
+ Supports both user authentication and admin authentication.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ client_id: str = "1875:55979:6.0.0a:windows:windows",
40
+ auth_url: str = "https://eur-janus.gameloft.com:443/authorize",
41
+ timeout: int = 30,
42
+ max_retries: int = 3
43
+ ):
44
+ """
45
+ Initialize the TokenGenerator.
46
+
47
+ Args:
48
+ client_id: Game client identifier
49
+ auth_url: Authentication endpoint URL
50
+ timeout: Request timeout in seconds
51
+ max_retries: Maximum number of retry attempts
52
+ """
53
+ self.client_id = client_id
54
+ self.auth_url = auth_url
55
+ self.timeout = timeout
56
+ self.max_retries = max_retries
57
+
58
+ # Setup session with retry strategy
59
+ self.session = requests.Session()
60
+ retry_strategy = Retry(
61
+ total=max_retries,
62
+ status_forcelist=[429, 500, 502, 503, 504],
63
+ allowed_methods=["HEAD", "GET", "POST"],
64
+ backoff_factor=1
65
+ )
66
+ adapter = HTTPAdapter(max_retries=retry_strategy)
67
+ self.session.mount("http://", adapter)
68
+ self.session.mount("https://", adapter)
69
+
70
+ # Default headers
71
+ self.session.headers.update({
72
+ "User-Agent": "MC5-API-Client/1.0.0",
73
+ "Accept": "*/*",
74
+ "Content-Type": "application/x-www-form-urlencoded",
75
+ "Connection": "close"
76
+ })
77
+
78
+ def generate_device_id(self) -> str:
79
+ """
80
+ Generate a random device ID.
81
+
82
+ Returns:
83
+ Random 19-digit device ID
84
+ """
85
+ return str(secrets.randbelow(10**19)).zfill(19)
86
+
87
+ def generate_token(
88
+ self,
89
+ username: str,
90
+ password: str,
91
+ device_id: Optional[str] = None,
92
+ scope: str = "alert auth chat leaderboard_ro lobby message session social config storage_ro tracking_bi feed storage leaderboard_admin social_eve social soc transaction schedule lottery voice matchmaker"
93
+ ) -> Dict[str, Any]:
94
+ """
95
+ Generate an access token for the MC5 API.
96
+
97
+ Args:
98
+ username: MC5 username (e.g., "anonymous:d2luOF92M18xNzU4NjQxNTE3Xy9bF5EFTbvwR7w1nqg2JnI=")
99
+ password: Account password
100
+ device_id: Device ID (generated if not provided)
101
+ scope: Space-separated list of permission scopes
102
+
103
+ Returns:
104
+ Dictionary containing token information
105
+
106
+ Raises:
107
+ InvalidCredentialsError: If credentials are invalid
108
+ AuthenticationError: If authentication fails for other reasons
109
+ NetworkError: If network issues occur
110
+ """
111
+ if device_id is None:
112
+ device_id = self.generate_device_id()
113
+
114
+ payload = {
115
+ "client_id": self.client_id,
116
+ "username": username,
117
+ "password": password,
118
+ "scope": scope,
119
+ "device_id": device_id
120
+ }
121
+
122
+ try:
123
+ print(f"🔵 Generating token for user: {username[:20]}...")
124
+ response = self.session.post(
125
+ self.auth_url,
126
+ data=payload,
127
+ timeout=self.timeout
128
+ )
129
+
130
+ if response.status_code == 200:
131
+ token_data = response.json()
132
+ access_token = token_data.get("access_token")
133
+
134
+ if not access_token:
135
+ raise AuthenticationError("No access token in response")
136
+
137
+ # Parse token components
138
+ token_parts = access_token.split(",")
139
+ if len(token_parts) < 6:
140
+ raise AuthenticationError("Invalid token format")
141
+
142
+ result = {
143
+ "access_token": access_token,
144
+ "token_id": token_parts[0],
145
+ "scopes": token_parts[1].split(" "),
146
+ "client_id": token_parts[2],
147
+ "expires_at": float(token_parts[3]),
148
+ "credential": token_parts[4],
149
+ "device_id": token_parts[5],
150
+ "signature": token_parts[6] if len(token_parts) > 6 else None,
151
+ "generated_at": time.time(),
152
+ "device_id_used": device_id
153
+ }
154
+
155
+ # Calculate expiry time
156
+ expiry_timestamp = result["expires_at"]
157
+ expiry_datetime = datetime.fromtimestamp(expiry_timestamp)
158
+ result["expires_in"] = expiry_timestamp - time.time()
159
+ result["expires_at_datetime"] = expiry_datetime
160
+
161
+ print(f"🟢 Token generated successfully! Expires: {expiry_datetime}")
162
+ return result
163
+
164
+ elif response.status_code == 401:
165
+ raise InvalidCredentialsError("Invalid username or password")
166
+ elif response.status_code == 403:
167
+ raise AuthenticationError("Access forbidden - check client permissions")
168
+ else:
169
+ error_msg = f"Authentication failed with status {response.status_code}"
170
+ try:
171
+ error_data = response.json()
172
+ error_msg += f": {error_data.get('error_description', error_data.get('error', 'Unknown error'))}"
173
+ except:
174
+ pass
175
+ raise AuthenticationError(error_msg)
176
+
177
+ except requests.exceptions.Timeout:
178
+ raise NetworkError("Authentication request timed out")
179
+ except requests.exceptions.ConnectionError:
180
+ raise NetworkError("Failed to connect to authentication server")
181
+ except requests.exceptions.RequestException as e:
182
+ raise NetworkError(f"Network error during authentication: {str(e)}")
183
+
184
+ def generate_admin_token(
185
+ self,
186
+ device_id: Optional[str] = None,
187
+ scope: str = "alert auth chat leaderboard_ro lobby message session social config storage_ro tracking_bi"
188
+ ) -> Dict[str, Any]:
189
+ """
190
+ Generate an admin access token.
191
+
192
+ Args:
193
+ device_id: Device ID (generated if not provided)
194
+ scope: Admin permission scopes
195
+
196
+ Returns:
197
+ Dictionary containing admin token information
198
+
199
+ Raises:
200
+ AuthenticationError: If admin authentication fails
201
+ """
202
+ return self.generate_token(
203
+ username="game:mc5_system",
204
+ password="admin",
205
+ device_id=device_id,
206
+ scope=scope
207
+ )
208
+
209
+ def validate_token(self, token_data: Dict[str, Any]) -> bool:
210
+ """
211
+ Check if a token is still valid.
212
+
213
+ Args:
214
+ token_data: Token data from generate_token()
215
+
216
+ Returns:
217
+ True if token is valid, False otherwise
218
+ """
219
+ if not token_data or "expires_at" not in token_data:
220
+ return False
221
+
222
+ # Add 60 second buffer to account for clock skew
223
+ expiry_time = token_data["expires_at"] - 60
224
+ current_time = time.time()
225
+
226
+ return current_time < expiry_time
227
+
228
+ def refresh_token_if_needed(
229
+ self,
230
+ token_data: Dict[str, Any],
231
+ username: str,
232
+ password: str,
233
+ device_id: Optional[str] = None
234
+ ) -> Dict[str, Any]:
235
+ """
236
+ Refresh token if it's expired or will expire soon.
237
+
238
+ Args:
239
+ token_data: Current token data
240
+ username: Account username
241
+ password: Account password
242
+ device_id: Device ID used for original token
243
+
244
+ Returns:
245
+ New token data if refresh was needed, original data otherwise
246
+ """
247
+ if self.validate_token(token_data):
248
+ print("🟢 Current token is still valid")
249
+ return token_data
250
+
251
+ print("🟠 Token expired, generating new token...")
252
+ return self.generate_token(username, password, device_id or token_data.get("device_id_used"))
253
+
254
+ def get_token_info(self, access_token: str) -> Dict[str, Any]:
255
+ """
256
+ Parse and return information about an access token.
257
+
258
+ Args:
259
+ access_token: Raw access token string
260
+
261
+ Returns:
262
+ Parsed token information
263
+ """
264
+ parts = access_token.split(",")
265
+ if len(parts) < 6:
266
+ raise AuthenticationError("Invalid token format")
267
+
268
+ return {
269
+ "token_id": parts[0],
270
+ "scopes": parts[1].split(" "),
271
+ "client_id": parts[2],
272
+ "expires_at": float(parts[3]),
273
+ "credential": parts[4],
274
+ "device_id": parts[5],
275
+ "signature": parts[6] if len(parts) > 6 else None
276
+ }
277
+
278
+ def close(self):
279
+ """Close the HTTP session."""
280
+ if self.session:
281
+ self.session.close()