mc5-api-client 1.0.6__py3-none-any.whl → 1.0.9__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,570 @@
1
+ #!/usr/bin/env python3
2
+ # ────────────[ CHIZOBA ]────────────────────────────
3
+ # | Email : chizoba2026@hotmail.com
4
+ # | File : simple_client.py
5
+ # | License | MIT License © 2026 Chizoba
6
+ # | Brief : User-friendly MC5 API client for non-developers
7
+ # ────────────────★─────────────────────────────────
8
+
9
+ """
10
+ MC5 Simple API Client - User Friendly Interface
11
+
12
+ A simplified version of the MC5 API Client designed for non-developers.
13
+ Provides easy-to-use methods with automatic clan detection and simplified syntax.
14
+ """
15
+
16
+ from typing import Optional, Dict, Any, List
17
+ from .client import MC5Client
18
+ from .exceptions import MC5APIError
19
+
20
+ class SimpleMC5Client:
21
+ """
22
+ User-friendly MC5 API client for non-developers.
23
+
24
+ Automatically handles clan detection and provides simplified methods
25
+ for common operations.
26
+ """
27
+
28
+ def __init__(self, username: str, password: str):
29
+ """
30
+ Initialize the client with your MC5 credentials.
31
+
32
+ Args:
33
+ username: Your MC5 username (e.g., "anonymous:your_credential")
34
+ password: Your MC5 password
35
+ """
36
+ self.username = username
37
+ self.password = password
38
+ self.client = None
39
+ self._clan_id = None
40
+ self._profile = None
41
+
42
+ def connect(self) -> bool:
43
+ """
44
+ Connect to MC5 and auto-detect your clan.
45
+
46
+ Returns:
47
+ True if connection successful, False otherwise
48
+ """
49
+ try:
50
+ # Create the client only when connecting
51
+ self.client = MC5Client(self.username, self.password)
52
+
53
+ # Get profile info
54
+ self._profile = self.client.get_profile()
55
+
56
+ # Auto-detect clan ID
57
+ groups = self._profile.get('groups', [])
58
+ if groups:
59
+ self._clan_id = groups[0]
60
+ print(f"✅ Connected! Auto-detected clan: {self._get_clan_name()}")
61
+ return True
62
+ else:
63
+ print("⚠️ Connected but no clan found")
64
+ return True
65
+
66
+ except Exception as e:
67
+ print(f"❌ Connection failed: {e}")
68
+ return False
69
+
70
+ def _get_clan_name(self) -> str:
71
+ """Get the clan name if available."""
72
+ if not self._clan_id:
73
+ return "No clan"
74
+
75
+ try:
76
+ clan_info = self.client.get_clan_info(self._clan_id)
77
+ return clan_info.get('name', 'Unknown clan')
78
+ except:
79
+ return "Unknown clan"
80
+
81
+ def search_player(self, dogtag: str) -> Optional[Dict[str, Any]]:
82
+ """
83
+ Search for a player by their dogtag.
84
+
85
+ Args:
86
+ dogtag: Player's dogtag (4-8 characters)
87
+
88
+ Returns:
89
+ Player information or None if not found
90
+ """
91
+ try:
92
+ stats = self.client.get_player_stats_by_dogtag(dogtag)
93
+ if 'error' not in stats:
94
+ # Parse and return simplified info
95
+ player_credential = list(stats.keys())[0]
96
+ player_data = stats[player_credential]
97
+ parsed = self.client.parse_player_stats(stats)
98
+
99
+ return {
100
+ 'dogtag': dogtag,
101
+ 'account': player_data.get('player_info', {}).get('account', 'Unknown'),
102
+ 'rating': parsed.get('rating', 0),
103
+ 'kills': parsed.get('overall_stats', {}).get('total_kills', 0),
104
+ 'deaths': parsed.get('overall_stats', {}).get('total_deaths', 0),
105
+ 'kd_ratio': parsed.get('overall_stats', {}).get('kd_ratio', 0),
106
+ 'headshot_percent': parsed.get('overall_stats', {}).get('headshot_percentage', 0),
107
+ 'play_time': parsed.get('overall_stats', {}).get('play_time', 0)
108
+ }
109
+ else:
110
+ print(f"❌ Player not found: {stats['error']}")
111
+ return None
112
+
113
+ except Exception as e:
114
+ print(f"❌ Error searching player: {e}")
115
+ return None
116
+
117
+ def get_my_stats(self) -> Optional[Dict[str, Any]]:
118
+ """
119
+ Get your own player statistics.
120
+
121
+ Returns:
122
+ Your player information or None if error
123
+ """
124
+ if not self._profile:
125
+ print("❌ Not connected. Call connect() first.")
126
+ return None
127
+
128
+ try:
129
+ return {
130
+ 'name': self._profile.get('name', 'Unknown'),
131
+ 'level': self._profile.get('level', 0),
132
+ 'xp': self._profile.get('xp', 0),
133
+ 'clan': self._get_clan_name(),
134
+ 'clan_id': self._clan_id
135
+ }
136
+ except Exception as e:
137
+ print(f"❌ Error getting your stats: {e}")
138
+ return None
139
+
140
+ def get_clan_members(self) -> List[Dict[str, Any]]:
141
+ """
142
+ Get all members of your clan.
143
+
144
+ Returns:
145
+ List of clan members with their stats
146
+ """
147
+ if not self._clan_id:
148
+ print("❌ No clan detected")
149
+ return []
150
+
151
+ try:
152
+ members = self.client.get_clan_members(self._clan_id)
153
+
154
+ simplified_members = []
155
+ for member in members:
156
+ simplified_members.append({
157
+ 'name': member.get('name', 'Unknown'),
158
+ 'level': member.get('level', 0),
159
+ 'xp': member.get('_xp', 0),
160
+ 'score': member.get('_score', 0),
161
+ 'status': member.get('status', 'Unknown'),
162
+ 'online': member.get('online', False),
163
+ 'killsig': member.get('_killsig_id', 'default')
164
+ })
165
+
166
+ return simplified_members
167
+
168
+ except Exception as e:
169
+ print(f"❌ Error getting clan members: {e}")
170
+ return []
171
+
172
+ def kick_member(self, dogtag: str, reason: str = "Kicked from clan") -> bool:
173
+ """
174
+ Kick a member from your clan by their dogtag.
175
+
176
+ Args:
177
+ dogtag: Member's dogtag to kick
178
+ reason: Reason for kicking (optional)
179
+
180
+ Returns:
181
+ True if successful, False otherwise
182
+ """
183
+ if not self._clan_id:
184
+ print("❌ No clan detected")
185
+ return False
186
+
187
+ try:
188
+ result = self.client.kick_clan_member_by_dogtag(
189
+ dogtag=dogtag,
190
+ clan_id=self._clan_id,
191
+ from_name="CLAN_ADMIN"
192
+ )
193
+
194
+ if 'error' not in result:
195
+ print(f"✅ Successfully kicked player with dogtag {dogtag}")
196
+ return True
197
+ else:
198
+ print(f"❌ Failed to kick: {result['error']}")
199
+ return False
200
+
201
+ except Exception as e:
202
+ print(f"❌ Error kicking member: {e}")
203
+ return False
204
+
205
+ def update_clan_settings(self, name: Optional[str] = None,
206
+ description: Optional[str] = None,
207
+ member_limit: Optional[int] = None) -> bool:
208
+ """
209
+ Update your clan settings.
210
+
211
+ Args:
212
+ name: New clan name (optional)
213
+ description: New clan description (optional)
214
+ member_limit: New member limit (optional)
215
+
216
+ Returns:
217
+ True if successful, False otherwise
218
+ """
219
+ if not self._clan_id:
220
+ print("❌ No clan detected")
221
+ return False
222
+
223
+ try:
224
+ # Build settings dict with only provided values
225
+ settings = {}
226
+ if name:
227
+ settings['name'] = name
228
+ if description:
229
+ settings['description'] = description
230
+ if member_limit:
231
+ settings['member_limit'] = str(member_limit)
232
+
233
+ if not settings:
234
+ print("⚠️ No settings to update")
235
+ return False
236
+
237
+ result = self.client.update_clan_settings(self._clan_id, **settings)
238
+
239
+ if 'error' not in result:
240
+ print(f"✅ Clan settings updated successfully")
241
+ return True
242
+ else:
243
+ print(f"❌ Failed to update settings: {result['error']}")
244
+ return False
245
+
246
+ except Exception as e:
247
+ print(f"❌ Error updating clan settings: {e}")
248
+ return False
249
+
250
+ def send_message(self, dogtag: str, message: str) -> bool:
251
+ """
252
+ Send a private message to a player.
253
+
254
+ Args:
255
+ dogtag: Player's dogtag
256
+ message: Message to send
257
+
258
+ Returns:
259
+ True if successful, False otherwise
260
+ """
261
+ try:
262
+ # First get player info to find their credential
263
+ player_info = self.client.get_player_stats_by_dogtag(dogtag)
264
+ if 'error' in player_info:
265
+ print(f"❌ Player not found: {player_info['error']}")
266
+ return False
267
+
268
+ # Get credential from player data
269
+ player_credential = list(player_info.keys())[0]
270
+
271
+ # Send message
272
+ result = self.client.send_private_message(
273
+ credential=player_credential,
274
+ message=message
275
+ )
276
+
277
+ if 'error' not in result:
278
+ print(f"✅ Message sent to {dogtag}")
279
+ return True
280
+ else:
281
+ print(f"❌ Failed to send message: {result['error']}")
282
+ return False
283
+
284
+ except Exception as e:
285
+ print(f"❌ Error sending message: {e}")
286
+ return False
287
+
288
+ def get_inactive_members(self, days_inactive: int = 30) -> List[Dict[str, Any]]:
289
+ """
290
+ Get members who haven't been active for specified days.
291
+
292
+ Args:
293
+ days_inactive: Number of days to consider inactive
294
+
295
+ Returns:
296
+ List of inactive members
297
+ """
298
+ if not self._clan_id:
299
+ print("❌ No clan detected")
300
+ return []
301
+
302
+ try:
303
+ members = self.client.get_clan_members(self._clan_id)
304
+ inactive_members = []
305
+
306
+ for member in members:
307
+ # Check last online time (simplified - you'd need to parse actual date)
308
+ if not member.get('online', False):
309
+ # In a real implementation, you'd check the last_online timestamp
310
+ # For now, we'll consider offline members as potentially inactive
311
+ inactive_members.append({
312
+ 'name': member.get('name', 'Unknown'),
313
+ 'level': member.get('level', 0),
314
+ 'xp': member.get('_xp', 0),
315
+ 'status': member.get('status', 'Unknown'),
316
+ 'online': False
317
+ })
318
+
319
+ return inactive_members
320
+
321
+ except Exception as e:
322
+ print(f"❌ Error getting inactive members: {e}")
323
+ return []
324
+
325
+ def auto_kick_inactive_members(self, days_inactive: int = 30, dry_run: bool = True) -> Dict[str, Any]:
326
+ """
327
+ Automatically kick inactive members with safety checks.
328
+
329
+ Args:
330
+ days_inactive: Days of inactivity to trigger kick
331
+ dry_run: If True, only show who would be kicked without actually kicking
332
+
333
+ Returns:
334
+ Dictionary with results
335
+ """
336
+ inactive_members = self.get_inactive_members(days_inactive)
337
+
338
+ if not inactive_members:
339
+ return {'kicked': 0, 'skipped': 0, 'members': []}
340
+
341
+ results = {'kicked': 0, 'skipped': 0, 'members': []}
342
+
343
+ for member in inactive_members:
344
+ # Safety checks
345
+ if member['level'] > 50: # Don't kick high-level members
346
+ print(f"⚠️ Skipping {member['name']} (Level {member['level']} - too high)")
347
+ results['skipped'] += 1
348
+ continue
349
+
350
+ if 'leader' in member['status'].lower() or 'admin' in member['status'].lower():
351
+ print(f"⚠️ Skipping {member['name']} ({member['status']} - protected role)")
352
+ results['skipped'] += 1
353
+ continue
354
+
355
+ if dry_run:
356
+ print(f"🔍 Would kick: {member['name']} (Level {member['level']}) - Inactive for {days_inactive}+ days")
357
+ else:
358
+ # Note: This would need the member's dogtag, which we'd need to fetch
359
+ print(f"🔨 Kicking {member['name']} (Level {member['level']})")
360
+ # client.kick_member(member['dogtag'], f"Inactive for {days_inactive} days")
361
+ results['kicked'] += 1
362
+
363
+ results['members'].append(member)
364
+
365
+ return results
366
+
367
+ def close(self):
368
+ """Close the connection."""
369
+ try:
370
+ if self.client:
371
+ self.client.close()
372
+ print("✅ Connection closed")
373
+ except:
374
+ pass
375
+
376
+ def __enter__(self):
377
+ """Context manager entry."""
378
+ return self
379
+
380
+ def __exit__(self, exc_type, exc_val, exc_tb):
381
+ """Context manager exit."""
382
+ self.close()
383
+
384
+
385
+ # Even simpler interface for absolute beginners
386
+ def quick_search(dogtag: str, username: str, password: str) -> Optional[Dict[str, Any]]:
387
+ """
388
+ Quick search for a player by dogtag.
389
+
390
+ Args:
391
+ dogtag: Player's dogtag to search
392
+ username: Your MC5 username
393
+ password: Your MC5 password
394
+
395
+ Returns:
396
+ Player information or None if not found
397
+ """
398
+ with SimpleMC5Client(username, password) as client:
399
+ if client.connect():
400
+ return client.search_player(dogtag)
401
+ return None
402
+
403
+
404
+ def quick_kick(dogtag: str, username: str, password: str, reason: str = "Kicked") -> bool:
405
+ """
406
+ Quick kick a member by dogtag.
407
+
408
+ Args:
409
+ dogtag: Member's dogtag to kick
410
+ username: Your MC5 username
411
+ password: Your MC5 password
412
+ reason: Reason for kicking
413
+
414
+ Returns:
415
+ True if successful, False otherwise
416
+ """
417
+ with SimpleMC5Client(username, password) as client:
418
+ if client.connect():
419
+ return client.kick_member(dogtag, reason)
420
+ return False
421
+
422
+
423
+ def batch_search_players(dogtags: List[str], username: str, password: str) -> Dict[str, Any]:
424
+ """
425
+ Search for multiple players at once.
426
+
427
+ Args:
428
+ dogtags: List of dogtags to search
429
+ username: Your MC5 username
430
+ password: Your MC5 password
431
+
432
+ Returns:
433
+ Dictionary with search results
434
+ """
435
+ results = {'found': [], 'not_found': [], 'errors': []}
436
+
437
+ with SimpleMC5Client(username, password) as client:
438
+ if client.connect():
439
+ for dogtag in dogtags:
440
+ try:
441
+ player = client.search_player(dogtag)
442
+ if player:
443
+ results['found'].append(player)
444
+ print(f"✅ Found {dogtag}: {player['kills']:,} kills")
445
+ else:
446
+ results['not_found'].append(dogtag)
447
+ print(f"❌ Not found: {dogtag}")
448
+ except Exception as e:
449
+ results['errors'].append({'dogtag': dogtag, 'error': str(e)})
450
+ print(f"⚠️ Error with {dogtag}: {e}")
451
+
452
+ return results
453
+
454
+
455
+ def clan_cleanup(username: str, password: str,
456
+ inactive_days: int = 30,
457
+ min_level: int = 10,
458
+ dry_run: bool = True) -> Dict[str, Any]:
459
+ """
460
+ Comprehensive clan cleanup with conditional logic.
461
+
462
+ Args:
463
+ username: Your MC5 username
464
+ password: Your MC5 password
465
+ inactive_days: Days of inactivity to consider
466
+ min_level: Minimum level to protect from kicking
467
+ dry_run: If True, only show what would be done
468
+
469
+ Returns:
470
+ Dictionary with cleanup results
471
+ """
472
+ results = {
473
+ 'total_members': 0,
474
+ 'active_members': 0,
475
+ 'inactive_members': 0,
476
+ 'would_kick': 0,
477
+ 'protected': 0,
478
+ 'actions': []
479
+ }
480
+
481
+ with SimpleMC5Client(username, password) as client:
482
+ if not client.connect():
483
+ return results
484
+
485
+ members = client.get_clan_members()
486
+ results['total_members'] = len(members)
487
+
488
+ for member in members:
489
+ # Conditional logic for each member
490
+ if member.get('online', False):
491
+ results['active_members'] += 1
492
+ results['actions'].append(f"✅ {member['name']} - Online (protected)")
493
+ continue
494
+
495
+ # Check level protection
496
+ if member.get('level', 0) >= min_level:
497
+ results['protected'] += 1
498
+ results['actions'].append(f"🛡️ {member['name']} - Level {member['level']} (protected)")
499
+ continue
500
+
501
+ # Check role protection
502
+ status = member.get('status', '').lower()
503
+ if any(role in status for role in ['leader', 'admin', 'officer']):
504
+ results['protected'] += 1
505
+ results['actions'].append(f"👑 {member['name']} - {member['status']} (protected)")
506
+ continue
507
+
508
+ # Member is inactive and not protected
509
+ results['inactive_members'] += 1
510
+ results['would_kick'] += 1
511
+
512
+ if dry_run:
513
+ results['actions'].append(f"🔍 Would kick: {member['name']} - Level {member['level']}, Inactive")
514
+ else:
515
+ # Note: Would need dogtag for actual kicking
516
+ results['actions'].append(f"🔨 Kicked: {member['name']} - Level {member['level']}")
517
+
518
+ return results
519
+
520
+
521
+ def monitor_clan_activity(username: str, password: str,
522
+ check_interval: int = 60,
523
+ max_checks: int = 10) -> None:
524
+ """
525
+ Monitor clan activity with while loop.
526
+
527
+ Args:
528
+ username: Your MC5 username
529
+ password: Your MC5 password
530
+ check_interval: Seconds between checks
531
+ max_checks: Maximum number of checks to perform
532
+ """
533
+ import time
534
+
535
+ check_count = 0
536
+
537
+ with SimpleMC5Client(username, password) as client:
538
+ if not client.connect():
539
+ return
540
+
541
+ print(f"🔍 Starting clan activity monitoring (checking every {check_interval}s)")
542
+
543
+ while check_count < max_checks:
544
+ check_count += 1
545
+
546
+ try:
547
+ members = client.get_clan_members()
548
+ online_count = sum(1 for m in members if m.get('online', False))
549
+
550
+ print(f"Check {check_count}/{max_checks}: {online_count}/{len(members)} members online")
551
+
552
+ # Conditional alerts
553
+ if online_count == 0:
554
+ print("🚨 Alert: No members online!")
555
+ elif online_count > len(members) * 0.8:
556
+ print("🎉 High activity: Most members are online!")
557
+
558
+ # Break if all members are online
559
+ if online_count == len(members):
560
+ print("✅ All members are online! Stopping monitor.")
561
+ break
562
+
563
+ except Exception as e:
564
+ print(f"⚠️ Error during check {check_count}: {e}")
565
+
566
+ # Wait before next check (except after last check)
567
+ if check_count < max_checks:
568
+ time.sleep(check_interval)
569
+
570
+ print(f"🏁 Monitoring completed after {check_count} checks")