mc5-api-client 1.0.5__py3-none-any.whl → 1.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mc5_api_client/__init__.py +19 -1
- mc5_api_client/client.py +406 -79
- mc5_api_client/simple_client.py +570 -0
- {mc5_api_client-1.0.5.dist-info → mc5_api_client-1.0.8.dist-info}/METADATA +349 -41
- mc5_api_client-1.0.8.dist-info/RECORD +13 -0
- {mc5_api_client-1.0.5.dist-info → mc5_api_client-1.0.8.dist-info}/WHEEL +1 -1
- mc5_api_client-1.0.5.dist-info/RECORD +0 -12
- {mc5_api_client-1.0.5.dist-info → mc5_api_client-1.0.8.dist-info}/entry_points.txt +0 -0
- {mc5_api_client-1.0.5.dist-info → mc5_api_client-1.0.8.dist-info/licenses}/LICENSE +0 -0
- {mc5_api_client-1.0.5.dist-info → mc5_api_client-1.0.8.dist-info}/top_level.txt +0 -0
mc5_api_client/__init__.py
CHANGED
|
@@ -13,17 +13,35 @@ Provides easy access to authentication, profile management, clan operations,
|
|
|
13
13
|
messaging, and more.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
__version__ = "1.0.
|
|
16
|
+
__version__ = "1.0.8"
|
|
17
17
|
__author__ = "Chizoba"
|
|
18
18
|
__email__ = "chizoba2026@hotmail.com"
|
|
19
19
|
__license__ = "MIT"
|
|
20
20
|
|
|
21
|
+
from .simple_client import (
|
|
22
|
+
SimpleMC5Client,
|
|
23
|
+
batch_search_players,
|
|
24
|
+
clan_cleanup,
|
|
25
|
+
monitor_clan_activity,
|
|
26
|
+
quick_search,
|
|
27
|
+
quick_kick,
|
|
28
|
+
get_inactive_members,
|
|
29
|
+
auto_kick_inactive_members
|
|
30
|
+
)
|
|
21
31
|
from .client import MC5Client
|
|
22
32
|
from .auth import TokenGenerator
|
|
23
33
|
from .exceptions import MC5APIError, AuthenticationError, RateLimitError
|
|
24
34
|
|
|
25
35
|
__all__ = [
|
|
26
36
|
"MC5Client",
|
|
37
|
+
"SimpleMC5Client",
|
|
38
|
+
"batch_search_players",
|
|
39
|
+
"clan_cleanup",
|
|
40
|
+
"monitor_clan_activity",
|
|
41
|
+
"quick_search",
|
|
42
|
+
"quick_kick",
|
|
43
|
+
"get_inactive_members",
|
|
44
|
+
"auto_kick_inactive_members",
|
|
27
45
|
"TokenGenerator",
|
|
28
46
|
"MC5APIError",
|
|
29
47
|
"AuthenticationError",
|
mc5_api_client/client.py
CHANGED
|
@@ -1150,16 +1150,20 @@ class MC5Client:
|
|
|
1150
1150
|
|
|
1151
1151
|
def get_alias_info(self, alias_id: str) -> Dict[str, Any]:
|
|
1152
1152
|
"""
|
|
1153
|
-
|
|
1153
|
+
Retrieve player information using alias/dogtag.
|
|
1154
1154
|
|
|
1155
1155
|
Args:
|
|
1156
|
-
alias_id:
|
|
1156
|
+
alias_id: Alias or dogtag identifier
|
|
1157
1157
|
|
|
1158
1158
|
Returns:
|
|
1159
|
-
|
|
1159
|
+
Player information including credential and account ID
|
|
1160
1160
|
"""
|
|
1161
|
-
url = f"{self.BASE_URLS['
|
|
1162
|
-
|
|
1161
|
+
url = f"{self.BASE_URLS['janus']}/games/mygame/alias/{alias_id}"
|
|
1162
|
+
params = {
|
|
1163
|
+
"access_token": self._token_data["access_token"]
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
return self._make_request("GET", url, params=params)
|
|
1163
1167
|
|
|
1164
1168
|
def convert_dogtag_to_alias(self, dogtag: str) -> str:
|
|
1165
1169
|
"""
|
|
@@ -1564,63 +1568,48 @@ class MC5Client:
|
|
|
1564
1568
|
|
|
1565
1569
|
# Alias/Dogtags System
|
|
1566
1570
|
|
|
1571
|
+
# Dogtag conversion mappings
|
|
1572
|
+
DOGTAG_SWAP = {
|
|
1573
|
+
'0': '8', '1': '9', '2': '0', '3': '1', '4': '2', '5': '3',
|
|
1574
|
+
'6': '4', '7': '5', '8': '6', '9': '7',
|
|
1575
|
+
'a': 'y', 'b': 'z', 'c': 'a', 'd': 'b', 'e': 'c', 'f': 'd',
|
|
1576
|
+
'g': 'e', 'h': 'f', 'i': 'g', 'j': 'h', 'k': 'i', 'l': 'j',
|
|
1577
|
+
'm': 'k', 'n': 'l', 'o': 'm', 'p': 'n', 'q': 'o', 'r': 'p',
|
|
1578
|
+
's': 'q', 't': 'r', 'u': 's', 'v': 't', 'w': 'u', 'x': 'v',
|
|
1579
|
+
'y': 'w', 'z': 'x'
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
REVERSE_DOGTAG_SWAP = {v: k for k, v in DOGTAG_SWAP.items()}
|
|
1583
|
+
|
|
1567
1584
|
def convert_dogtag_to_alias(self, dogtag: str) -> str:
|
|
1568
1585
|
"""
|
|
1569
|
-
Convert a dogtag (player ID) to alias format.
|
|
1586
|
+
Convert a dogtag (player ID) to alias format using the correct MC5 mapping.
|
|
1570
1587
|
|
|
1571
1588
|
Args:
|
|
1572
|
-
dogtag: Dogtag in XXXX format (hexadecimal)
|
|
1589
|
+
dogtag: Dogtag in XXXX format (hexadecimal), can be 4-8 characters
|
|
1573
1590
|
|
|
1574
1591
|
Returns:
|
|
1575
1592
|
Alias string
|
|
1576
1593
|
"""
|
|
1577
|
-
if not dogtag or len(dogtag)
|
|
1594
|
+
if not dogtag or len(dogtag) < 4 or len(dogtag) > 8:
|
|
1578
1595
|
return dogtag
|
|
1579
1596
|
|
|
1580
|
-
|
|
1581
|
-
for char in dogtag.lower():
|
|
1582
|
-
if char.isdigit():
|
|
1583
|
-
# Digit: subtract 2 using modulo 10 arithmetic
|
|
1584
|
-
digit = int(char)
|
|
1585
|
-
new_digit = (digit - 2) % 10
|
|
1586
|
-
alias += str(new_digit)
|
|
1587
|
-
elif char.isalpha():
|
|
1588
|
-
# Letter: subtract 2 from ASCII value
|
|
1589
|
-
new_char = chr(ord(char) - 2)
|
|
1590
|
-
alias += new_char
|
|
1591
|
-
else:
|
|
1592
|
-
alias += char
|
|
1593
|
-
|
|
1594
|
-
return alias
|
|
1597
|
+
return ''.join(self.DOGTAG_SWAP.get(c, c) for c in dogtag.lower())
|
|
1595
1598
|
|
|
1596
1599
|
def convert_alias_to_dogtag(self, alias: str) -> str:
|
|
1597
1600
|
"""
|
|
1598
|
-
Convert an alias back to dogtag format.
|
|
1601
|
+
Convert an alias back to dogtag format using the correct MC5 mapping.
|
|
1599
1602
|
|
|
1600
1603
|
Args:
|
|
1601
1604
|
alias: Alias string
|
|
1602
1605
|
|
|
1603
1606
|
Returns:
|
|
1604
|
-
Dogtag in XXXX format
|
|
1607
|
+
Dogtag in XXXX format (hexadecimal)
|
|
1605
1608
|
"""
|
|
1606
|
-
if not alias
|
|
1609
|
+
if not alias:
|
|
1607
1610
|
return alias
|
|
1608
1611
|
|
|
1609
|
-
|
|
1610
|
-
for char in alias.lower():
|
|
1611
|
-
if char.isdigit():
|
|
1612
|
-
# Digit: add 2 using modulo 10 arithmetic
|
|
1613
|
-
digit = int(char)
|
|
1614
|
-
new_digit = (digit + 2) % 10
|
|
1615
|
-
dogtag += str(new_digit)
|
|
1616
|
-
elif char.isalpha():
|
|
1617
|
-
# Letter: add 2 to ASCII value
|
|
1618
|
-
new_char = chr(ord(char) + 2)
|
|
1619
|
-
dogtag += new_char
|
|
1620
|
-
else:
|
|
1621
|
-
dogtag += char
|
|
1622
|
-
|
|
1623
|
-
return dogtag
|
|
1612
|
+
return ''.join(self.REVERSE_DOGTAG_SWAP.get(c, c) for c in alias.lower())
|
|
1624
1613
|
|
|
1625
1614
|
def get_alias_info(self, alias_id: str) -> Dict[str, Any]:
|
|
1626
1615
|
"""
|
|
@@ -1632,10 +1621,6 @@ class MC5Client:
|
|
|
1632
1621
|
Returns:
|
|
1633
1622
|
Player account information
|
|
1634
1623
|
"""
|
|
1635
|
-
# Convert to alias if it's a dogtag
|
|
1636
|
-
if len(alias_id) == 4 and all(c in '0123456789abcdefABCDEF' for c in alias_id):
|
|
1637
|
-
alias_id = self.convert_dogtag_to_alias(alias_id)
|
|
1638
|
-
|
|
1639
1624
|
url = f"{self.BASE_URLS['janus']}/games/mygame/alias/{alias_id}"
|
|
1640
1625
|
return self._make_request("GET", url)
|
|
1641
1626
|
|
|
@@ -1673,15 +1658,21 @@ class MC5Client:
|
|
|
1673
1658
|
Returns:
|
|
1674
1659
|
List of game objects with detailed information
|
|
1675
1660
|
"""
|
|
1676
|
-
#
|
|
1677
|
-
# For now, using a placeholder URL
|
|
1661
|
+
# Use timestamp-based URL for dynamic content
|
|
1678
1662
|
import time
|
|
1679
|
-
|
|
1663
|
+
timestamp = int(time.time())
|
|
1664
|
+
url = f"https://iris16-gold.gameloft.com/1875/game_object_{timestamp}"
|
|
1665
|
+
|
|
1680
1666
|
try:
|
|
1681
1667
|
return self._make_request("GET", url)
|
|
1682
1668
|
except:
|
|
1683
|
-
# Fallback to
|
|
1684
|
-
|
|
1669
|
+
# Fallback to static URL if dynamic fails
|
|
1670
|
+
fallback_url = "https://iris16-gold.gameloft.com/1875/game_object_1747885487.1257186"
|
|
1671
|
+
try:
|
|
1672
|
+
return self._make_request("GET", fallback_url)
|
|
1673
|
+
except:
|
|
1674
|
+
# Return empty catalog if both fail
|
|
1675
|
+
return []
|
|
1685
1676
|
|
|
1686
1677
|
def get_service_urls(self) -> Dict[str, str]:
|
|
1687
1678
|
"""
|
|
@@ -1759,12 +1750,27 @@ class MC5Client:
|
|
|
1759
1750
|
|
|
1760
1751
|
return self._make_request("POST", url, data=data, headers=headers)
|
|
1761
1752
|
|
|
1753
|
+
def get_complete_player_stats(self, credentials: List[str]) -> Dict[str, Any]:
|
|
1754
|
+
"""
|
|
1755
|
+
Get complete player statistics with all available fields.
|
|
1756
|
+
|
|
1757
|
+
Args:
|
|
1758
|
+
credentials: List of player credentials to fetch
|
|
1759
|
+
|
|
1760
|
+
Returns:
|
|
1761
|
+
Dictionary with complete player profiles
|
|
1762
|
+
"""
|
|
1763
|
+
# Try to get all possible fields
|
|
1764
|
+
all_fields = ['_game_save', 'inventory', 'statistics', 'profile', 'achievements']
|
|
1765
|
+
return self.get_batch_profiles(credentials, all_fields)
|
|
1766
|
+
|
|
1762
1767
|
def get_player_stats_by_dogtag(self, dogtag: str) -> Dict[str, Any]:
|
|
1763
1768
|
"""
|
|
1764
1769
|
Get detailed player statistics using their dogtag (in-game ID).
|
|
1770
|
+
Follows the exact API flow: dogtag → alias → janus lookup → batch profiles.
|
|
1765
1771
|
|
|
1766
1772
|
Args:
|
|
1767
|
-
dogtag: Player's dogtag in XXXX format (hexadecimal)
|
|
1773
|
+
dogtag: Player's dogtag in XXXX format (hexadecimal), can be 4-8 characters
|
|
1768
1774
|
|
|
1769
1775
|
Returns:
|
|
1770
1776
|
Player statistics and profile information
|
|
@@ -1772,42 +1778,300 @@ class MC5Client:
|
|
|
1772
1778
|
# Convert dogtag to alias for API lookup
|
|
1773
1779
|
alias = self.convert_dogtag_to_alias(dogtag)
|
|
1774
1780
|
|
|
1775
|
-
# Get player info using alias
|
|
1776
|
-
player_info = self.get_alias_info(alias)
|
|
1777
|
-
if not player_info.get('credential'):
|
|
1778
|
-
return {'error': f'Player not found for dogtag: {dogtag}', 'alias': alias}
|
|
1779
|
-
|
|
1780
|
-
# Get detailed stats using the credential
|
|
1781
|
+
# Get player info using alias from janus endpoint
|
|
1781
1782
|
try:
|
|
1782
|
-
|
|
1783
|
+
player_info = self.get_alias_info(alias)
|
|
1784
|
+
if not player_info.get('credential'):
|
|
1785
|
+
return {
|
|
1786
|
+
'error': f'Player not found for dogtag: {dogtag}',
|
|
1787
|
+
'dogtag': dogtag,
|
|
1788
|
+
'alias': alias,
|
|
1789
|
+
'janus_response': player_info
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
# Get detailed stats using the credential from batch profiles endpoint
|
|
1793
|
+
try:
|
|
1794
|
+
stats = self.get_batch_profiles([player_info['credential']])
|
|
1795
|
+
|
|
1796
|
+
# Add dogtag and alias info to the response
|
|
1797
|
+
if player_info['credential'] in stats:
|
|
1798
|
+
stats[player_info['credential']]['dogtag'] = dogtag
|
|
1799
|
+
stats[player_info['credential']]['alias'] = alias
|
|
1800
|
+
stats[player_info['credential']]['player_info'] = player_info
|
|
1801
|
+
stats[player_info['credential']]['janus_response'] = player_info
|
|
1802
|
+
|
|
1803
|
+
return stats
|
|
1804
|
+
|
|
1805
|
+
except Exception as e:
|
|
1806
|
+
return {
|
|
1807
|
+
'error': f'Failed to get stats for dogtag {dogtag}: {e}',
|
|
1808
|
+
'dogtag': dogtag,
|
|
1809
|
+
'alias': alias,
|
|
1810
|
+
'player_info': player_info,
|
|
1811
|
+
'janus_response': player_info
|
|
1812
|
+
}
|
|
1813
|
+
except Exception as e:
|
|
1814
|
+
return {
|
|
1815
|
+
'error': f'Failed to lookup alias {alias} for dogtag {dogtag}: {e}',
|
|
1816
|
+
'dogtag': dogtag,
|
|
1817
|
+
'alias': alias
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
# Clan Management & Profile Operations
|
|
1821
|
+
|
|
1822
|
+
def get_profile(self) -> Dict[str, Any]:
|
|
1823
|
+
"""
|
|
1824
|
+
Retrieve player profile details including name, groups, and last time played.
|
|
1825
|
+
|
|
1826
|
+
Returns:
|
|
1827
|
+
Player profile information
|
|
1828
|
+
"""
|
|
1829
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me"
|
|
1830
|
+
params = {
|
|
1831
|
+
"access_token": self._token_data["access_token"]
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
return self._make_request("GET", url, params=params)
|
|
1835
|
+
|
|
1836
|
+
def update_profile(self, name: str = None, groups: str = None, games: str = None,
|
|
1837
|
+
credential: str = None, fed_id: str = None) -> Dict[str, Any]:
|
|
1838
|
+
"""
|
|
1839
|
+
Update player profile (name, groups, games data).
|
|
1840
|
+
|
|
1841
|
+
Args:
|
|
1842
|
+
name: Player nickname
|
|
1843
|
+
groups: Comma-separated clan IDs
|
|
1844
|
+
games: JSON string with game data
|
|
1845
|
+
credential: Player credential
|
|
1846
|
+
fed_id: Federated ID
|
|
1847
|
+
|
|
1848
|
+
Returns:
|
|
1849
|
+
Update result
|
|
1850
|
+
"""
|
|
1851
|
+
url = f"{self.BASE_URLS['osiris']}/accounts/me"
|
|
1852
|
+
|
|
1853
|
+
data = {
|
|
1854
|
+
"access_token": self._token_data["access_token"],
|
|
1855
|
+
"timestamp": str(int(time.time()))
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
if name is not None:
|
|
1859
|
+
data["name"] = name
|
|
1860
|
+
if groups is not None:
|
|
1861
|
+
data["groups"] = groups
|
|
1862
|
+
if games is not None:
|
|
1863
|
+
data["games"] = games
|
|
1864
|
+
if credential is not None:
|
|
1865
|
+
data["credential"] = credential
|
|
1866
|
+
if fed_id is not None:
|
|
1867
|
+
data["fed_id"] = fed_id
|
|
1868
|
+
|
|
1869
|
+
return self._make_request("POST", url, data=data)
|
|
1870
|
+
|
|
1871
|
+
def get_clan_members(self, clan_id: str, offset: int = 0) -> List[Dict[str, Any]]:
|
|
1872
|
+
"""
|
|
1873
|
+
Get all members of a clan.
|
|
1874
|
+
|
|
1875
|
+
Args:
|
|
1876
|
+
clan_id: The unique identifier of the clan
|
|
1877
|
+
offset: Number of results to skip for pagination
|
|
1878
|
+
|
|
1879
|
+
Returns:
|
|
1880
|
+
List of clan members with their details
|
|
1881
|
+
"""
|
|
1882
|
+
url = f"{self.BASE_URLS['osiris']}/groups/{clan_id}/members"
|
|
1883
|
+
params = {
|
|
1884
|
+
"access_token": self._token_data["access_token"],
|
|
1885
|
+
"group_id": clan_id,
|
|
1886
|
+
"offset": str(offset)
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
response = self._make_request("GET", url, params=params)
|
|
1890
|
+
return response if isinstance(response, list) else []
|
|
1891
|
+
|
|
1892
|
+
def get_clan_info(self, clan_id: str) -> Dict[str, Any]:
|
|
1893
|
+
"""
|
|
1894
|
+
Get detailed information about a clan.
|
|
1895
|
+
|
|
1896
|
+
Args:
|
|
1897
|
+
clan_id: The unique identifier of the clan
|
|
1898
|
+
|
|
1899
|
+
Returns:
|
|
1900
|
+
Clan information including settings
|
|
1901
|
+
"""
|
|
1902
|
+
url = f"{self.BASE_URLS['osiris']}/groups/{clan_id}"
|
|
1903
|
+
params = {
|
|
1904
|
+
"access_token": self._token_data["access_token"]
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
return self._make_request("GET", url, params=params)
|
|
1908
|
+
|
|
1909
|
+
def update_clan_settings(self, clan_id: str, name: str = None, description: str = None,
|
|
1910
|
+
rating: str = None, score: str = None, member_limit: str = None,
|
|
1911
|
+
membership: str = None, logo: str = None, logo_primary_color: str = None,
|
|
1912
|
+
logo_secondary_color: str = None, min_join_value: str = None,
|
|
1913
|
+
xp: str = None, currency: str = None, killsig_id: str = None,
|
|
1914
|
+
active_clan_label: str = None, active_member_count: str = None,
|
|
1915
|
+
active_clan_threshold: str = None) -> Dict[str, Any]:
|
|
1916
|
+
"""
|
|
1917
|
+
Update clan settings and information.
|
|
1918
|
+
|
|
1919
|
+
Args:
|
|
1920
|
+
clan_id: The unique identifier of the clan
|
|
1921
|
+
name: Clan name
|
|
1922
|
+
description: Clan description
|
|
1923
|
+
rating: Clan rating
|
|
1924
|
+
score: Clan score
|
|
1925
|
+
member_limit: Maximum number of members
|
|
1926
|
+
membership: Membership type (open, owner_approved, member_approved, closed)
|
|
1927
|
+
logo: Logo ID
|
|
1928
|
+
logo_primary_color: Primary logo color (0-16777215)
|
|
1929
|
+
logo_secondary_color: Secondary logo color (0-16777215)
|
|
1930
|
+
min_join_value: Minimum join value
|
|
1931
|
+
xp: Clan XP (max 10000)
|
|
1932
|
+
currency: Clan currency
|
|
1933
|
+
killsig_id: Kill sign ID
|
|
1934
|
+
active_clan_label: Whether clan label is active (true/false)
|
|
1935
|
+
active_member_count: Active member count
|
|
1936
|
+
active_clan_threshold: Active member threshold
|
|
1937
|
+
|
|
1938
|
+
Returns:
|
|
1939
|
+
Update result
|
|
1940
|
+
"""
|
|
1941
|
+
url = f"{self.BASE_URLS['osiris']}/groups/{clan_id}"
|
|
1942
|
+
|
|
1943
|
+
# Get current profile for required fields
|
|
1944
|
+
profile = self.get_profile()
|
|
1945
|
+
|
|
1946
|
+
data = {
|
|
1947
|
+
"access_token": self._token_data["access_token"],
|
|
1948
|
+
"_anonId": profile.get("credential", ""),
|
|
1949
|
+
"_fedId": profile.get("fed_id", ""),
|
|
1950
|
+
"timestamp": str(int(time.time()))
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
# Add optional parameters
|
|
1954
|
+
if name is not None:
|
|
1955
|
+
data["name"] = name
|
|
1956
|
+
if description is not None:
|
|
1957
|
+
data["description"] = description
|
|
1958
|
+
if rating is not None:
|
|
1959
|
+
data["_rating"] = rating
|
|
1960
|
+
if score is not None:
|
|
1961
|
+
data["score"] = score
|
|
1962
|
+
if member_limit is not None:
|
|
1963
|
+
data["member_limit"] = member_limit
|
|
1964
|
+
if membership is not None:
|
|
1965
|
+
data["membership"] = membership
|
|
1966
|
+
if logo is not None:
|
|
1967
|
+
data["_logo"] = logo
|
|
1968
|
+
if logo_primary_color is not None:
|
|
1969
|
+
data["_logo_clr_prim"] = logo_primary_color
|
|
1970
|
+
if logo_secondary_color is not None:
|
|
1971
|
+
data["_logo_clr_sec"] = logo_secondary_color
|
|
1972
|
+
if min_join_value is not None:
|
|
1973
|
+
data["_min_join_value"] = min_join_value
|
|
1974
|
+
if xp is not None:
|
|
1975
|
+
data["_xp"] = xp
|
|
1976
|
+
if currency is not None:
|
|
1977
|
+
data["currency"] = currency
|
|
1978
|
+
if killsig_id is not None:
|
|
1979
|
+
data["_killsig_id"] = killsig_id
|
|
1980
|
+
if active_clan_label is not None:
|
|
1981
|
+
data["active_clan_label"] = active_clan_label
|
|
1982
|
+
if active_member_count is not None:
|
|
1983
|
+
data["active_member_count"] = active_member_count
|
|
1984
|
+
if active_clan_threshold is not None:
|
|
1985
|
+
data["active_clan_threshold"] = active_clan_threshold
|
|
1986
|
+
|
|
1987
|
+
return self._make_request("POST", url, data=data)
|
|
1988
|
+
|
|
1989
|
+
def kick_clan_member_by_dogtag(self, dogtag: str, clan_id: str, from_name: str = "SYSTEM",
|
|
1990
|
+
kill_sign_name: str = "default_killsig_42",
|
|
1991
|
+
kill_sign_color: str = "-974646126") -> Dict[str, Any]:
|
|
1992
|
+
"""
|
|
1993
|
+
Kick a clan member using their dogtag (in-game ID).
|
|
1994
|
+
|
|
1995
|
+
This method follows the flow: dogtag → alias → janus lookup → credential → kick
|
|
1996
|
+
|
|
1997
|
+
Args:
|
|
1998
|
+
dogtag: Player's dogtag in XXXX format (4-8 characters)
|
|
1999
|
+
clan_id: Clan ID to kick the member from
|
|
2000
|
+
from_name: Sender name (default: "SYSTEM")
|
|
2001
|
+
kill_sign_name: Kill sign name (default: "default_killsig_42")
|
|
2002
|
+
kill_sign_color: Kill sign color (default: "-974646126")
|
|
1783
2003
|
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2004
|
+
Returns:
|
|
2005
|
+
Dictionary with kick result
|
|
2006
|
+
"""
|
|
2007
|
+
# Convert dogtag to alias for API lookup
|
|
2008
|
+
alias = self.convert_dogtag_to_alias(dogtag)
|
|
2009
|
+
|
|
2010
|
+
# Get player info using alias from janus endpoint
|
|
2011
|
+
try:
|
|
2012
|
+
player_info = self.get_alias_info(alias)
|
|
2013
|
+
if not player_info.get('credential'):
|
|
2014
|
+
return {
|
|
2015
|
+
'error': f'Player not found for dogtag: {dogtag}',
|
|
2016
|
+
'dogtag': dogtag,
|
|
2017
|
+
'alias': alias,
|
|
2018
|
+
'success': False
|
|
2019
|
+
}
|
|
1789
2020
|
|
|
1790
|
-
|
|
2021
|
+
# Use the credential to kick the member
|
|
2022
|
+
return self.kick_clan_member(
|
|
2023
|
+
target_fed_id=player_info['credential'],
|
|
2024
|
+
clan_id=clan_id,
|
|
2025
|
+
from_name=from_name,
|
|
2026
|
+
kill_sign_name=kill_sign_name,
|
|
2027
|
+
kill_sign_color=kill_sign_color
|
|
2028
|
+
)
|
|
1791
2029
|
|
|
1792
2030
|
except Exception as e:
|
|
1793
2031
|
return {
|
|
1794
|
-
'error': f'Failed to
|
|
2032
|
+
'error': f'Failed to kick member with dogtag {dogtag}: {e}',
|
|
1795
2033
|
'dogtag': dogtag,
|
|
1796
2034
|
'alias': alias,
|
|
1797
|
-
'
|
|
2035
|
+
'success': False
|
|
1798
2036
|
}
|
|
1799
2037
|
|
|
1800
|
-
def
|
|
2038
|
+
def kick_clan_member(self, target_fed_id: str, clan_id: str, from_name: str = "SYSTEM",
|
|
2039
|
+
kill_sign_name: str = "default_killsig_42",
|
|
2040
|
+
kill_sign_color: str = "-974646126") -> Dict[str, Any]:
|
|
1801
2041
|
"""
|
|
1802
|
-
|
|
2042
|
+
Kick a clan member using the hermes messaging system with clan kick message type.
|
|
1803
2043
|
|
|
1804
2044
|
Args:
|
|
1805
|
-
|
|
2045
|
+
target_fed_id: Target player's fed_id or credential
|
|
2046
|
+
clan_id: Clan ID to kick the member from
|
|
2047
|
+
from_name: Sender name (default: "SYSTEM")
|
|
2048
|
+
kill_sign_name: Kill sign name (default: "default_killsig_42")
|
|
2049
|
+
kill_sign_color: Kill sign color (default: "-974646126")
|
|
1806
2050
|
|
|
1807
2051
|
Returns:
|
|
1808
|
-
|
|
2052
|
+
Dictionary with kick result
|
|
1809
2053
|
"""
|
|
1810
|
-
|
|
2054
|
+
url = f"{self.BASE_URLS['hermes']}/messages/inbox/{target_fed_id}"
|
|
2055
|
+
|
|
2056
|
+
headers = {
|
|
2057
|
+
'Accept': '*/*',
|
|
2058
|
+
'User-Agent': 'ChatLibv2',
|
|
2059
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
2060
|
+
'Connection': 'keep-alive'
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
data = {
|
|
2064
|
+
'access_token': self._token_data['access_token'],
|
|
2065
|
+
'alert_kairos': 'true',
|
|
2066
|
+
'from': from_name,
|
|
2067
|
+
'body': clan_id,
|
|
2068
|
+
'reply_to': 'game:mc5_system',
|
|
2069
|
+
'_killSignColor': kill_sign_color,
|
|
2070
|
+
'_killSignName': kill_sign_name,
|
|
2071
|
+
'_type': 'clankick'
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
return self._make_request("POST", url, data=data, headers=headers)
|
|
1811
2075
|
|
|
1812
2076
|
def get_player_detailed_stats(self, credential: str) -> Dict[str, Any]:
|
|
1813
2077
|
"""
|
|
@@ -1882,23 +2146,86 @@ class MC5Client:
|
|
|
1882
2146
|
weapon_stats.sort(key=lambda x: x['kills'], reverse=True)
|
|
1883
2147
|
parsed_stats['weapon_stats'] = weapon_stats
|
|
1884
2148
|
|
|
1885
|
-
# Parse overall statistics
|
|
2149
|
+
# Parse overall statistics with detailed breakdown
|
|
1886
2150
|
stats = game_save.get('statistics', {})
|
|
1887
2151
|
if stats:
|
|
1888
2152
|
sp_stats = stats.get('sp', {})
|
|
1889
2153
|
mp_stats = stats.get('mp', {})
|
|
1890
2154
|
|
|
2155
|
+
# Calculate comprehensive kill statistics
|
|
2156
|
+
sp_total_kills = sp_stats.get('kill.total', 0)
|
|
2157
|
+
mp_total_kills = mp_stats.get('kill.total', 0)
|
|
2158
|
+
sp_headshots = sp_stats.get('kill.headshots', 0)
|
|
2159
|
+
mp_headshots = mp_stats.get('kill.headshots', 0)
|
|
2160
|
+
sp_assists = sp_stats.get('kill.assists', 0)
|
|
2161
|
+
mp_assists = mp_stats.get('kill.assists', 0)
|
|
2162
|
+
|
|
2163
|
+
# Parse detailed kill types
|
|
2164
|
+
sp_kill_types = {
|
|
2165
|
+
'rifle': sp_stats.get('kill.rifle', 0),
|
|
2166
|
+
'pistol': sp_stats.get('kill.pistol', 0),
|
|
2167
|
+
'explosives': sp_stats.get('kill.explosives', 0),
|
|
2168
|
+
'melees': sp_stats.get('kill.melees', 0),
|
|
2169
|
+
'primsecweapon': sp_stats.get('kill.primsecweapon', 0),
|
|
2170
|
+
'assaults': sp_stats.get('kill.assaults', 0),
|
|
2171
|
+
'recon': sp_stats.get('kill.recon', 0)
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
mp_kill_types = {
|
|
2175
|
+
'rifle': mp_stats.get('kill.rifle', 0),
|
|
2176
|
+
'pistol': mp_stats.get('kill.pistol', 0),
|
|
2177
|
+
'explosives': mp_stats.get('kill.explosives', 0),
|
|
2178
|
+
'primsecweapon': mp_stats.get('kill.primsecweapon', 0),
|
|
2179
|
+
'assaults': mp_stats.get('kill.assaults', 0),
|
|
2180
|
+
'with_collected_weapons': mp_stats.get('kill.with_collected_weapons', 0)
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
# Parse killstreak stats
|
|
2184
|
+
sp_killstreaks = {
|
|
2185
|
+
'total': sp_stats.get('killstreak.total', 0),
|
|
2186
|
+
'assault_total': sp_stats.get('killstreak.assault.total', 0),
|
|
2187
|
+
'recon_total': sp_stats.get('killstreak.recon.total', 0)
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
mp_killstreaks = {
|
|
2191
|
+
'total': mp_stats.get('killstreak.total', 0),
|
|
2192
|
+
'assault_total': mp_stats.get('killstreak.assault.total', 0),
|
|
2193
|
+
'recon_total': mp_stats.get('killstreak.recon.total', 0)
|
|
2194
|
+
}
|
|
2195
|
+
|
|
1891
2196
|
parsed_stats['overall_stats'] = {
|
|
1892
|
-
'total_kills':
|
|
2197
|
+
'total_kills': sp_total_kills + mp_total_kills,
|
|
1893
2198
|
'total_deaths': sp_stats.get('death.total', 0) + mp_stats.get('death.total', 0),
|
|
1894
|
-
'total_headshots':
|
|
1895
|
-
'total_assists':
|
|
2199
|
+
'total_headshots': sp_headshots + mp_headshots,
|
|
2200
|
+
'total_assists': sp_assists + mp_assists,
|
|
1896
2201
|
'total_time_played': sp_stats.get('time.played', 0) + mp_stats.get('time.played', 0),
|
|
1897
|
-
'sp_kills':
|
|
1898
|
-
'mp_kills':
|
|
1899
|
-
'sp_headshots':
|
|
1900
|
-
'mp_headshots':
|
|
2202
|
+
'sp_kills': sp_total_kills,
|
|
2203
|
+
'mp_kills': mp_total_kills,
|
|
2204
|
+
'sp_headshots': sp_headshots,
|
|
2205
|
+
'mp_headshots': mp_headshots,
|
|
2206
|
+
'sp_assists': sp_assists,
|
|
2207
|
+
'mp_assists': mp_assists,
|
|
2208
|
+
'sp_kill_types': sp_kill_types,
|
|
2209
|
+
'mp_kill_types': mp_kill_types,
|
|
2210
|
+
'sp_killstreaks': sp_killstreaks,
|
|
2211
|
+
'mp_killstreaks': mp_killstreaks,
|
|
2212
|
+
'weapon_kills': sum(weapon_stats[i]['kills'] for i in range(len(weapon_stats))),
|
|
2213
|
+
'top_weapon': weapon_stats[0] if weapon_stats else None
|
|
1901
2214
|
}
|
|
2215
|
+
|
|
2216
|
+
# Calculate K/D ratios
|
|
2217
|
+
total_deaths = sp_stats.get('death.total', 0) + mp_stats.get('death.total', 0)
|
|
2218
|
+
if total_deaths > 0:
|
|
2219
|
+
parsed_stats['overall_stats']['kd_ratio'] = (sp_total_kills + mp_total_kills) / total_deaths
|
|
2220
|
+
else:
|
|
2221
|
+
parsed_stats['overall_stats']['kd_ratio'] = float('inf')
|
|
2222
|
+
|
|
2223
|
+
# Calculate headshot percentage
|
|
2224
|
+
total_kills = sp_total_kills + mp_total_kills
|
|
2225
|
+
if total_kills > 0:
|
|
2226
|
+
parsed_stats['overall_stats']['headshot_percentage'] = ((sp_headshots + mp_headshots) / total_kills) * 100
|
|
2227
|
+
else:
|
|
2228
|
+
parsed_stats['overall_stats']['headshot_percentage'] = 0
|
|
1902
2229
|
|
|
1903
2230
|
return parsed_stats
|
|
1904
2231
|
|