mc5-api-client 1.0.2__py3-none-any.whl → 1.0.4__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/client.py CHANGED
@@ -1211,6 +1211,696 @@ class MC5Client:
1211
1211
  if self.token_generator:
1212
1212
  self.token_generator.close()
1213
1213
 
1214
+ # Events API
1215
+
1216
+ def get_events(self, event_type: str = None, status: str = None) -> List[Dict[str, Any]]:
1217
+ """
1218
+ Fetch all active and upcoming game events.
1219
+
1220
+ Args:
1221
+ event_type: Filter by event type (e.g., "sem_1875", "mc5_activities_event")
1222
+ status: Filter by status ("active", "ended", "upcoming")
1223
+
1224
+ Returns:
1225
+ List of event objects with detailed information
1226
+ """
1227
+ url = f"{self.BASE_URLS['osiris']}/events"
1228
+ params = {}
1229
+
1230
+ if event_type:
1231
+ params['category'] = event_type
1232
+ if status:
1233
+ params['status'] = status
1234
+
1235
+ events = self._make_request("GET", url, params=params)
1236
+ return events if isinstance(events, list) else []
1237
+
1238
+ def get_event_details(self, event_name: str) -> Dict[str, Any]:
1239
+ """
1240
+ Get detailed information about a specific event including tasks and milestones.
1241
+
1242
+ Args:
1243
+ event_name: The name/identifier of the event
1244
+
1245
+ Returns:
1246
+ Detailed event information with parsed tasks and milestones
1247
+ """
1248
+ events = self.get_events()
1249
+ for event in events:
1250
+ if isinstance(event, dict) and event.get('name') == event_name:
1251
+ return self._parse_event_template(event)
1252
+ return {}
1253
+
1254
+ def _parse_event_template(self, event: Dict[str, Any]) -> Dict[str, Any]:
1255
+ """
1256
+ Parse event template to extract tasks and milestones information.
1257
+
1258
+ Args:
1259
+ event: Raw event data from API
1260
+
1261
+ Returns:
1262
+ Parsed event with structured tasks and milestones
1263
+ """
1264
+ parsed_event = {
1265
+ 'name': event.get('name'),
1266
+ 'category': event.get('category'),
1267
+ 'description': event.get('description'),
1268
+ 'start_date': event.get('start_date'),
1269
+ 'end_date': event.get('end_date'),
1270
+ 'status': event.get('status'),
1271
+ 'tasks': [],
1272
+ 'milestones': [],
1273
+ 'tournament_info': None
1274
+ }
1275
+
1276
+ template = event.get('_template', {})
1277
+ if template:
1278
+ tuning = template.get('event_tuning', {})
1279
+
1280
+ # Parse tasks
1281
+ tasks_data = tuning.get('_tasks', {}).get('value', [])
1282
+ for task in tasks_data:
1283
+ parsed_task = {
1284
+ 'milestone_id': task.get('_milestone_id'),
1285
+ 'points': task.get('points', 0),
1286
+ 'condition': task.get('condition', {}),
1287
+ 'award': task.get('award', {}),
1288
+ 'condition_type': self._extract_condition_type(task.get('condition', {})),
1289
+ 'reward': self._extract_reward(task.get('award', {}))
1290
+ }
1291
+ parsed_event['tasks'].append(parsed_task)
1292
+
1293
+ # Parse milestones
1294
+ milestones_data = tuning.get('milestones', {}).get('value', [])
1295
+ for milestone in milestones_data:
1296
+ parsed_milestone = {
1297
+ 'milestone_id': milestone.get('_milestone_id'),
1298
+ 'condition': milestone.get('condition', 0),
1299
+ 'award': milestone.get('award', {}),
1300
+ 'reward': self._extract_reward(milestone.get('award', {}))
1301
+ }
1302
+ parsed_event['milestones'].append(parsed_milestone)
1303
+
1304
+ # Parse tournament info if present
1305
+ tournament = template.get('tournament', {})
1306
+ if tournament:
1307
+ parsed_event['tournament_info'] = {
1308
+ 'type': tournament.get('type'),
1309
+ 'leaderboard': tournament.get('leaderboard', {}),
1310
+ 'awards': tournament.get('awards', [])
1311
+ }
1312
+
1313
+ return parsed_event
1314
+
1315
+ def _extract_condition_type(self, condition: Dict[str, Any]) -> str:
1316
+ """
1317
+ Extract the condition type from task condition data.
1318
+
1319
+ Args:
1320
+ condition: Condition data from task
1321
+
1322
+ Returns:
1323
+ String representing the condition type
1324
+ """
1325
+ if not condition or 'value' not in condition:
1326
+ return 'unknown'
1327
+
1328
+ value = condition['value']
1329
+ if isinstance(value, dict):
1330
+ # Extract the first key which represents the condition type
1331
+ return list(value.keys())[0] if value else 'unknown'
1332
+
1333
+ return str(value)
1334
+
1335
+ def _extract_reward(self, award: Dict[str, Any]) -> Dict[str, Any]:
1336
+ """
1337
+ Extract reward information from award data.
1338
+
1339
+ Args:
1340
+ award: Award data from task or milestone
1341
+
1342
+ Returns:
1343
+ Structured reward information
1344
+ """
1345
+ reward = {'type': 'unknown', 'items': []}
1346
+
1347
+ if not award or 'value' not in award:
1348
+ return reward
1349
+
1350
+ award_value = award['value']
1351
+ if isinstance(award_value, list):
1352
+ for item in award_value:
1353
+ if isinstance(item, dict) and 'value' in item:
1354
+ for reward_type, reward_data in item['value'].items():
1355
+ reward['items'].append({
1356
+ 'type': reward_type,
1357
+ 'amount': reward_data.get('value', 0) if isinstance(reward_data, dict) else reward_data
1358
+ })
1359
+ reward['type'] = 'items'
1360
+
1361
+ return reward
1362
+
1363
+ def get_daily_tasks(self) -> Dict[str, Any]:
1364
+ """
1365
+ Get current daily tasks with progress and rewards.
1366
+
1367
+ Returns:
1368
+ Daily tasks event information with parsed tasks and milestones
1369
+ """
1370
+ events = self.get_events(event_type="mc5_activities_event")
1371
+ for event in events:
1372
+ if 'daily' in event.get('name', '').lower() or 'activities' in event.get('name', '').lower():
1373
+ return self._parse_event_template(event)
1374
+ return {}
1375
+
1376
+ def get_squad_events(self) -> List[Dict[str, Any]]:
1377
+ """
1378
+ Get all squad/team events.
1379
+
1380
+ Returns:
1381
+ List of squad events with detailed information
1382
+ """
1383
+ events = self.get_events(event_type="sem_1875")
1384
+ squad_events = []
1385
+
1386
+ for event in events:
1387
+ parsed_event = self._parse_event_template(event)
1388
+ squad_events.append(parsed_event)
1389
+
1390
+ return squad_events
1391
+
1392
+ def get_event_progress(self, event_name: str) -> Dict[str, Any]:
1393
+ """
1394
+ Get current progress for a specific event (if available).
1395
+
1396
+ Args:
1397
+ event_name: The name/identifier of the event
1398
+
1399
+ Returns:
1400
+ Event progress information
1401
+ """
1402
+ # This would typically require a different endpoint for user progress
1403
+ # For now, return the event details as a base
1404
+ return self.get_event_details(event_name)
1405
+
1406
+ def calculate_event_rewards(self, event_name: str, current_points: int = 0) -> Dict[str, Any]:
1407
+ """
1408
+ Calculate potential rewards for an event based on current points.
1409
+
1410
+ Args:
1411
+ event_name: The name/identifier of the event
1412
+ current_points: Current points earned by the player
1413
+
1414
+ Returns:
1415
+ Available milestones and rewards
1416
+ """
1417
+ event_details = self.get_event_details(event_name)
1418
+ if not event_details:
1419
+ return {}
1420
+
1421
+ available_milestones = []
1422
+ total_rewards = {'type': 'items', 'items': []}
1423
+
1424
+ for milestone in event_details.get('milestones', []):
1425
+ if current_points >= milestone['condition']:
1426
+ # Milestone is available
1427
+ available_milestones.append(milestone)
1428
+ # Add milestone rewards to total
1429
+ if milestone.get('reward', {}).get('items'):
1430
+ total_rewards['items'].extend(milestone['reward']['items'])
1431
+
1432
+ return {
1433
+ 'event_name': event_name,
1434
+ 'current_points': current_points,
1435
+ 'available_milestones': available_milestones,
1436
+ 'total_rewards': total_rewards,
1437
+ 'next_milestone': self._get_next_milestone(event_details, current_points)
1438
+ }
1439
+
1440
+ def _get_next_milestone(self, event_details: Dict[str, Any], current_points: int) -> Dict[str, Any]:
1441
+ """
1442
+ Get the next milestone the player can achieve.
1443
+
1444
+ Args:
1445
+ event_details: Parsed event details
1446
+ current_points: Current points earned
1447
+
1448
+ Returns:
1449
+ Next milestone information or empty dict if none available
1450
+ """
1451
+ milestones = sorted(event_details.get('milestones', []), key=lambda x: x.get('condition', 0))
1452
+
1453
+ for milestone in milestones:
1454
+ if current_points < milestone.get('condition', 0):
1455
+ return milestone
1456
+
1457
+ return {}
1458
+
1459
+ # Account Management
1460
+
1461
+ def import_account_data(self, from_credential: str, secret: str, platform: str = "windows", account_data: Dict[str, Any] = None) -> Dict[str, Any]:
1462
+ """
1463
+ Import account data from another platform or backup.
1464
+
1465
+ Args:
1466
+ from_credential: Source platform credential (e.g., "microsoftgraph:..." or "anonymous:...")
1467
+ secret: Encrypted verification secret
1468
+ platform: Target platform (default: "windows")
1469
+ account_data: Platform-specific account data (optional)
1470
+
1471
+ Returns:
1472
+ Import result with status and details
1473
+ """
1474
+ url = f"{self.BASE_URLS['osiris']}/accounts/me/import"
1475
+ params = {
1476
+ "from_credential": from_credential,
1477
+ "secret": secret
1478
+ }
1479
+
1480
+ data = {}
1481
+ if platform:
1482
+ data["platform"] = platform
1483
+ if account_data:
1484
+ data["data"] = account_data
1485
+
1486
+ return self._make_request("POST", url, params=params, data=data)
1487
+
1488
+ def link_account(self, target_credential: str, platform: str = "windows") -> Dict[str, Any]:
1489
+ """
1490
+ Link current account with another platform account.
1491
+
1492
+ Args:
1493
+ target_credential: Target platform credential to link
1494
+ platform: Target platform (default: "windows")
1495
+
1496
+ Returns:
1497
+ Account linking result
1498
+ """
1499
+ # This would typically use a specific account linking endpoint
1500
+ # For now, using import as a base implementation
1501
+ return self.import_account_data(
1502
+ from_credential=target_credential,
1503
+ secret="", # Would need proper secret generation
1504
+ platform=platform
1505
+ )
1506
+
1507
+ def get_account_info(self, credential: str = None) -> Dict[str, Any]:
1508
+ """
1509
+ Get detailed account information including linked platforms.
1510
+
1511
+ Args:
1512
+ credential: Specific credential to query (optional, uses current account if not provided)
1513
+
1514
+ Returns:
1515
+ Account information with platform details
1516
+ """
1517
+ # This would typically use a specific account info endpoint
1518
+ # For now, returning profile as base information
1519
+ return self.get_profile()
1520
+
1521
+ def unlink_account(self, platform_credential: str) -> Dict[str, Any]:
1522
+ """
1523
+ Unlink a previously linked platform account.
1524
+
1525
+ Args:
1526
+ platform_credential: Platform credential to unlink
1527
+
1528
+ Returns:
1529
+ Unlink result
1530
+ """
1531
+ # This would typically use a specific unlink endpoint
1532
+ # For now, returning a placeholder response
1533
+ return {
1534
+ "status": "success",
1535
+ "message": "Account unlink functionality requires specific endpoint implementation",
1536
+ "unlinked_credential": platform_credential
1537
+ }
1538
+
1539
+ def migrate_account_data(self, from_platform: str, to_platform: str, include_data: List[str] = None) -> Dict[str, Any]:
1540
+ """
1541
+ Migrate account data between platforms.
1542
+
1543
+ Args:
1544
+ from_platform: Source platform
1545
+ to_platform: Target platform
1546
+ include_data: List of data types to include (e.g., ["profile", "inventory", "progress"])
1547
+
1548
+ Returns:
1549
+ Migration result with status and details
1550
+ """
1551
+ if include_data is None:
1552
+ include_data = ["profile", "inventory", "progress"]
1553
+
1554
+ # This would typically use a specific migration endpoint
1555
+ # For now, returning a placeholder response
1556
+ return {
1557
+ "status": "success",
1558
+ "message": "Account migration functionality requires specific endpoint implementation",
1559
+ "from_platform": from_platform,
1560
+ "to_platform": to_platform,
1561
+ "included_data": include_data
1562
+ }
1563
+
1564
+ # Alias/Dogtags System
1565
+
1566
+ def convert_dogtag_to_alias(self, dogtag: str) -> str:
1567
+ """
1568
+ Convert a dogtag (player ID) to alias format.
1569
+
1570
+ Args:
1571
+ dogtag: Dogtag in XXXX format (hexadecimal)
1572
+
1573
+ Returns:
1574
+ Alias string
1575
+ """
1576
+ if not dogtag or len(dogtag) != 4:
1577
+ return dogtag
1578
+
1579
+ alias = ""
1580
+ for char in dogtag.lower():
1581
+ if char.isdigit():
1582
+ # Digit: subtract 2 using modulo 10 arithmetic
1583
+ digit = int(char)
1584
+ new_digit = (digit - 2) % 10
1585
+ alias += str(new_digit)
1586
+ elif char.isalpha():
1587
+ # Letter: subtract 2 from ASCII value
1588
+ new_char = chr(ord(char) - 2)
1589
+ alias += new_char
1590
+ else:
1591
+ alias += char
1592
+
1593
+ return alias
1594
+
1595
+ def convert_alias_to_dogtag(self, alias: str) -> str:
1596
+ """
1597
+ Convert an alias back to dogtag format.
1598
+
1599
+ Args:
1600
+ alias: Alias string
1601
+
1602
+ Returns:
1603
+ Dogtag in XXXX format
1604
+ """
1605
+ if not alias or len(alias) != 4:
1606
+ return alias
1607
+
1608
+ dogtag = ""
1609
+ for char in alias.lower():
1610
+ if char.isdigit():
1611
+ # Digit: add 2 using modulo 10 arithmetic
1612
+ digit = int(char)
1613
+ new_digit = (digit + 2) % 10
1614
+ dogtag += str(new_digit)
1615
+ elif char.isalpha():
1616
+ # Letter: add 2 to ASCII value
1617
+ new_char = chr(ord(char) + 2)
1618
+ dogtag += new_char
1619
+ else:
1620
+ dogtag += char
1621
+
1622
+ return dogtag
1623
+
1624
+ def get_alias_info(self, alias_id: str) -> Dict[str, Any]:
1625
+ """
1626
+ Retrieve player information using alias/dogtag.
1627
+
1628
+ Args:
1629
+ alias_id: Player's alias (e.g., "d33d") or dogtag
1630
+
1631
+ Returns:
1632
+ Player account information
1633
+ """
1634
+ # Convert to alias if it's a dogtag
1635
+ if len(alias_id) == 4 and all(c in '0123456789abcdefABCDEF' for c in alias_id):
1636
+ alias_id = self.convert_dogtag_to_alias(alias_id)
1637
+
1638
+ url = f"{self.BASE_URLS['janus']}/games/mygame/alias/{alias_id}"
1639
+ return self._make_request("GET", url)
1640
+
1641
+ def search_player_by_alias(self, alias: str) -> Dict[str, Any]:
1642
+ """
1643
+ Search for a player by their alias.
1644
+
1645
+ Args:
1646
+ alias: Player alias to search for
1647
+
1648
+ Returns:
1649
+ Player information if found
1650
+ """
1651
+ return self.get_alias_info(alias)
1652
+
1653
+ # Game Configuration
1654
+
1655
+ def get_asset_metadata(self, asset_path: str) -> Dict[str, Any]:
1656
+ """
1657
+ Get hash metadata for specific game assets.
1658
+
1659
+ Args:
1660
+ asset_path: Asset path (e.g., "videos_HD_UPD18_pvx")
1661
+
1662
+ Returns:
1663
+ Asset metadata including hash
1664
+ """
1665
+ url = f"{self.BASE_URLS['iris']}/assets/{self.client_id}/{asset_path}/metadata/hash"
1666
+ return self._make_request("GET", url)
1667
+
1668
+ def get_game_object_catalog(self) -> List[Dict[str, Any]]:
1669
+ """
1670
+ Get the catalog of in-game objects (currencies, weapons, items, etc.).
1671
+
1672
+ Returns:
1673
+ List of game objects with detailed information
1674
+ """
1675
+ # This would typically use a dynamic URL with timestamp
1676
+ # For now, using a placeholder URL
1677
+ import time
1678
+ url = f"https://iris16-gold.gameloft.com/game_object_{int(time.time())}"
1679
+ try:
1680
+ return self._make_request("GET", url)
1681
+ except:
1682
+ # Fallback to basic catalog if dynamic URL fails
1683
+ return []
1684
+
1685
+ def get_service_urls(self) -> Dict[str, str]:
1686
+ """
1687
+ Get service URLs for the current region and client.
1688
+
1689
+ Returns:
1690
+ Dictionary of service URLs
1691
+ """
1692
+ url = f"https://eve.gameloft.com/config/{self.client_id}/datacenters/eur/urls"
1693
+ try:
1694
+ return self._make_request("GET", url)
1695
+ except:
1696
+ # Return default URLs if endpoint fails
1697
+ return {
1698
+ "osiris": "https://eur-osiris.gameloft.com",
1699
+ "janus": "https://eur-janus.gameloft.com",
1700
+ "iris": "https://eur-iris.gameloft.com",
1701
+ "hermes": "https://eur-hermes.gameloft.com"
1702
+ }
1703
+
1704
+ def get_game_config(self, config_type: str = "basic") -> Dict[str, Any]:
1705
+ """
1706
+ Get game configuration data.
1707
+
1708
+ Args:
1709
+ config_type: Type of configuration to retrieve ("basic", "advanced", "all")
1710
+
1711
+ Returns:
1712
+ Game configuration data
1713
+ """
1714
+ config_data = {
1715
+ "service_urls": self.get_service_urls(),
1716
+ "client_id": self.client_id
1717
+ }
1718
+
1719
+ if config_type in ["advanced", "all"]:
1720
+ config_data["game_objects"] = self.get_game_object_catalog()
1721
+
1722
+ if config_type == "all":
1723
+ config_data["asset_metadata"] = "Use get_asset_metadata() for specific assets"
1724
+
1725
+ return config_data
1726
+
1727
+ # Batch Operations
1728
+
1729
+ def get_batch_profiles(self, credentials: List[str], include_fields: List[str] = None) -> Dict[str, Any]:
1730
+ """
1731
+ Get batch player profiles with detailed statistics and game save data.
1732
+
1733
+ Args:
1734
+ credentials: List of player credentials to fetch
1735
+ include_fields: List of fields to include (default: ['_game_save', 'inventory'])
1736
+
1737
+ Returns:
1738
+ Dictionary with player profiles containing detailed statistics
1739
+ """
1740
+ if include_fields is None:
1741
+ include_fields = ['_game_save', 'inventory']
1742
+
1743
+ url = "https://app-468561b3-9ecd-4d21-8241-30ed288f4d8b.gold0009.gameloft.com/1875/windows/09/public/OfficialScripts/mc5Portal.wsgi"
1744
+
1745
+ data = {
1746
+ 'op_code': 'get_batch_profiles',
1747
+ 'client_id': self.client_id,
1748
+ 'credentials': ','.join(credentials),
1749
+ 'pandora': f"https://vgold-eur.gameloft.com/{self.client_id}",
1750
+ 'include_fields': ','.join(include_fields)
1751
+ }
1752
+
1753
+ headers = {
1754
+ 'Accept': '*/*',
1755
+ 'Content-Type': 'application/x-www-form-urlencoded',
1756
+ 'accept-encoding': 'identity'
1757
+ }
1758
+
1759
+ return self._make_request("POST", url, data=data, headers=headers)
1760
+
1761
+ def get_player_stats_by_dogtag(self, dogtag: str) -> Dict[str, Any]:
1762
+ """
1763
+ Get detailed player statistics using their dogtag (in-game ID).
1764
+
1765
+ Args:
1766
+ dogtag: Player's dogtag in XXXX format (hexadecimal)
1767
+
1768
+ Returns:
1769
+ Player statistics and profile information
1770
+ """
1771
+ # Convert dogtag to alias for API lookup
1772
+ alias = self.convert_dogtag_to_alias(dogtag)
1773
+
1774
+ # Get player info using alias
1775
+ player_info = self.get_alias_info(alias)
1776
+ if not player_info.get('credential'):
1777
+ return {'error': f'Player not found for dogtag: {dogtag}', 'alias': alias}
1778
+
1779
+ # Get detailed stats using the credential
1780
+ try:
1781
+ stats = self.get_batch_profiles([player_info['credential']])
1782
+
1783
+ # Add dogtag and alias info to the response
1784
+ if player_info['credential'] in stats:
1785
+ stats[player_info['credential']]['dogtag'] = dogtag
1786
+ stats[player_info['credential']]['alias'] = alias
1787
+ stats[player_info['credential']]['player_info'] = player_info
1788
+
1789
+ return stats
1790
+
1791
+ except Exception as e:
1792
+ return {
1793
+ 'error': f'Failed to get stats for dogtag {dogtag}: {e}',
1794
+ 'dogtag': dogtag,
1795
+ 'alias': alias,
1796
+ 'player_info': player_info
1797
+ }
1798
+
1799
+ def search_player_by_dogtag(self, dogtag: str) -> Dict[str, Any]:
1800
+ """
1801
+ Search for a player using their dogtag and return their profile information.
1802
+
1803
+ Args:
1804
+ dogtag: Player's dogtag in XXXX format (hexadecimal)
1805
+
1806
+ Returns:
1807
+ Complete player information including stats if found
1808
+ """
1809
+ return self.get_player_stats_by_dogtag(dogtag)
1810
+
1811
+ def get_player_detailed_stats(self, credential: str) -> Dict[str, Any]:
1812
+ """
1813
+ Get detailed player statistics including game save and inventory.
1814
+
1815
+ Args:
1816
+ credential: Player's credential
1817
+
1818
+ Returns:
1819
+ Detailed player statistics and game data
1820
+ """
1821
+ return self.get_batch_profiles([credential])
1822
+
1823
+ def parse_player_stats(self, stats_data: Dict[str, Any]) -> Dict[str, Any]:
1824
+ """
1825
+ Parse and structure player statistics for easier consumption.
1826
+
1827
+ Args:
1828
+ stats_data: Raw stats data from get_batch_profiles
1829
+
1830
+ Returns:
1831
+ Structured player statistics
1832
+ """
1833
+ if not stats_data or not isinstance(stats_data, dict):
1834
+ return {'error': 'Invalid stats data'}
1835
+
1836
+ # Get the first (and likely only) player's data
1837
+ player_credential = list(stats_data.keys())[0] if stats_data else None
1838
+ if not player_credential or player_credential not in stats_data:
1839
+ return {'error': 'No player data found'}
1840
+
1841
+ player_data = stats_data[player_credential]
1842
+
1843
+ # Extract game save data
1844
+ game_save = player_data.get('_game_save', {})
1845
+ inventory = player_data.get('inventory', {})
1846
+
1847
+ # Parse statistics
1848
+ parsed_stats = {
1849
+ 'credential': player_credential,
1850
+ 'rating': game_save.get('rating', 0),
1851
+ 'rating_duel': game_save.get('rating_duel', 0),
1852
+ 'rating_last': game_save.get('rating_last', 0),
1853
+ 'current_skill': game_save.get('current_skill', 'unknown'),
1854
+ 'current_weapon': game_save.get('current_weapon', 'unknown'),
1855
+ 'current_loadout': game_save.get('current_loadout', 0),
1856
+ 'kill_signature': game_save.get('killsig', {}),
1857
+ 'progress': game_save.get('progress', []),
1858
+ 'loadouts': game_save.get('loadouts', []),
1859
+ 'statistics': game_save.get('statistics', {}),
1860
+ 'inventory': inventory,
1861
+ 'xp': inventory.get('xp', 0),
1862
+ 'vip_points': inventory.get('vip_points', 0)
1863
+ }
1864
+
1865
+ # Parse weapon statistics
1866
+ weapons = inventory.get('weapons', {})
1867
+ weapon_stats = []
1868
+
1869
+ for weapon_id, weapon_data in weapons.items():
1870
+ if isinstance(weapon_data, dict):
1871
+ weapon_stats.append({
1872
+ 'id': weapon_id,
1873
+ 'kills': weapon_data.get('kills', 0),
1874
+ 'shots': weapon_data.get('shots', 0),
1875
+ 'hits': weapon_data.get('hits', 0),
1876
+ 'score': weapon_data.get('score', 0),
1877
+ 'time_used': weapon_data.get('time_used', 0)
1878
+ })
1879
+
1880
+ # Sort by kills
1881
+ weapon_stats.sort(key=lambda x: x['kills'], reverse=True)
1882
+ parsed_stats['weapon_stats'] = weapon_stats
1883
+
1884
+ # Parse overall statistics
1885
+ stats = game_save.get('statistics', {})
1886
+ if stats:
1887
+ sp_stats = stats.get('sp', {})
1888
+ mp_stats = stats.get('mp', {})
1889
+
1890
+ parsed_stats['overall_stats'] = {
1891
+ 'total_kills': sp_stats.get('kill.total', 0) + mp_stats.get('kill.total', 0),
1892
+ 'total_deaths': sp_stats.get('death.total', 0) + mp_stats.get('death.total', 0),
1893
+ 'total_headshots': sp_stats.get('kill.headshots', 0) + mp_stats.get('kill.headshots', 0),
1894
+ 'total_assists': sp_stats.get('kill.assists', 0) + mp_stats.get('kill.assists', 0),
1895
+ 'total_time_played': sp_stats.get('time.played', 0) + mp_stats.get('time.played', 0),
1896
+ 'sp_kills': sp_stats.get('kill.total', 0),
1897
+ 'mp_kills': mp_stats.get('kill.total', 0),
1898
+ 'sp_headshots': sp_stats.get('kill.headshots', 0),
1899
+ 'mp_headshots': mp_stats.get('kill.headshots', 0)
1900
+ }
1901
+
1902
+ return parsed_stats
1903
+
1214
1904
  def __enter__(self):
