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/cli.py
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# ────────────[ CHIZOBA ]────────────────────────────
|
|
2
|
+
# | Email : chizoba2026@hotmail.com
|
|
3
|
+
# | File : cli.py
|
|
4
|
+
# | License : MIT License © 2026 Chizoba
|
|
5
|
+
# | Brief : Modern CLI interface for MC5 API Client
|
|
6
|
+
# ────────────────★─────────────────────────────────
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
Modern Command Line Interface for MC5 API Client.
|
|
10
|
+
Features rich output, debug capabilities, and comprehensive functionality.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
import argparse
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Optional, Dict, Any
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
from rich.table import Table
|
|
25
|
+
from rich.panel import Panel
|
|
26
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
27
|
+
from rich.prompt import Prompt, Confirm
|
|
28
|
+
from rich.syntax import Syntax
|
|
29
|
+
from rich.tree import Tree
|
|
30
|
+
from rich import print as rprint
|
|
31
|
+
RICH_AVAILABLE = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
RICH_AVAILABLE = False
|
|
34
|
+
print("🟠 Warning: Rich library not found. Install with: pip install rich")
|
|
35
|
+
print("🟠 Falling back to basic output...")
|
|
36
|
+
|
|
37
|
+
from .auth import TokenGenerator
|
|
38
|
+
from .exceptions import MC5APIError, AuthenticationError, NetworkError
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MC5CLI:
|
|
42
|
+
"""
|
|
43
|
+
Modern CLI interface for MC5 API operations.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, debug: bool = False):
|
|
47
|
+
"""
|
|
48
|
+
Initialize the CLI.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
debug: Enable debug output
|
|
52
|
+
"""
|
|
53
|
+
self.debug = debug
|
|
54
|
+
self.token_generator = TokenGenerator()
|
|
55
|
+
self.console = Console() if RICH_AVAILABLE else None
|
|
56
|
+
|
|
57
|
+
# Configuration file path
|
|
58
|
+
self.config_dir = Path.home() / ".mc5"
|
|
59
|
+
self.config_file = self.config_dir / "config.json"
|
|
60
|
+
self.token_file = self.config_dir / "token.json"
|
|
61
|
+
|
|
62
|
+
# Ensure config directory exists
|
|
63
|
+
self.config_dir.mkdir(exist_ok=True)
|
|
64
|
+
|
|
65
|
+
self._setup_logging()
|
|
66
|
+
|
|
67
|
+
def _setup_logging(self):
|
|
68
|
+
"""Setup debug logging."""
|
|
69
|
+
if self.debug:
|
|
70
|
+
import logging
|
|
71
|
+
logging.basicConfig(
|
|
72
|
+
level=logging.DEBUG,
|
|
73
|
+
format='🔵 %(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
74
|
+
handlers=[
|
|
75
|
+
logging.FileHandler(self.config_dir / 'debug.log'),
|
|
76
|
+
logging.StreamHandler()
|
|
77
|
+
]
|
|
78
|
+
)
|
|
79
|
+
self.logger = logging.getLogger(__name__)
|
|
80
|
+
else:
|
|
81
|
+
self.logger = None
|
|
82
|
+
|
|
83
|
+
def _print(self, message: str, color: str = "white", style: str = None):
|
|
84
|
+
"""Print with rich formatting if available."""
|
|
85
|
+
if self.console:
|
|
86
|
+
if style:
|
|
87
|
+
self.console.print(message, style=style)
|
|
88
|
+
else:
|
|
89
|
+
self.console.print(message, style=color)
|
|
90
|
+
else:
|
|
91
|
+
print(message)
|
|
92
|
+
|
|
93
|
+
def _print_success(self, message: str):
|
|
94
|
+
"""Print success message."""
|
|
95
|
+
self._print(f"🟢 {message}", "green")
|
|
96
|
+
|
|
97
|
+
def _print_error(self, message: str):
|
|
98
|
+
"""Print error message."""
|
|
99
|
+
self._print(f"🔴 {message}", "red")
|
|
100
|
+
|
|
101
|
+
def _print_warning(self, message: str):
|
|
102
|
+
"""Print warning message."""
|
|
103
|
+
self._print(f"🟠 {message}", "yellow")
|
|
104
|
+
|
|
105
|
+
def _print_info(self, message: str):
|
|
106
|
+
"""Print info message."""
|
|
107
|
+
self._print(f"🔵 {message}", "blue")
|
|
108
|
+
|
|
109
|
+
def _print_debug(self, message: str):
|
|
110
|
+
"""Print debug message."""
|
|
111
|
+
if self.debug:
|
|
112
|
+
self._print(f"🟣 {message}", "magenta")
|
|
113
|
+
if self.logger:
|
|
114
|
+
self.logger.debug(message)
|
|
115
|
+
|
|
116
|
+
def _show_banner(self):
|
|
117
|
+
"""Display the CLI banner."""
|
|
118
|
+
if self.console:
|
|
119
|
+
banner = Panel.fit(
|
|
120
|
+
"[bold cyan]Modern Combat 5 API Client[/bold cyan]\n"
|
|
121
|
+
"[dim]Version 1.0.0 | Author: Chizoba[/dim]\n"
|
|
122
|
+
"[dim]Email: chizoba2026@hotmail.com[/dim]",
|
|
123
|
+
border_style="cyan",
|
|
124
|
+
padding=(1, 2)
|
|
125
|
+
)
|
|
126
|
+
self.console.print(banner)
|
|
127
|
+
else:
|
|
128
|
+
print("=" * 60)
|
|
129
|
+
print("🎮 Modern Combat 5 API Client v1.0.0")
|
|
130
|
+
print("👤 Author: Chizoba | 📧 chizoba2026@hotmail.com")
|
|
131
|
+
print("=" * 60)
|
|
132
|
+
|
|
133
|
+
def _save_config(self, config: Dict[str, Any]):
|
|
134
|
+
"""Save configuration to file."""
|
|
135
|
+
try:
|
|
136
|
+
with open(self.config_file, 'w') as f:
|
|
137
|
+
json.dump(config, f, indent=2)
|
|
138
|
+
self._print_debug(f"Configuration saved to {self.config_file}")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
self._print_error(f"Failed to save configuration: {e}")
|
|
141
|
+
|
|
142
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
143
|
+
"""Load configuration from file."""
|
|
144
|
+
if not self.config_file.exists():
|
|
145
|
+
return {}
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
with open(self.config_file, 'r') as f:
|
|
149
|
+
config = json.load(f)
|
|
150
|
+
self._print_debug(f"Configuration loaded from {self.config_file}")
|
|
151
|
+
return config
|
|
152
|
+
except Exception as e:
|
|
153
|
+
self._print_error(f"Failed to load configuration: {e}")
|
|
154
|
+
return {}
|
|
155
|
+
|
|
156
|
+
def _save_token(self, token_data: Dict[str, Any]):
|
|
157
|
+
"""Save token data to file."""
|
|
158
|
+
try:
|
|
159
|
+
# Convert datetime to string for JSON serialization
|
|
160
|
+
save_data = token_data.copy()
|
|
161
|
+
if 'expires_at_datetime' in save_data:
|
|
162
|
+
save_data['expires_at_datetime'] = save_data['expires_at_datetime'].isoformat()
|
|
163
|
+
|
|
164
|
+
with open(self.token_file, 'w') as f:
|
|
165
|
+
json.dump(save_data, f, indent=2)
|
|
166
|
+
self._print_debug(f"Token saved to {self.token_file}")
|
|
167
|
+
except Exception as e:
|
|
168
|
+
self._print_error(f"Failed to save token: {e}")
|
|
169
|
+
|
|
170
|
+
def _load_token(self) -> Optional[Dict[str, Any]]:
|
|
171
|
+
"""Load token data from file."""
|
|
172
|
+
if not self.token_file.exists():
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
with open(self.token_file, 'r') as f:
|
|
177
|
+
token_data = json.load(f)
|
|
178
|
+
|
|
179
|
+
# Convert datetime string back to datetime object
|
|
180
|
+
if 'expires_at_datetime' in token_data and isinstance(token_data['expires_at_datetime'], str):
|
|
181
|
+
from datetime import datetime
|
|
182
|
+
token_data['expires_at_datetime'] = datetime.fromisoformat(token_data['expires_at_datetime'])
|
|
183
|
+
|
|
184
|
+
self._print_debug(f"Token loaded from {self.token_file}")
|
|
185
|
+
return token_data
|
|
186
|
+
except Exception as e:
|
|
187
|
+
self._print_error(f"Failed to load token: {e}")
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
def cmd_generate_token(self, args):
|
|
191
|
+
"""Generate a new access token."""
|
|
192
|
+
self._print_info("Generating new access token...")
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
# Get credentials from args or prompt
|
|
196
|
+
username = args.username or Prompt.ask("Enter username")
|
|
197
|
+
password = args.password or Prompt.ask("Enter password", password=True)
|
|
198
|
+
device_id = args.device_id
|
|
199
|
+
|
|
200
|
+
with Progress(
|
|
201
|
+
SpinnerColumn(),
|
|
202
|
+
TextColumn("[progress.description]{task.description}"),
|
|
203
|
+
console=self.console
|
|
204
|
+
) as progress:
|
|
205
|
+
task = progress.add_task("Authenticating...", total=None)
|
|
206
|
+
|
|
207
|
+
token_data = self.token_generator.generate_token(
|
|
208
|
+
username=username,
|
|
209
|
+
password=password,
|
|
210
|
+
device_id=device_id,
|
|
211
|
+
scope=args.scope
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
progress.update(task, description="Token generated!")
|
|
215
|
+
|
|
216
|
+
# Save token if requested
|
|
217
|
+
if args.save:
|
|
218
|
+
self._save_token(token_data)
|
|
219
|
+
self._print_success("Token saved to configuration")
|
|
220
|
+
|
|
221
|
+
# Display token information
|
|
222
|
+
self._display_token_info(token_data)
|
|
223
|
+
|
|
224
|
+
# Save credentials to config if requested
|
|
225
|
+
if args.save_config:
|
|
226
|
+
config = self._load_config()
|
|
227
|
+
config['username'] = username
|
|
228
|
+
config['device_id'] = token_data['device_id_used']
|
|
229
|
+
config['default_scope'] = args.scope
|
|
230
|
+
self._save_config(config)
|
|
231
|
+
self._print_success("Credentials saved to configuration")
|
|
232
|
+
|
|
233
|
+
except AuthenticationError as e:
|
|
234
|
+
self._print_error(f"Authentication failed: {e}")
|
|
235
|
+
except NetworkError as e:
|
|
236
|
+
self._print_error(f"Network error: {e}")
|
|
237
|
+
except Exception as e:
|
|
238
|
+
self._print_error(f"Unexpected error: {e}")
|
|
239
|
+
if self.debug:
|
|
240
|
+
import traceback
|
|
241
|
+
traceback.print_exc()
|
|
242
|
+
|
|
243
|
+
def cmd_generate_admin_token(self, args):
|
|
244
|
+
"""Generate an admin access token."""
|
|
245
|
+
self._print_info("Generating admin access token...")
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
with Progress(
|
|
249
|
+
SpinnerColumn(),
|
|
250
|
+
TextColumn("[progress.description]{task.description}"),
|
|
251
|
+
console=self.console
|
|
252
|
+
) as progress:
|
|
253
|
+
task = progress.add_task("Authenticating as admin...", total=None)
|
|
254
|
+
|
|
255
|
+
token_data = self.token_generator.generate_admin_token(
|
|
256
|
+
device_id=args.device_id,
|
|
257
|
+
scope=args.scope
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
progress.update(task, description="Admin token generated!")
|
|
261
|
+
|
|
262
|
+
# Save token if requested
|
|
263
|
+
if args.save:
|
|
264
|
+
self._save_token(token_data)
|
|
265
|
+
self._print_success("Admin token saved to configuration")
|
|
266
|
+
|
|
267
|
+
# Display token information
|
|
268
|
+
self._display_token_info(token_data)
|
|
269
|
+
|
|
270
|
+
except AuthenticationError as e:
|
|
271
|
+
self._print_error(f"Admin authentication failed: {e}")
|
|
272
|
+
except NetworkError as e:
|
|
273
|
+
self._print_error(f"Network error: {e}")
|
|
274
|
+
except Exception as e:
|
|
275
|
+
self._print_error(f"Unexpected error: {e}")
|
|
276
|
+
if self.debug:
|
|
277
|
+
import traceback
|
|
278
|
+
traceback.print_exc()
|
|
279
|
+
|
|
280
|
+
def cmd_validate_token(self, args):
|
|
281
|
+
"""Validate a saved or provided token."""
|
|
282
|
+
if args.token:
|
|
283
|
+
# Validate provided token
|
|
284
|
+
try:
|
|
285
|
+
token_info = self.token_generator.get_token_info(args.token)
|
|
286
|
+
# Create mock token data for validation
|
|
287
|
+
token_data = {"expires_at": token_info["expires_at"]}
|
|
288
|
+
except:
|
|
289
|
+
self._print_error("Invalid token format")
|
|
290
|
+
return
|
|
291
|
+
else:
|
|
292
|
+
# Validate saved token
|
|
293
|
+
token_data = self._load_token()
|
|
294
|
+
if not token_data:
|
|
295
|
+
self._print_error("No saved token found. Use --token to provide a token.")
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
if self.token_generator.validate_token(token_data):
|
|
299
|
+
self._print_success("Token is valid")
|
|
300
|
+
|
|
301
|
+
if self.console:
|
|
302
|
+
expiry_time = datetime.fromtimestamp(token_data["expires_at"])
|
|
303
|
+
self._print_info(f"Expires at: {expiry_time}")
|
|
304
|
+
else:
|
|
305
|
+
self._print_info(f"Expires at: {token_data['expires_at']}")
|
|
306
|
+
else:
|
|
307
|
+
self._print_error("Token is expired or invalid")
|
|
308
|
+
|
|
309
|
+
def cmd_show_config(self, args):
|
|
310
|
+
"""Show current configuration."""
|
|
311
|
+
config = self._load_config()
|
|
312
|
+
token_data = self._load_token()
|
|
313
|
+
|
|
314
|
+
if self.console:
|
|
315
|
+
# Rich table output
|
|
316
|
+
table = Table(title="MC5 API Configuration")
|
|
317
|
+
table.add_column("Setting", style="cyan")
|
|
318
|
+
table.add_column("Value", style="white")
|
|
319
|
+
|
|
320
|
+
for key, value in config.items():
|
|
321
|
+
if 'password' in key.lower():
|
|
322
|
+
value = "***" if value else "Not set"
|
|
323
|
+
table.add_row(key, str(value))
|
|
324
|
+
|
|
325
|
+
self.console.print(table)
|
|
326
|
+
|
|
327
|
+
if token_data:
|
|
328
|
+
self._print_info("\nToken Information:")
|
|
329
|
+
self._display_token_info(token_data)
|
|
330
|
+
else:
|
|
331
|
+
# Basic output
|
|
332
|
+
print("Configuration:")
|
|
333
|
+
for key, value in config.items():
|
|
334
|
+
if 'password' in key.lower():
|
|
335
|
+
value = "***" if value else "Not set"
|
|
336
|
+
print(f" {key}: {value}")
|
|
337
|
+
|
|
338
|
+
if token_data:
|
|
339
|
+
print("\nToken Information:")
|
|
340
|
+
self._display_token_info(token_data)
|
|
341
|
+
|
|
342
|
+
def cmd_clear_config(self, args):
|
|
343
|
+
"""Clear saved configuration."""
|
|
344
|
+
self._print_info("Clearing saved configuration...")
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
if self.config_file.exists():
|
|
348
|
+
self.config_file.unlink()
|
|
349
|
+
self._print_success("Configuration cleared")
|
|
350
|
+
|
|
351
|
+
if self.token_file.exists():
|
|
352
|
+
self.token_file.unlink()
|
|
353
|
+
self._print_success("Tokens cleared")
|
|
354
|
+
|
|
355
|
+
if self.log_file.exists():
|
|
356
|
+
self.log_file.unlink()
|
|
357
|
+
self._print_success("Logs cleared")
|
|
358
|
+
|
|
359
|
+
self._print_success("All saved data cleared successfully")
|
|
360
|
+
except Exception as e:
|
|
361
|
+
self._print_error(f"Failed to clear configuration: {e}")
|
|
362
|
+
|
|
363
|
+
def cmd_clan(self, args):
|
|
364
|
+
"""Handle clan management commands."""
|
|
365
|
+
if not args.clan_command:
|
|
366
|
+
self._print_error("Please specify a clan command. Use 'mc5 clan --help' for available commands.")
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
# Initialize client for clan operations
|
|
370
|
+
try:
|
|
371
|
+
client = MC5Client()
|
|
372
|
+
|
|
373
|
+
# Try to load saved token
|
|
374
|
+
token_data = self._load_token()
|
|
375
|
+
if token_data:
|
|
376
|
+
client._token_data = token_data
|
|
377
|
+
self._print_success("Using saved authentication token")
|
|
378
|
+
else:
|
|
379
|
+
self._print_warning("No saved token found. Please generate a token first.")
|
|
380
|
+
self._print_info("Run: mc5 generate-token --username 'user:pass' --password 'pass' --save")
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
except Exception as e:
|
|
384
|
+
self._print_error(f"Failed to initialize client: {e}")
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
if args.clan_command == "search":
|
|
389
|
+
self._cmd_clan_search(client, args)
|
|
390
|
+
elif args.clan_command == "create":
|
|
391
|
+
self._cmd_clan_create(client, args)
|
|
392
|
+
elif args.clan_command == "info":
|
|
393
|
+
self._cmd_clan_info(client, args)
|
|
394
|
+
elif args.clan_command == "members":
|
|
395
|
+
self._cmd_clan_members(client, args)
|
|
396
|
+
elif args.clan_command == "invite":
|
|
397
|
+
self._cmd_clan_invite(client, args)
|
|
398
|
+
elif args.clan_command == "applications":
|
|
399
|
+
self._cmd_clan_applications(client, args)
|
|
400
|
+
elif args.clan_command == "accept":
|
|
401
|
+
self._cmd_clan_accept(client, args)
|
|
402
|
+
elif args.clan_command == "reject":
|
|
403
|
+
self._cmd_clan_reject(client, args)
|
|
404
|
+
elif args.clan_command == "apply":
|
|
405
|
+
self._cmd_clan_apply(client, args)
|
|
406
|
+
elif args.clan_command == "stats":
|
|
407
|
+
self._cmd_clan_stats(client, args)
|
|
408
|
+
elif args.clan_command == "leaderboard":
|
|
409
|
+
self._cmd_clan_leaderboard(client, args)
|
|
410
|
+
else:
|
|
411
|
+
self._print_error(f"Unknown clan command: {args.clan_command}")
|
|
412
|
+
|
|
413
|
+
except Exception as e:
|
|
414
|
+
self._print_error(f"Clan command failed: {e}")
|
|
415
|
+
finally:
|
|
416
|
+
try:
|
|
417
|
+
client.close()
|
|
418
|
+
except:
|
|
419
|
+
pass
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def run(self):
|
|
423
|
+
"""Run the CLI application."""
|
|
424
|
+
import sys
|
|
425
|
+
if len(sys.argv) < 2:
|
|
426
|
+
self._print_help()
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
command = sys.argv[1]
|
|
430
|
+
|
|
431
|
+
if command == "--help" or command == "-h":
|
|
432
|
+
self._print_help()
|
|
433
|
+
elif command == "version":
|
|
434
|
+
self._print_version()
|
|
435
|
+
else:
|
|
436
|
+
self._print(f"Unknown command: {command}", "red")
|
|
437
|
+
self._print_help()
|
|
438
|
+
|
|
439
|
+
def _print_help(self):
|
|
440
|
+
"""Print help information."""
|
|
441
|
+
self._print("🎮 MC5 API Client CLI", "cyan")
|
|
442
|
+
self._print("=" * 30, "cyan")
|
|
443
|
+
self._print("\nAvailable commands:", "yellow")
|
|
444
|
+
self._print(" --help, -h Show this help message")
|
|
445
|
+
self._print(" version Show version information")
|
|
446
|
+
self._print("\n📚 For more examples, see the README.md file!", "green")
|
|
447
|
+
|
|
448
|
+
def _print_version(self):
|
|
449
|
+
"""Print version information."""
|
|
450
|
+
self._print("🎮 MC5 API Client v1.0.0", "cyan")
|
|
451
|
+
self._print("📦 The ultimate Modern Combat 5 API library", "green")
|
|
452
|
+
self._print("👤 Author: Chizoba", "blue")
|
|
453
|
+
self._print("📧 Email: chizoba2026@hotmail.com", "blue")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def main():
|
|
457
|
+
"""Main entry point for the CLI."""
|
|
458
|
+
cli = MC5CLI()
|
|
459
|
+
cli.run()
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
if __name__ == "__main__":
|
|
463
|
+
main()
|