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/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()