1215
1905
  """Context manager entry."""
1216
1906
  return self
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mc5-api-client
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: A comprehensive Python library for interacting with the Modern Combat 5 API
5
5
  Home-page: https://pypi.org/project/mc5-api-client/
6
6
  Author: Chizoba
@@ -140,13 +140,6 @@ pip install .
140
140
 
141
141
  ### 🔧 Build Status
142
142
 
143
- ✅ **Compilation Successful** - No critical errors
144
- ⚠️ **Warnings Fixed** - Configuration warnings resolved
145
- ✅ **All Examples Included** - 7 comprehensive example scripts
146
- ✅ **CLI Entry Points** - `mc5` command available
147
- ✅ **Dependencies Managed** - All requirements included
148
-
149
- ## �� Let's Get Started!
150
143
 
151
144
  ### Step 1: Install the Library
152
145
 
@@ -156,24 +149,31 @@ Just run this in your terminal (Command Prompt/PowerShell):
156
149
  pip install mc5_api_client
157
150
  ```
158
151
 
159
- That's it! 🎉 You're ready to go!
152
+ ### Step 2: Verify Installation
153
+
154
+ ```bash
155
+ # Check the CLI
156
+ mc5 --help
157
+ mc5 version
160
158
 
161
- ### Step 2: Your First Program
159
+ # Test in Python
160
+ python -c "import mc5_api_client; print('✅ MC5 API Client ready!')"
161
+ ```
162
162
 
163
- Let's write a simple Python script to connect to MC5:
163
+ ### Step 3: Your First Program
164
+
165
+ Copy and paste this simple example to get started:
164
166
 
165
167
  ```python
