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 +690 -0
- {mc5_api_client-1.0.2.dist-info → mc5_api_client-1.0.4.dist-info}/METADATA +97 -22
- {mc5_api_client-1.0.2.dist-info → mc5_api_client-1.0.4.dist-info}/RECORD +7 -7
- {mc5_api_client-1.0.2.dist-info → mc5_api_client-1.0.4.dist-info}/LICENSE +0 -0
- {mc5_api_client-1.0.2.dist-info → mc5_api_client-1.0.4.dist-info}/WHEEL +0 -0
- {mc5_api_client-1.0.2.dist-info → mc5_api_client-1.0.4.dist-info}/entry_points.txt +0 -0
- {mc5_api_client-1.0.2.dist-info → mc5_api_client-1.0.4.dist-info}/top_level.txt +0 -0
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.
|
|
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
|
-
|
|
152
|
+
### Step 2: Verify Installation
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Check the CLI
|
|
156
|
+
mc5 --help
|
|
157
|
+
mc5 version
|
|
160
158
|
|
|
161
|
-
|
|
159
|
+
# Test in Python
|
|
160
|
+
python -c "import mc5_api_client; print('✅ MC5 API Client ready!')"
|
|
161
|
+
```
|
|
162
162
|
|
|
163
|
-
|
|
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
|
-
#
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
#
|
|
175
|
-
|
|
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 **
|
|
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=
|
|
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.
|
|
8
|
-
mc5_api_client-1.0.
|
|
9
|
-
mc5_api_client-1.0.
|
|
10
|
-
mc5_api_client-1.0.
|
|
11
|
-
mc5_api_client-1.0.
|
|
12
|
-
mc5_api_client-1.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|