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.
@@ -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.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
- Get alias information.
1153
+ Retrieve player information using alias/dogtag.
1154
1154
 
1155
1155
  Args:
1156
- alias_id: Player alias ID
1156
+ alias_id: Alias or dogtag identifier
1157
1157
 
1158
1158
  Returns:
1159
- Alias information
1159
+ Player information including credential and account ID
1160
1160
  """
1161
- url = f"{self.BASE_URLS['auth']}/games/mygame/alias/{alias_id}"
1162
- return self._make_request("GET", url)
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) != 4:
1594
+ if not dogtag or len(dogtag) < 4 or len(dogtag) > 8:
1578
1595
  return dogtag
1579
1596
 
1580
- alias = ""
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 or len(alias) != 4:
1609
+ if not alias:
1607
1610
  return alias
1608
1611
 
1609
- dogtag = ""
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
- # This would typically use a dynamic URL with timestamp
1677
- # For now, using a placeholder URL
1661
+ # Use timestamp-based URL for dynamic content
1678
1662
  import time
1679
- url = f"https://iris16-gold.gameloft.com/game_object_{int(time.time())}"
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 basic catalog if dynamic URL fails
1684
- return []
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
- stats = self.get_batch_profiles([player_info['credential']])
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
- # Add dogtag and alias info to the response
1785
- if player_info['credential'] in stats:
1786
- stats[player_info['credential']]['dogtag'] = dogtag
1787
- stats[player_info['credential']]['alias'] = alias
1788
- stats[player_info['credential']]['player_info'] = player_info
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
- return stats
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 get stats for dogtag {dogtag}: {e}',
2032
+ 'error': f'Failed to kick member with dogtag {dogtag}: {e}',
1795
2033
  'dogtag': dogtag,
1796
2034
  'alias': alias,
1797
- 'player_info': player_info
2035
+ 'success': False
1798
2036
  }
1799
2037
 
1800
- def search_player_by_dogtag(self, dogtag: str) -> Dict[str, Any]:
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
- Search for a player using their dogtag and return their profile information.
2042
+ Kick a clan member using the hermes messaging system with clan kick message type.
1803
2043
 
1804
2044
  Args:
1805
- dogtag: Player's dogtag in XXXX format (hexadecimal)
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
- Complete player information including stats if found
2052
+ Dictionary with kick result
1809
2053
  """
1810
- return self.get_player_stats_by_dogtag(dogtag)
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': sp_stats.get('kill.total', 0) + mp_stats.get('kill.total', 0),
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': sp_stats.get('kill.headshots', 0) + mp_stats.get('kill.headshots', 0),
1895
- 'total_assists': sp_stats.get('kill.assists', 0) + mp_stats.get('kill.assists', 0),
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': sp_stats.get('kill.total', 0),
1898
- 'mp_kills': mp_stats.get('kill.total', 0),
1899
- 'sp_headshots': sp_stats.get('kill.headshots', 0),
1900
- 'mp_headshots': mp_stats.get('kill.headshots', 0)
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