168
+ # Save this as my_mc5_script.py
166
169
  from mc5_api_client import MC5Client
167
170
 
168
- # Create a client and login
169
- client = MC5Client(
170
- username="YOUR_USERNAME_HERE",
171
- password="YOUR_PASSWORD_HERE"
172
- )
171
+ # Replace with your actual credentials
172
+ username = "YOUR_USERNAME_HERE"
173
+ password = "YOUR_PASSWORD_HERE"
173
174
 
174
- # Check your profile
175
- profile = client.get_profile()
176
- print(f"Hey {profile['name']}! You're level {profile['level']}")
175
+ # Create client and login
176
+ client = MC5Client(username=username, password=password)
177
177
 
178
178
  # See what's happening in the game
179
179
  events = client.get_events()
@@ -959,6 +959,8 @@ The library comes with comprehensive examples to get you started:
959
959
  - `examples/squad_wall_management.py` - Squad wall communication examples
960
960
  - `examples/private_messaging.py` - Private messaging and inbox management
961
961
  - `examples/message_management.py` - Advanced message management and bulk deletion
962
+ - `examples/advanced_features.py` - Events, account management, alias system, game config
963
+ - `examples/player_stats.py` - Player statistics, batch profiles, dogtag search
962
964
 
963
965
  ### 🚀 Advanced Examples
964
966
  - Squad management bot with automated updates
@@ -1006,6 +1008,75 @@ The library comes with comprehensive examples to get you started:
1006
1008
  - ✅ Bulk stat updates
1007
1009
  - ✅ Real-time activity monitoring
1008
1010
 
1011
+ ### � Events API (10+ Methods)
1012
+ - ✅ Get all active and upcoming events
1013
+ - ✅ Filter events by type and status
1014
+ - ✅ Get detailed event information with tasks and milestones
1015
+ - ✅ Parse event templates and extract rewards
1016
+ - ✅ Get daily tasks with progress tracking
1017
+ - ✅ Get squad events and tournament information
1018
+ - ✅ Calculate event rewards based on current points
1019
+ - ✅ Track event progress and next milestones
1020
+ - ✅ Extract task conditions and reward types
1021
+ - ✅ Support for multiple event categories
1022
+
1023
+ ### 👤 Account Management (5+ Methods)
1024
+ - ✅ Import account data from other platforms
1025
+ - ✅ Link accounts across different platforms
1026
+ - ✅ Get detailed account information
1027
+ - ✅ Unlink platform accounts
1028
+ - ✅ Migrate account data between platforms
1029
+ - ✅ Support for cross-platform data transfer
1030
+ - ✅ Account verification and validation
1031
+
1032
+ ### 📊 Player Statistics & Batch Profiles
1033
+
1034
+ Get detailed player statistics using dogtags (in-game IDs) or credentials:
1035
+
1036
+ ```python
1037
+ # Search player by their in-game dogtag
1038
+ player_stats = client.get_player_stats_by_dogtag("f55f")
1039
+ print(f"Player found: {player_stats.get('player_info', {}).get('account', 'Unknown')}")
1040
+
1041
+ # Parse and analyze statistics
1042
+ parsed = client.parse_player_stats(player_stats)
1043
+ print(f"Rating: {parsed.get('rating', 0)}")
1044
+ print(f"K/D Ratio: {parsed.get('overall_stats', {}).get('total_kills', 0) / max(parsed.get('overall_stats', {}).get('total_deaths', 1), 1):.2f}")
1045
+
1046
+ # Get detailed stats for multiple players
1047
+ credentials = ["player1_cred", "player2_cred", "player3_cred"]
1048
+ batch_stats = client.get_batch_profiles(credentials)
1049
+
1050
+ # Search players using dogtags
1051
+ for dogtag in ["f55f", "ff11", "g6765"]:
1052
+ player = client.search_player_by_dogtag(dogtag)
1053
+ if 'error' not in player:
1054
+ print(f"Found: {player.get('player_info', {}).get('account')}")
1055
+ ```
1056
+
1057
+ ### 📊 Player Statistics & Batch Profiles (6+ Methods)
1058
+ - ✅ Get batch player profiles with detailed statistics
1059
+ - ✅ Search players by dogtag (in-game ID)
1060
+ - ✅ Get detailed player statistics using credentials
1061
+ - ✅ Parse and structure player statistics
1062
+ - ✅ Convert dogtags to aliases for API lookup
1063
+ - ✅ Combine alias lookup with stats retrieval
1064
+ - ✅ Support for multiple player batch operations
1065
+ - ✅ Detailed game save and inventory data access
1066
+ - ✅ Weapon statistics and performance analysis
1067
+ - ✅ Campaign progress tracking
1068
+ - ✅ Overall statistics calculation (K/D, accuracy, etc.)
1069
+ - ✅ Player performance analysis tools
1070
+
1071
+ ### ⚙️ Game Configuration (4+ Methods)
1072
+ - ✅ Get asset hash metadata
1073
+ - ✅ Get game object catalog
1074
+ - ✅ Get service URLs for current region
1075
+ - ✅ Get comprehensive game configuration
1076
+ - ✅ Support for multiple config types
1077
+ - ✅ Asset metadata tracking
1078
+ - ✅ Service URL management
1079
+ - ✅ Game object categorization
1009
1080
  ### 💬 Complete Communication System (6+ Methods)
1010
1081
  - ✅ Send private messages with rich formatting
1011
1082
  - ✅ Include kill signatures and colors
@@ -1117,18 +1188,20 @@ Stuck on something? No worries!
1117
1188
 
1118
1189
  ## 🔗 Useful Links
1119
1190
 
1120
- This is the **most comprehensive Modern Combat 5 API library** ever created! With **70+ methods** across **10 major categories**, you can:
1191
+ This is the **most comprehensive Modern Combat 5 API library** ever created! With **95+ methods** across **14 major categories**, you can:
1121
1192
 
1122
1193
  - 🏰 **Manage entire clans** from creation to disbandment
1123
1194
  - 👥 **Control squad members** with real-time stat updates
1124
1195
  - 💬 **Complete communication system** - Private messages, squad wall, alerts
1196
+ - 📅 **Advanced events system** - Daily tasks, squad events, milestones
1197
+ - 👤 **Account management** - Import, link, migrate across platforms
1198
+ - 🏷️ **Alias/dogtags system** - Player ID conversion and search
1199
+ - ⚙️ **Game configuration** - Assets, catalogs, service URLs
1125
1200
  - 🎯 **Customize everything** with kill signatures and rich formatting
1126
1201
  - 📊 **Track performance** with detailed analytics
1127
1202
  - 🎮 **Automate gameplay** with custom bots and scripts
1128
1203
  - 🏆 **Create leaderboards** and ranking systems
1129
1204
  - 🔄 **Schedule tasks** and monitor activity
1130
- - 📱 **Manage messages** with inbox and reply systems
1131
-
1132
1205
 
1133
1206
  **Perfect for:**
1134
1207
  - 🏆 Squad leaders who want to automate management
@@ -1139,6 +1212,8 @@ This is the **most comprehensive Modern Combat 5 API library** ever created! Wit
1139
1212
  - 🏅 Competitive players seeking advantages
1140
1213
  - 💬 Community managers handling communications
1141
1214
  - 📧 Support teams providing assistance
1215
+ - 📅 Event coordinators managing tournaments
1216
+ - 🏷️ Player search and identification systems
1142
1217
 
1143
1218
  **Ready to dominate Modern Combat 5?** 🚀
1144
1219
 
@@ -1,12 +1,12 @@
1
1
  mc5_api_client/__init__.py,sha256=VtZ8P-CwnjHSq6VmSuBwVttNXIK3qoOrFuNxXMVeaMc,1021
2
2
  mc5_api_client/auth.py,sha256=Yj_6s8KmtbswWbR6q816d8soIirUF2aD_KWxg-jNqR0,9978
3
3
  mc5_api_client/cli.py,sha256=KegNTxwq28gu_vrffc_cXcALrHzUBDHd-5DqKyYp4p0,17284
4
- mc5_api_client/client.py,sha256=gJ1NSe3pJIb-9d6bgEjkcoxGgfQhuYnd462MXnk2GzI,40015
4
+ mc5_api_client/client.py,sha256=dHngiS2KgLBJ_WF2Xb0kMAOkWsLggH9jkM2Hz1W0m-w,65469
5
5
  mc5_api_client/exceptions.py,sha256=o7od4GrEIlgq6xSNUjZdh74xoDTytF3PLcMq5ewRiJw,2683
6
6
  mc5_api_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- mc5_api_client-1.0.2.dist-info/LICENSE,sha256=M0UBQ4B3pB9XcV54_jhVP681xyauF8GB6YK_rKmuXzk,1064
8
- mc5_api_client-1.0.2.dist-info/METADATA,sha256=t0sm82BnYPPWgjWg84mS9GWXWoVXiBpqFc45yYNcYS0,33982
9
- mc5_api_client-1.0.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
10
- mc5_api_client-1.0.2.dist-info/entry_points.txt,sha256=2kruOpleFYK3Jl1MoQwGyqCd-Pj4kQWngXmIjnXx_gE,48
11
- mc5_api_client-1.0.2.dist-info/top_level.txt,sha256=eYJe4ue9j1ig_jFY5Z05mDqpizUEV7TYpk5lBXVd4kA,15
12
- mc5_api_client-1.0.2.dist-info/RECORD,,
7
+ mc5_api_client-1.0.4.dist-info/LICENSE,sha256=M0UBQ4B3pB9XcV54_jhVP681xyauF8GB6YK_rKmuXzk,1064
8
+ mc5_api_client-1.0.4.dist-info/METADATA,sha256=oyW9LIWxY9Ov5aAkzRD6JcxUHgEOu0DpcYtIPvNo3vE,37125
9
+ mc5_api_client-1.0.4.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
10
+ mc5_api_client-1.0.4.dist-info/entry_points.txt,sha256=2kruOpleFYK3Jl1MoQwGyqCd-Pj4kQWngXmIjnXx_gE,48
11
+ mc5_api_client-1.0.4.dist-info/top_level.txt,sha256=eYJe4ue9j1ig_jFY5Z05mDqpizUEV7TYpk5lBXVd4kA,15
12
+ mc5_api_client-1.0.4.dist-info/RECORD,,