diff --git a/README.md b/README.md index effc407..591f145 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ If done correctly, this should return the extra API information. ## Command Line Arguments ``` -usage: get_cod_stats.py [-h] -p PLAYER_NAME [-a] [-sl] [-i] [-m] [-c] [-sm] [-csd] [-cmd] +usage: get_cod_stats.py [-h] [-p PLAYER_NAME] [-a] [-sl] [-id] [-m] [-i] [-f] [-e] [-cp] [-ca] [-s] [-c] [-sm] [-csd] [-cmd] [-cff] [-cef] Detailed Modern Warfare (2019) Statistics Tool @@ -79,8 +79,15 @@ Data Fetching Options: Player's username (with #1234567) -a, --all_stats Fetch all the different types of stats data -sl, --season_loot Fetch only the season loot data - -i, --identities Fetch only the logged-in identities data + -id, --identities Fetch only the logged-in identities data -m, --maps Fetch only the map list data + -i, --info Fetch only general information + -f, --friendFeed Fetch only your friend feed + -e, --eventFeed Fetch only your event feed + -cp, --cod_points Fetch only your COD Point balance + -ca, --connected_accounts + Fetch only the map list data + -s, --settings Fetch only your account settings Data Cleaning Options: -c, --clean Beautify all data @@ -89,6 +96,10 @@ Data Cleaning Options: Beautify the data and convert to human-readable strings in stats.json -cmd, --clean_match_data Beautify the match data and convert to human-readable strings in match_info.json + -cff, --clean_friend_feed + Clean the friend feed data + -cef, --clean_event_feed + Clean the event feed data ``` ## Command Examples diff --git a/bin/get_cod_stats.exe b/bin/get_cod_stats.exe index 7a18134..9482498 100644 Binary files a/bin/get_cod_stats.exe and b/bin/get_cod_stats.exe differ diff --git a/clean_friendFeed_regex.txt b/clean_friendFeed_regex.txt deleted file mode 100644 index 3734f4f..0000000 --- a/clean_friendFeed_regex.txt +++ /dev/null @@ -1 +0,0 @@ -<span class="|</span>|">|mp-stat-items|kills-value|headshots-value|username|game-mode|kdr-value \ No newline at end of file diff --git a/cod_api/__init__.py b/cod_api/__init__.py index f2098f6..01e631f 100644 --- a/cod_api/__init__.py +++ b/cod_api/__init__.py @@ -547,30 +547,28 @@ class API: class __USER(_Common): def info(self): if self.loggedIn: - # Assuming 'user_info.json' is the file you've downloaded with the information. + # Assuming 'user_info.json' is the file you've downloaded with the information file_path = 'userInfo.json' - # Load the JSON content from the local file. + # Load the JSON content from the local file with open(file_path, 'r') as file: rawData = json.load(file) - try: - userInfo = rawData['userInfo'] # Accessing the nested 'userInfo' dictionary. - identities = rawData.get('identities', []) # Accessing the 'identities' if it exists or default to empty list. + userInfo = rawData['userInfo'] # Accessing the nested 'userInfo' dictionary + identities = rawData.get('identities', []) # Accessing the 'identities' if it exists or default to empty list - data = {'userName': userInfo['userName'], 'identities': []} # Getting 'userName' from the nested dictionary. - for i in identities: # Loop through each identity in the 'identities' list. + data = {'userName': userInfo['userName'], 'identities': []} # Getting 'userName' from the nested dictionary + for i in identities: # Loop through each identity in the 'identities' list data['identities'].append({ 'platform': i['provider'], 'gamertag': i['username'], - 'accountID': i['accountID'] # Assuming 'accountID' exists; otherwise, you might need a default value. + 'accountID': i['accountID'] # Assuming 'accountID' exists; otherwise, you might need a default value }) - return data except KeyError as e: - # Handle the case where the expected key is not found in the dictionary. + # Handle the case where the expected key is not found in the dictionary print(f"Error: A required field is missing in the data. Details: {str(e)}") - # Re-raise the exception or handle it as required for your application's logic. + # Re-raise the exception or handle it as required raise else: diff --git a/cod_api/__init__dev.py b/cod_api/__init__dev.py new file mode 100644 index 0000000..1d5c586 --- /dev/null +++ b/cod_api/__init__dev.py @@ -0,0 +1,725 @@ +__version__ = "2.0.1" + +# Imports +import asyncio +import enum +import json +import uuid +import os +from abc import abstractmethod +from datetime import datetime +from urllib.parse import quote + +import aiohttp +import requests +from aiohttp import ClientResponseError + + +# Enums + +class platforms(enum.Enum): + All = 'all' + Activision = 'acti' + Battlenet = 'battle' + PSN = 'psn' + Steam = 'steam' + Uno = 'uno' + XBOX = 'xbl' + + +class games(enum.Enum): + ColdWar = 'cw' + ModernWarfare = 'mw' + ModernWarfare2 = 'mw2' + Vanguard = 'vg' + Warzone = 'wz' + Warzone2 = 'wz2' + + +class friendActions(enum.Enum): + Invite = "invite" + Uninvite = "uninvite" + Remove = "remove" + Block = "block" + Unblock = "unblock" + + +class API: + """ + Call Of Duty API Wrapper + + Developed by Todo Lodo & Engineer152 + + Contributors + - Werseter + + Source Code: https://github.com/TodoLodo/cod-python-api + """ + def __init__(self): + # sub classes + self.Warzone = self.__WZ() + self.ModernWarfare = self.__MW() + self.Warzone2 = self.__WZ2() + self.ModernWarfare2 = self.__MW2() + self.ColdWar = self.__CW() + self.Vanguard = self.__VG() + self.Shop = self.__SHOP() + self.Me = self.__USER() + self.Misc = self.__ALT() + + async def loginAsync(self, sso_token: str) -> None: + await API._Common.loginAsync(sso_token) + + # Login + def login(self, ssoToken: str): + API._Common.login(ssoToken) + + class _Common: + requestHeaders = { + "content-type": "application/json", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/74.0.3729.169 " + "Safari/537.36", + "Accept": "application/json", + "Connection": "Keep-Alive" + } + cookies = {"new_SiteId": "cod", "ACT_SSO_LOCALE": "en_US", "country": "US", + "ACT_SSO_COOKIE_EXPIRY": "1645556143194"} + cachedMappings = None + + fakeXSRF = str(uuid.uuid4()) + baseUrl: str = "https://my.callofduty.com/api/papi-client" + loggedIn: bool = False + + # endPoints + + # game platform lookupType gamertag type + fullDataUrl = "/stats/cod/v1/title/%s/platform/%s/%s/%s/profile/type/%s" + # game platform lookupType gamertag type start end [?limit=n or ''] + combatHistoryUrl = "/crm/cod/v2/title/%s/platform/%s/%s/%s/matches/%s/start/%d/end/%d/details" + # game platform lookupType gamertag type start end + breakdownUrl = "/crm/cod/v2/title/%s/platform/%s/%s/%s/matches/%s/start/%d/end/%d" + # game platform lookupType gamertag + seasonLootUrl = "/loot/title/%s/platform/%s/%s/%s/status/en" + # game platform + mapListUrl = "/ce/v1/title/%s/platform/%s/gameType/mp/communityMapData/availability" + # game platform type matchId + matchInfoUrl = "/crm/cod/v2/title/%s/platform/%s/fullMatch/%s/%d/en" + + @staticmethod + async def loginAsync(sso_token: str) -> None: + API._Common.cookies["ACT_SSO_COOKIE"] = sso_token + API._Common.baseSsoToken = sso_token + r = await API._Common.__Request(f"{API._Common.baseUrl}/crm/cod/v2/identities/{sso_token}") + if r['status'] == 'success': + API._Common.loggedIn = True + else: + raise InvalidToken(sso_token) + + @staticmethod + def login(sso_token: str) -> None: + API._Common.cookies["ACT_SSO_COOKIE"] = sso_token + API._Common.baseSsoToken = sso_token + + r = requests.get(f"{API._Common.baseUrl}/crm/cod/v2/identities/{sso_token}", + headers=API._Common.requestHeaders, cookies=API._Common.cookies) + + if r.json()['status'] == 'success': + API._Common.loggedIn = True + API._Common.cookies.update(r.cookies) + else: + raise InvalidToken(sso_token) + + @staticmethod + def sso_token() -> str: + return API._Common.cookies["ACT_SSO_COOKIE"] + + # Requests + + @staticmethod + async def __Request(url): + async with aiohttp.client.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=True), + timeout=aiohttp.ClientTimeout(total=30)) as session: + try: + async with session.get(url, cookies=API._Common.cookies, + headers=API._Common.requestHeaders) as resp: + try: + resp.raise_for_status() + except ClientResponseError as err: + return {'status': 'error', 'data': {'type': type(err), 'message': err.message}} + else: + API._Common.cookies.update({c.key: c.value for c in session.cookie_jar}) + return await resp.json() + except asyncio.TimeoutError as err: + return {'status': 'error', 'data': {'type': type(err), 'message': str(err)}} + + async def __sendRequest(self, url: str): + if self.loggedIn: + response = await API._Common.__Request(f"{self.baseUrl}{url}") + if response['status'] == 'success': + response['data'] = await self.__perform_mapping(response['data']) + return response + else: + raise NotLoggedIn + + # client name url formatter + def __cleanClientName(self, gamertag): + return quote(gamertag.encode("utf-8")) + + # helper + def __helper(self, platform, gamertag): + lookUpType = "gamer" + if platform == platforms.Uno: + lookUpType = "id" + if platform == platforms.Activision: + platform = platforms.Uno + if platform not in [platforms.Activision, platforms.Battlenet, platforms.Uno, platforms.All, platforms.PSN, + platforms.XBOX]: + raise InvalidPlatform(platform) + else: + gamertag = self.__cleanClientName(gamertag) + return lookUpType, gamertag, platform + + async def __get_mappings(self): + if API._Common.cachedMappings is None: + API._Common.cachedMappings = ( + await API._Common.__Request('https://engineer152.github.io/wz-data/weapon-ids.json'), + await API._Common.__Request('https://engineer152.github.io/wz-data/game-modes.json'), + await API._Common.__Request('https://engineer152.github.io/wz-data/perks.json')) + return API._Common.cachedMappings + + # mapping + async def __perform_mapping(self, data): + guns, modes, perks = await self.__get_mappings() + if not isinstance(data, list) or 'matches' not in data: + return data + try: + for match in data['matches']: + # time stamps + try: + match['utcStartDateTime'] = datetime.fromtimestamp( + match['utcStartSeconds']).strftime("%A, %B %d, %Y, %I:%M:%S") + match['utcEndDateTime'] = datetime.fromtimestamp( + match['utcEndSeconds']).strftime("%A, %B %d, %Y, %I:%M:%S") + except KeyError: + pass + + # loadouts list + for loadout in match['player']['loadouts']: + # weapons + if loadout['primaryWeapon']['label'] is None: + try: + loadout['primaryWeapon']['label'] = guns[loadout['primaryWeapon']['name']] + except KeyError: + pass + if loadout['secondaryWeapon']['label'] is None: + try: + loadout['secondaryWeapon']['label'] = guns[loadout['secondaryWeapon']['name']] + except KeyError: + pass + + # perks list + for perk in loadout['perks']: + if perk['label'] is None: + try: + perk['label'] = perks[perk['name']] + except KeyError: + pass + + # extra perks list + for perk in loadout['extraPerks']: + if perk['label'] is None: + try: + perk['label'] = perks[perk['name']] + except KeyError: + pass + + # loadout list + for loadout in match['player']['loadout']: + if loadout['primaryWeapon']['label'] is None: + try: + loadout['primaryWeapon']['label'] = guns[loadout['primaryWeapon']['name']] + except KeyError: + pass + if loadout['secondaryWeapon']['label'] is None: + try: + loadout['secondaryWeapon']['label'] = guns[loadout['secondaryWeapon']['name']] + except KeyError: + pass + + # perks list + for perk in loadout['perks']: + if perk['label'] is None: + try: + perk['label'] = perks[perk['name']] + except KeyError: + pass + + # extra perks list + for perk in loadout['extraPerks']: + if perk['label'] is None: + try: + perk['label'] = perks[perk['name']] + except KeyError: + pass + except KeyError: + pass + + # return mapped or unmapped data + return data + + # API Requests + async def _fullDataReq(self, game, platform, gamertag, type): + lookUpType, gamertag, platform = self.__helper(platform, gamertag) + return await self.__sendRequest(self.fullDataUrl % (game, platform.value, lookUpType, gamertag, type)) + + async def _combatHistoryReq(self, game, platform, gamertag, type, start, end): + lookUpType, gamertag, platform = self.__helper(platform, gamertag) + return await self.__sendRequest( + self.combatHistoryUrl % (game, platform.value, lookUpType, gamertag, type, start, end)) + + async def _breakdownReq(self, game, platform, gamertag, type, start, end): + lookUpType, gamertag, platform = self.__helper(platform, gamertag) + return await self.__sendRequest( + self.breakdownUrl % (game, platform.value, lookUpType, gamertag, type, start, end)) + + async def _seasonLootReq(self, game, platform, gamertag): + lookUpType, gamertag, platform = self.__helper(platform, gamertag) + return await self.__sendRequest(self.seasonLootUrl % (game, platform.value, lookUpType, gamertag)) + + async def _mapListReq(self, game, platform): + return await self.__sendRequest(self.mapListUrl % (game, platform.value)) + + async def _matchInfoReq(self, game, platform, type, matchId): + return await self.__sendRequest(self.matchInfoUrl % (game, platform.value, type, matchId)) + + class __GameDataCommons(_Common): + """ + Methods + ======= + Sync + ---- + fullData(platform:platforms, gamertag:str) + returns player's game data of type dict + + combatHistory(platform:platforms, gamertag:str) + returns player's combat history of type dict + + combatHistoryWithDate(platform:platforms, gamertag:str, start:int, end:int) + returns player's combat history within the specified timeline of type dict + + breakdown(platform:platforms, gamertag:str) + returns player's combat history breakdown of type dict + + breakdownWithDate(platform:platforms, gamertag:str, start:int, end:int) + returns player's combat history breakdown within the specified timeline of type dict + + seasonLoot(platform:platforms, gamertag:str) + returns player's season loot + + mapList(platform:platforms) + returns available maps and available modes for each + + matchInfo(platform:platforms, matchId:int) + returns details match details of type dict + + Async + ---- + fullDataAsync(platform:platforms, gamertag:str) + returns player's game data of type dict + + combatHistoryAsync(platform:platforms, gamertag:str) + returns player's combat history of type dict + + combatHistoryWithDateAsync(platform:platforms, gamertag:str, start:int, end:int) + returns player's combat history within the specified timeline of type dict + + breakdownAsync(platform:platforms, gamertag:str) + returns player's combat history breakdown of type dict + + breakdownWithDateAsync(platform:platforms, gamertag:str, start:int, end:int) + returns player's combat history breakdown within the specified timeline of type dict + + seasonLootAsync(platform:platforms, gamertag:str) + returns player's season loot + + mapListAsync(platform:platforms) + returns available maps and available modes for each + + matchInfoAsync(platform:platforms, matchId:int) + returns details match details of type dict + """ + + def __init_subclass__(cls, **kwargs): + cls.__doc__ = cls.__doc__ + super(cls, cls).__doc__ + + @property + @abstractmethod + def _game(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def _type(self) -> str: + raise NotImplementedError + + async def fullDataAsync(self, platform: platforms, gamertag: str): + data = await self._fullDataReq(self._game, platform, gamertag, self._type) + return data + + def fullData(self, platform: platforms, gamertag: str): + return asyncio.run(self.fullDataAsync(platform, gamertag)) + + async def combatHistoryAsync(self, platform: platforms, gamertag: str): + data = await self._combatHistoryReq(self._game, platform, gamertag, self._type, 0, 0) + return data + + def combatHistory(self, platform: platforms, gamertag: str): + return asyncio.run(self.combatHistoryAsync(platform, gamertag)) + + async def combatHistoryWithDateAsync(self, platform, gamertag: str, start: int, end: int): + data = await self._combatHistoryReq(self._game, platform, gamertag, self._type, start, end) + return data + + def combatHistoryWithDate(self, platform, gamertag: str, start: int, end: int): + return asyncio.run(self.combatHistoryWithDateAsync(platform, gamertag, start, end)) + + async def breakdownAsync(self, platform, gamertag: str): + data = await self._breakdownReq(self._game, platform, gamertag, self._type, 0, 0) + return data + + def breakdown(self, platform, gamertag: str): + return asyncio.run(self.breakdownAsync(platform, gamertag)) + + async def breakdownWithDateAsync(self, platform, gamertag: str, start: int, end: int): + data = await self._breakdownReq(self._game, platform, gamertag, self._type, start, end) + return data + + def breakdownWithDate(self, platform, gamertag: str, start: int, end: int): + return asyncio.run(self.breakdownWithDateAsync(platform, gamertag, start, end)) + + async def matchInfoAsync(self, platform, matchId: int): + data = await self._matchInfoReq(self._game, platform, self._type, matchId) + return data + + def matchInfo(self, platform, matchId: int): + return asyncio.run(self.matchInfoAsync(platform, matchId)) + + async def seasonLootAsync(self, platform, gamertag): + data = await self._seasonLootReq(self._game, platform, gamertag) + return data + + def seasonLoot(self, platform, gamertag): + return asyncio.run(self.seasonLootAsync(platform, gamertag)) + + async def mapListAsync(self, platform): + data = await self._mapListReq(self._game, platform) + return data + + def mapList(self, platform): + return asyncio.run(self.mapListAsync(platform)) + # WZ + + class __WZ(__GameDataCommons): + """ + Warzone class: A class to get players warzone stats, warzone combat history and specific warzone match details + classCategory: game + gameId/gameTitle: mw or wz + gameType: wz + + """ + + @property + def _game(self) -> str: + return "mw" + + @property + def _type(self) -> str: + return "wz" + + async def seasonLootAsync(self, platform, gamertag): + raise InvalidEndpoint + + async def mapListAsync(self, platform): + raise InvalidEndpoint + + # WZ2 + + class __WZ2(__GameDataCommons): + """ + Warzone 2 class: A class to get players warzone 2 stats, warzone 2 combat history and specific warzone 2 match details + classCategory: game + gameId/gameTitle: mw or wz + gameType: wz2 + + """ + + @property + def _game(self) -> str: + return "mw2" + + @property + def _type(self) -> str: + return "wz2" + + async def seasonLootAsync(self, platform, gamertag): + raise InvalidEndpoint + + async def mapListAsync(self, platform): + raise InvalidEndpoint + + # MW + + class __MW(__GameDataCommons): + """ + ModernWarfare class: A class to get players modernwarfare stats, modernwarfare combat history, a player's modernwarfare season loot, modernwarfare map list and specific modernwarfare match details + classCategory: game + gameId/gameTitle: mw + gameType: mp + + """ + + @property + def _game(self) -> str: + return "mw" + + @property + def _type(self) -> str: + return "mp" + + # CW + + class __CW(__GameDataCommons): + """ + ColdWar class: A class to get players coldwar stats, coldwar combat history, a player's coldwar season loot, coldwar map list and specific coldwar match details + classCategory: game + gameId/gameTitle: cw + gameType: mp + + """ + @property + def _game(self) -> str: + return "cw" + + @property + def _type(self) -> str: + return "mp" + + # VG + + class __VG(__GameDataCommons): + """ + Vanguard class: A class to get players vanguard stats, vanguard combat history, a player's vanguard season loot, vanguard map list and specific vanguard match details + classCategory: game + gameId/gameTitle: vg + gameType: pm + + """ + + @property + def _game(self) -> str: + return "vg" + + @property + def _type(self) -> str: + return "mp" + + # MW2 + + class __MW2(__GameDataCommons): + """ + ModernWarfare 2 class: A class to get players modernwarfare 2 stats, modernwarfare 2 combat history, a player's modernwarfare 2 season loot, modernwarfare 2 map list and specific modernwarfare 2 match details + classCategory: game + gameId/gameTitle: mw + gameType: mp + + """ + + @property + def _game(self) -> str: + return "mw2" + + @property + def _type(self) -> str: + return "mp" + + # USER + class __USER(_Common): + def info(self): + if self.loggedIn: + # First, try to load the user info from a local file if it exists. + if os.path.exists('userInfo.json'): + with open('userInfo.json', 'r') as file: + rawData = json.load(file) + else: + # If the file doesn't exist, make the HTTP request. + rawData = requests.get(f"https://profile.callofduty.com/cod/userInfo/{self.sso_token()}", + headers=API._Common.requestHeaders) + + # Process the raw data (either from the file or HTTP response) + data = {'userName': rawData['userInfo']['userName'], 'identities': []} + for i in rawData['identities']: + data['identities'].append({ + 'platform': i['provider'], + 'gamertag': i['username'], + 'accountID': i['accountID'] + }) + return data + else: + raise NotLoggedIn + + def __priv(self): + d = self.info() + return d['identities'][0]['platform'], quote(d['identities'][0]['gamertag'].encode("utf-8")) + + async def friendFeedAsync(self): + p, g = self.__priv() + data = await self._Common__sendRequest( + f"/userfeed/v1/friendFeed/platform/{p}/gamer/{g}/friendFeedEvents/en") + return data + + def friendFeed(self): + return asyncio.run(self.friendFeedAsync()) + + async def eventFeedAsync(self): + data = await self._Common__sendRequest(f"/userfeed/v1/friendFeed/rendered/en/{self.sso_token()}") + return data + + def eventFeed(self): + return asyncio.run(self.eventFeedAsync()) + + async def loggedInIdentitiesAsync(self): + data = await self._Common__sendRequest(f"/crm/cod/v2/identities/{self.sso_token()}") + return data + + def loggedInIdentities(self): + return asyncio.run(self.loggedInIdentitiesAsync()) + + async def codPointsAsync(self): + p, g = self.__priv() + data = await self._Common__sendRequest(f"/inventory/v1/title/mw/platform/{p}/gamer/{g}/currency") + return data + + def codPoints(self): + return asyncio.run(self.codPointsAsync()) + + async def connectedAccountsAsync(self): + p, g = self.__priv() + data = await self._Common__sendRequest(f"/crm/cod/v2/accounts/platform/{p}/gamer/{g}") + return data + + def connectedAccounts(self): + return asyncio.run(self.connectedAccountsAsync()) + + async def settingsAsync(self): + p, g = self.__priv() + data = await self._Common__sendRequest(f"/preferences/v1/platform/{p}/gamer/{g}/list") + return data + + def settings(self): + return asyncio.run(self.settingsAsync()) + + # SHOP + class __SHOP(_Common): + """ + Shop class: A class to get bundle details and battle pass loot + classCategory: other + + Methods + ======= + Sync + ---- + purchasableItems(game: games) + returns purchasable items for a specific gameId/gameTitle + + bundleInformation(game: games, bundleId: int) + returns bundle details for the specific gameId/gameTitle and bundleId + + battlePassLoot(game: games, platform: platforms, season: int) + returns battle pass loot for specific game and season on given platform + + Async + ---- + purchasableItemsAsync(game: games) + returns purchasable items for a specific gameId/gameTitle + + bundleInformationAsync(game: games, bundleId: int) + returns bundle details for the specific gameId/gameTitle and bundleId + + battlePassLootAsync(game: games, platform: platforms, season: int) + returns battle pass loot for specific game and season on given platform + """ + + async def purchasableItemsAsync(self, game: games): + data = await self._Common__sendRequest(f"/inventory/v1/title/{game.value}/platform/uno/purchasable/public/en") + return data + + def purchasableItems(self, game: games): + return asyncio.run(self.purchasableItemsAsync(game)) + + async def bundleInformationAsync(self, game: games, bundleId: int): + data = await self._Common__sendRequest(f"/inventory/v1/title/{game.value}/bundle/{bundleId}/en") + return data + + def bundleInformation(self, game: games, bundleId: int): + return asyncio.run(self.bundleInformationAsync(game, bundleId)) + + async def battlePassLootAsync(self, game: games, platform: platforms, season: int): + data = await self._Common__sendRequest( + f"/loot/title/{game.value}/platform/{platform.value}/list/loot_season_{season}/en") + return data + + def battlePassLoot(self, game: games, platform: platforms, season: int): + return asyncio.run(self.battlePassLootAsync(game, platform, season)) + + # ALT + class __ALT(_Common): + + async def searchAsync(self, platform, gamertag: str): + lookUpType, gamertag, platform = self._Common__helper(platform, gamertag) + data = await self._Common__sendRequest(f"/crm/cod/v2/platform/{platform.value}/username/{gamertag}/search") + return data + + def search(self, platform, gamertag: str): + return asyncio.run(self.searchAsync(platform, gamertag)) + + +# Exceptions + +class NotLoggedIn(Exception): + def __str__(self): + return "Not logged in!" + + +class InvalidToken(Exception): + def __init__(self, token): + self.token = token + + def __str__(self): + return f"Token is invalid, token: {self.token}" + + +class InvalidPlatform(Exception): + def __init__(self, platform: platforms): + self.message: str + if platform == platforms.Steam: + self.message = "Steam cannot be used till further updates." + else: + self.message = "Invalid platform, use platform class!" + + + super().__init__(self.message) + + def __str__(self): + return self.message + + +class InvalidEndpoint(Exception): + def __str__(self): + return "This endpoint is not available for selected title" + + +class StatusError(Exception): + def __str__(self): + return "Status Error, Check if your sso token is valid or try again later." diff --git a/cod_api/replacements.py b/cod_api/replacements.py new file mode 100644 index 0000000..8cf9945 --- /dev/null +++ b/cod_api/replacements.py @@ -0,0 +1,361 @@ +replacements = { + # Maps + "career": "Career", + "mp_hackney_yard": "Hackney Yard (Night)", + "mp_aniyah": "Aniyah Palace", + "mp_euphrates": "Euphrates Bridge", + "mp_raid": "Grazna Raid", + "mp_m_pine": "Pine", + "mp_m_stack": "Stack", + "mp_deadzone": "Arklov Peak", + "mp_quarry": "Karst River Quarry", + "mp_m_overunder": "Docks", + "mp_cave_am": "Azhir Cave", + "mp_cave": "Azhir Cave (Night)", + "mp_runner": "Gun Runner", + "mp_runner_pm": "Gun Runner (Night)", + "mp_hackney_am": "Hackney Yard", + "mp_piccadilly": "Piccadilly", + "mp_spear": "Rammaza", + "mp_spear_pm": "Rammaza (Night)", + "mp_petrograd": "St. Petrograd", + "mp_m_hill": "Hill", + "mp_m_king": "King", + "mp_m_speedball": "Speedball", + "mp_m_showers": "Gulag Showers", + "mp_downtown_gw": "Tarvosk District", + "mp_m_speed": "Shoot House", + "mp_farms2_gw": "Krovnik Farmland", + "mp_port2_gw": "Port", + "mp_crash": "Crash", + "mp_vacant": "Vacant", + "mp_shipment": "Shipment", + "mp_m_cargo": "Cargo", + "mp_m_cage": "Atrium", + "mp_m_overwinter": "Docks", + "mp_emporium": "Atlas Superstore", + "mp_rust": "Rust", + "mp_boneyard_gw": "Zhokov Boneyard", + "mp_m_fork": "Bazaar", + "mp_donetsk": "Verdansk", + "mp_hideout": "Khandor Hideout", + "loading_mp_hideout": "Khandor Hideout", + "mp_aniyah_tac": "Aniyah Incursion", + "mp_backlot": "Talsik Backlot", + "mp_village": "Hovec Sawmill", + "mp_hardhat": "Hardhat", + "mp_m_wallco": "Aisle 9", + "mp_donetsk": "Verdansk", + "mp_scrapyard": "Zhokov Scrapyard", + "mp_m_trench": "Trench", + "mp_promenade_gw": "Barakett Promenade", + "mp_don": "Verdansk", + "mp_garden": "Cheshire Park", + "mp_oilrig": "Petrov Oil Rig", + "mp_harbor": "Suldal Harbor", + "mp_layover_gw": "Verdansk International Airport", + "mp_m_cornfield": "Livestock", + "mp_m_stadium": "Verdansk Stadium", + "mp_malyshev": "Mialstor Tank Factory", + "mp_malyshev_10v10": "Mialstor Tank Factory", + "mp_broadcast": "Broadcast", + "mp_riverside_gw": "Verdansk Riverside", + "mp_m_train": "Station", + "mp_kstenod": "Verdansk (Night)", + "mp_escape": "Rebirth", + "mp_herat": "Al-Raab Airbase", + "mp_killhouse": "Killhouse", + "mp_m_drainage": "Drainage", + # Gamemodes + "war": "Team Deathmatch", + "sd": "Search and Destroy", + "dom": "Domination", + "tdef": "Team Defender", + "dm": "Free-for-all", + "koth": "Hardpoint", + "hq": "Headquarters", + "arena": "Gunfight", + "arm": "Ground War", + "conf": "Kill Confirmed", + "cyber": "Cyber Attack", + "hc_war": "Team Deathmatch Hardcore", + "hc_arena": "Gunfight Hardcore", + "hc_arm": "Ground War Hardcore", + "hc_conf": "Kill Confirmed Hardcore", + "hc_cyber": "Cyber Attack Hardcore", + "hc_dm": "Free-for-all Hardcore", + "hc_hq": "Headquarters Hardcore", + "hc_dom": "Domination Hardcore", + "hc_sd": "Search and Destroy Hardcore", + "cyber_hc": "Cyber Attack Hardcore", + "war_hc": "Team Deathmatch Hardcore", + "dom_hc": "Domination Hardcore", + "sd_hc": "Search and Destroy Hardcore", + "conf_hc": "Kill Confirmed Hardcore", + "gun": "Gun Game", + "gun_hc": "Gun Game Hardcore", + "siege": "Reinforce", + "infect": "Infected", + "arena_osp": "Gunfight O.S.P.", + "hq_hc": "Headquarters Hardcore", + "grnd": "Grind", + "grind": "Grind", + "ctf": "Capture the Flag", + "br_all": "All", + "br": "Battle Royale", + "br_dmz": "Plunder", + "br_dmz_38": "Plunder Quads", + "br_87": "BR Solos", + "br_dmz_104": "Blood Money", + "koth_hc": "Hardpoint Hardcore", + "br_25": "BR Trios", + "br_89": "BR Quads", + "br_dmz_76": "Plunder Quads", + "br_77": "BR Scopes & Scatterguns", + "br_dmz_85": "Plunder Duos", + "dd_hc": "Demolition Hardcore", + "dd": "Demolition", + "br_71": "BR Solos", + "br_74": "BR Trios", + "br_88": "BR Duos", + "brtdm_113": "Warzone Rumble", + "brtdm_rmbl": "Warzone Rumble", + "br_brsolo": "BR Solos", + "br_brduos": "BR Duos", + "br_brtrios": "BR Trios", + "br_brquads": "BR Quads", + "br_dmz_plnbld": "Blood Money", + "br_br_real": "Realism Battle Royale", + "br_86": "Realism Battle Royale", + "br_brthquad": "BR 200 Quads", + "br_jugg_brtriojugr": "Juggernaut Royal Trios", + "br_dmz_plunquad": "Plunder Quads", + "br_dmz_bldmnytrio": "Blood Money Trios", + "br_mini_miniroyale": "Mini Royale", + "br_brbbsolo": "BR Buyback Solos", + "br_jugg_brquadjugr": "Juggernaut Royal Quads", + "br_kingslayer_kingsltrios": "King Slayer Trios", + "br_truckwar_trwarsquads": "Armored Royale Quads", + "br_zxp_zmbroy": "Zombie Royale", + "br_brhwntrios": "BR Trick-Or-Trios", + "rugby": "Onslaughter", + "br_brsolohwn": "BR Solo Survivor", + "br_dmz_plndcndy": "Plunder: Candy Collector", + "br_jugg_jugpmpkn": "Juggourdnaut Royale", + "br_rebirth_rbrthtrios": "Resurgence Trio", + "br_rebirth_rbrthduos": "Resurgence Duos", + "br_rebirth_rbrthquad": "Rebirth Resurgance Quads", + "br_dmz_plndtrios": "Plunder Trios", + "br_rebirth_resurgence_trios": "Verdansk Resurgence Trios", + "br_mini_rebirth_mini_royale_quads": "Rebirth Mini Royale Quads", + "br_bodycount_pwergrb": "Power Grab", + "br_rebirth_resurgence_mini": "Verdansk Resurgence Mini", + "br_payload_payload": "Payload", + "br_mini_rebirth_mini_royale_trios": "Rebirth Mini Royale Trios", + "br_x2_br_reveal_x2_event/event_title_x2": "Battle of Verdansk", + "br_rumble_clash": "Clash", + "br_dbd_dbd": "Iron Trials '84", + "br_gxp_gov": "Ghosts of Verdansk", + # Weapons + "scorestreak": "Scorestreak", + "equipment": "Equipment", + "gear": "Gear", + "weapon_bare_hands": "Bare Hands", + "weapon_tactical_rifle": "Tactical Rifle", + "weapon_shotgun": "Shotgun", + "weapon_sniper": "Sniper", + "weapon_lmg": "Light Machine Guns", + "weapon_launcher": "Launcher", + "weapon_pistol": "Pistol", + "weapon_smg": "Submachine Guns", + "weapon_melee": "Melee", + "weapon_assault_rifle": "Assault Rifle", + "attachments": "Attachments", + "weapons": "Weapons", + "specialist": "Specialist", + "weapon": "Weapon", + "weapon_special": "Special", + "iw8_ar_akilo47": "AK-47", + "iw8_ar_kilo433": "Kilo-141", + "iw8_ar_mcharlie": "M13", + "iw8_ar_falima": "FAL", + "iw8_ar_asierra12": "Oden", + "iw8_sm_mpapa7": "MP7", + "iw8_sm_augolf": "AUG", + "iw8_sm_uzulu": "Uzi", + "iw8_sh_romeo870": "Model 680", + "iw8_sh_charlie725": "725", + "iw8_sh_aalpha12": "JAK-12", + "iw8_sh_oscar12": "Origin 12", + "iw8_lm_pkilo": "PKM", + "iw8_lm_mgolf34": "MG34", + "iw8_lm_lima86": "SA87", + "iw8_lm_dblmg": "MP Juggernaut", + "iw8_sn_mike14": "EBR-14", + "iw8_sn_delta": "Dragunov", + "iw8_sn_alpha50": "AX-50", + "iw8_sn_hdromeo": "HDR", + "iw8_sn_sbeta": "Mk2 Carbine", + "iw8_pi_papa320": "M19", + "iw8_pi_cpapa": ".357", + "iw8_la_rpapa7": "RPG-7", + "iw8_la_juliet": "JOKR", + "iw8_la_gromeo": "PILA", + "iw8_la_kgolf": "Strela-P", + "iw8_me_riotshield": "Riot Shield", + "equip_gas_grenade": "Gas Grenade", + "equip_snapshot_grenade": "Snapshot Grenade", + "equip_decoy": "Decoy Grenade", + "equip_smoke": "Smoke Grenade", + "equip_concussion": "Stun Grenade", + "equip_hb_sensor": "Heartbeat Sensor", + "equip_flash": "Flash Grenade", + "equip_adrenaline": "Stim", + "equip_frag": "Frag Grenade", + "equip_thermite": "Thermite", + "equip_semtex": "Semtex", + "equip_claymore": "Claymore", + "equip_c4": "C4", + "equip_at_mine": "Proximity Mine", + "equip_throwing_knife": "Throwing Knife", + "equip_molotov": "Molotov Cocktail", + "iw8_knife": "Combat Knife", + "weapon_other": "Primary Melee", + "iw8_ar_tango21": "RAM-7", + "iw8_ar_falpha": "FR 5.56", + "iw8_ar_mike4": "M4A1", + "iw8_sm_papa90": "P90", + "iw8_sm_mpapa5": "MP5", + "iw8_sm_beta": "PP19 Bizon", + "iw8_sh_dpapa12": "R9-0", + "iw8_lm_mgolf36": "Holger-26", + "iw8_sn_kilo98": "Kar98k", + "iw8_pi_mike1911": "1911", + "iw8_pi_golf21": "X16", + "iw8_pi_decho": ".50 GS", + "weapon_marksman": "Marksman Rifles", + "iw8_lm_kilo121": "M91", + "iw8_ar_scharlie": "FN Scar 17", + "iw8_ar_sierra552": "Grau 5.56", + "iw8_sm_smgolf45": "Striker 45", + "iw8_pi_mike9a3": "Renetti", + "iw8_lm_mkilo3": "Bruen MK9", + "iw8_sh_mike26": "VLK Rogue", + "iw8_sn_crossbow": "Crossbow", + "iw8_sn_sksierra": "SKS", + "iw8_ar_galima": "CR-56 AMAX", + "iw8_me_kalistick": "Kali Sticks", + "iw8_sm_victor": "Fennec Mk9", + "iw8_sn_xmike109": "Rytec AMR", + "iw8_pi_mike9": "Renetti", + "iw8_me_akimboblunt": "Kali Sticks", + "iw8_ar_anovember94": "AN-94", + "iw8_sm_charlie9": "ISO", + "iw8_me_akimboblades": "Dual Kodachis", + "iw8_lm_sierrax": "FiNN", + "iw8_ar_valpha": "AS VAL", + "iw8_sn_romeo700": "SP-R 208", + "cruise_predator": "Cruise Missile", + "manual_turret": "Shield Turret", + "toma_strike": "Cluster Strike", + "sentry_gun": "Sentry Gun", + "hover_jet": "VTOL Jet", + "precision_airstrike": "Precision Airstrike", + "juggernaut": "Juggernaut", + "pac_sentry": "", + "chopper_gunner": "Chopper Gunner", + "gunship": "Gunship", + "white_phosphorus": "White Phosphorus", + "nuke": "Nuke", + "chopper_support": "Support Helo", + "bradley": "Infantry Assault Vehicle", + "uav": "UAV", + "directional_uav": "Advanced UAV", + "airdrop": "Care Package", + "airdrop_multiple": "Emergency Airdrop", + "radar_drone_overwatch": "Personal Radar", + "scrambler_drone_guard": "Counter UAV", + "super_emp_drone": "EMP Drone", + "super_trophy": "Trophy System", + "super_ammo_drop": "Munitions Box", + "super_weapon_drop": "Weapon Drop", + "super_fulton": "Cash Deposit Balloon", + "super_armor_drop": "Armor Box", + "super_select": "Field Upgrade Pro (Any)", + "super_tac_insert": "Tactical Insertion", + "super_recon_drone": "Recon Drone", + "super_deadsilence": "Dead Silence", + "super_supply_drop": "Loadout Drop", ### Unsure if this is Loadout Drop + "super_tac_cover": "Deployable Cover", + "super_support_box": "Stopping Power Rounds", + # Accolades + # "accoladeData": "Accolades", + # "classChanges": "Most classes changed (Evolver)", + # "highestAvgAltitude": "Highest average altitude (High Command)", + # "killsFromBehind": "Most kills from behind (Flanker)", + # "lmgDeaths": "Most LMG deaths (Target Practice)", + # "riotShieldDamageAbsorbed": "Most damage absorbed with Riot Shield (Guardian)", + # "flashbangHits": "Most Flashbang hits (Blinder)", + # "meleeKills": "Most Melee kills (Brawler)", + # "tagsLargestBank": "Largest bank (Bank Account)", + # "shotgunKills": "Most Shotgun kills (Buckshot)", + # "sniperDeaths": "Most Sniper deaths (Zeroed In)", + # "timeProne": "Most time spent Prone (Grassy Knoll)", + # "killstreakWhitePhosphorousKillsAssists": "Most kills and assists with White Phosphorus (Burnout)", + # "shortestLife": "Shortest life (Terminal)", + # "deathsFromBehind": "Most deaths from behind (Blindsided)", + # "higherRankedKills": "Most kills on higher ranked scoreboard players (Upriser)", + # "mostAssists": "Most assists (Wingman)", + # "leastKills": "Fewest kills (The Fearful)", + # "tagsDenied": "Denied the most tags (Denied)", + # "killstreakWheelsonKills": "Most Wheelson kills", + # "sniperHeadshots": "Most Sniper headshots (Dead Aim)", + # "killstreakJuggernautKills": "Most Juggernaut kills (Heavy Metal)", + # "smokesUsed": "Most Smoke Grenades used (Chimney)", + # "avengerKills": "Most avenger kills (Avenger)", + # "decoyHits": "Most Decoy Grenade hits (Made You Look)", + # "killstreakCarePackageUsed": "Most Care Packages called in (Helping Hand)", + # "molotovKills": "Most Molotov kills (Arsonist)", + # "gasHits": "Most Gas Grenade hits (Gaseous)", + # "comebackKills": "Most comebacks (Rally)", + # "lmgHeadshots": "Most LMG headshots (LMG Expert)", + # "smgDeaths": "Most SMG deaths (Run and Gunned)", + # "carrierKills": "Most kills as carrier (Carrier)", + # "deployableCoverUsed": "Most Deployable Covers used (Combat Engineer)", + # "thermiteKills": "Most Thermite kills (Red Iron)", + # "arKills": "Most assault rifle kills (AR Specialist)", + # "c4Kills": "Most C4 kills (Handle With Care)", + # "suicides": "Most suicides (Accident Prone)", + # "clutch": "Most kills as the last alive (Clutched)", + # "survivorKills": "Most kills as survivor (Survivalist)", + # "killstreakGunshipKills": "Most Gunship kills (Death From Above)", + # "timeSpentAsPassenger": "Most time spent as a passenger (Navigator)", + # "returns": "Most flags returned (Flag Returner)", + # "smgHeadshots": "Most SMG headshots (SMG Expert)", + # "launcherDeaths": "Most launcher deaths (Fubar)", + # "oneShotOneKills": "Most one shot kills (One Shot Kill)", + # "ammoBoxUsed": "Most Munitions Boxes used (Provider)", + # #"spawnSelectSquad": "", + # "weaponPickups": "Most picked up weapons (Loaner)", + # "pointBlankKills": "Most point blank kills (Personal Space)", + # "tagsCaptured": "Collected the most tags (Confirmed Kills)", + # "killstreakGroundKills": "Most ground based killstreak kills (Ground Control)", + # "distanceTraveledInVehicle": "Longest distance travelled in a vehicle (Cross Country)", + # "longestLife": "Longest life (Lifer)", + # "stunHits": "Most Stun Grenade hits (Stunner)", + # "spawnSelectFlag": "Most FOB Spawns (Objective Focused)", # Unsure + # "shotgunHeadshots": "Most Shotgun headshots (Boomstick)", + # "bombDefused": "Most defuses (Defuser)", + # "snapshotHits": "Most Snapshot Grenade hits (Photographer)", + # "noKillsWithDeath": "No kills with at least 1 death (Participant)", + # "killstreakAUAVAssists": "Most Advanced UAV assists (Target Rich Environment)", + # "killstreakPersonalUAVKills": "Most kills with a Personal Radar active (Nothing Personal)", + # "tacticalInsertionSpawns": "Most Tactical Insertions used (Revenant)", + # "launcherKills": "Most Launcher kills (Explosive)", + # "spawnSelectVehicle": "Most vehicle spawns (Oscar Mike)", + # "mostKillsLeastDeaths": "Most kills and fewest deaths (MVP)", + # "mostKills": "Most kills (The Feared)", + # "defends": "Most defend kills (Defense)", + # "timeSpentAsDriver": "Most time spent driving (Driver)", + # "": "" # WIP - Still adding more +} \ No newline at end of file diff --git a/depricated_dev_scripts/curl/curl_links.txt b/depricated_dev_scripts/curl/curl_links.txt index 575c644..dd2864e 100644 --- a/depricated_dev_scripts/curl/curl_links.txt +++ b/depricated_dev_scripts/curl/curl_links.txt @@ -24,4 +24,7 @@ https://my.callofduty.com/api/papi-client/inventory/v1/title/mw/platform/battle/ https://my.callofduty.com/api/papi-client/crm/cod/v2/accounts/platform/battle/gamer/$PROF/ -https://my.callofduty.com/api/papi-client/preferences/v1/platform/battle/gamer/$PROF/list \ No newline at end of file +https://my.callofduty.com/api/papi-client/preferences/v1/platform/battle/gamer/$PROF/list + +# Get Bundle Info +https://my.callofduty.com/api/papi-client/inventory/v1/title/mw/bundle/400525/en \ No newline at end of file diff --git a/get_cod_stats.py b/get_cod_stats.py index fe6102d..fa37271 100644 --- a/get_cod_stats.py +++ b/get_cod_stats.py @@ -1,8 +1,10 @@ +import re import sys import json import os import argparse from cod_api import API, platforms +from cod_api.replacements import replacements import asyncio import datetime @@ -10,368 +12,6 @@ import datetime if os.name == 'nt': asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) -replacements = { - # Maps - "career": "Career", - "mp_hackney_yard": "Hackney Yard (Night)", - "mp_aniyah": "Aniyah Palace", - "mp_euphrates": "Euphrates Bridge", - "mp_raid": "Grazna Raid", - "mp_m_pine": "Pine", - "mp_m_stack": "Stack", - "mp_deadzone": "Arklov Peak", - "mp_quarry": "Karst River Quarry", - "mp_m_overunder": "Docks", - "mp_cave_am": "Azhir Cave", - "mp_cave": "Azhir Cave (Night)", - "mp_runner": "Gun Runner", - "mp_runner_pm": "Gun Runner (Night)", - "mp_hackney_am": "Hackney Yard", - "mp_piccadilly": "Piccadilly", - "mp_spear": "Rammaza", - "mp_spear_pm": "Rammaza (Night)", - "mp_petrograd": "St. Petrograd", - "mp_m_hill": "Hill", - "mp_m_king": "King", - "mp_m_speedball": "Speedball", - "mp_m_showers": "Gulag Showers", - "mp_downtown_gw": "Tarvosk District", - "mp_m_speed": "Shoot House", - "mp_farms2_gw": "Krovnik Farmland", - "mp_port2_gw": "Port", - "mp_crash": "Crash", - "mp_vacant": "Vacant", - "mp_shipment": "Shipment", - "mp_m_cargo": "Cargo", - "mp_m_cage": "Atrium", - "mp_m_overwinter": "Docks", - "mp_emporium": "Atlas Superstore", - "mp_rust": "Rust", - "mp_boneyard_gw": "Zhokov Boneyard", - "mp_m_fork": "Bazaar", - "mp_donetsk": "Verdansk", - "mp_hideout": "Khandor Hideout", - "loading_mp_hideout": "Khandor Hideout", - "mp_aniyah_tac": "Aniyah Incursion", - "mp_backlot": "Talsik Backlot", - "mp_village": "Hovec Sawmill", - "mp_hardhat": "Hardhat", - "mp_m_wallco": "Aisle 9", - "mp_donetsk": "Verdansk", - "mp_scrapyard": "Zhokov Scrapyard", - "mp_m_trench": "Trench", - "mp_promenade_gw": "Barakett Promenade", - "mp_don": "Verdansk", - "mp_garden": "Cheshire Park", - "mp_oilrig": "Petrov Oil Rig", - "mp_harbor": "Suldal Harbor", - "mp_layover_gw": "Verdansk International Airport", - "mp_m_cornfield": "Livestock", - "mp_m_stadium": "Verdansk Stadium", - "mp_malyshev": "Mialstor Tank Factory", - "mp_malyshev_10v10": "Mialstor Tank Factory", - "mp_broadcast": "Broadcast", - "mp_riverside_gw": "Verdansk Riverside", - "mp_m_train": "Station", - "mp_kstenod": "Verdansk (Night)", - "mp_escape": "Rebirth", - "mp_herat": "Al-Raab Airbase", - "mp_killhouse": "Killhouse", - "mp_m_drainage": "Drainage", - # Gamemodes - "war": "Team Deathmatch", - "sd": "Search and Destroy", - "dom": "Domination", - "tdef": "Team Defender", - "dm": "Free-for-all", - "koth": "Hardpoint", - "hq": "Headquarters", - "arena": "Gunfight", - "arm": "Ground War", - "conf": "Kill Confirmed", - "cyber": "Cyber Attack", - "hc_war": "Team Deathmatch Hardcore", - "hc_arena": "Gunfight Hardcore", - "hc_arm": "Ground War Hardcore", - "hc_conf": "Kill Confirmed Hardcore", - "hc_cyber": "Cyber Attack Hardcore", - "hc_dm": "Free-for-all Hardcore", - "hc_hq": "Headquarters Hardcore", - "hc_dom": "Domination Hardcore", - "hc_sd": "Search and Destroy Hardcore", - "cyber_hc": "Cyber Attack Hardcore", - "war_hc": "Team Deathmatch Hardcore", - "dom_hc": "Domination Hardcore", - "sd_hc": "Search and Destroy Hardcore", - "conf_hc": "Kill Confirmed Hardcore", - "gun": "Gun Game", - "gun_hc": "Gun Game Hardcore", - "siege": "Reinforce", - "infect": "Infected", - "arena_osp": "Gunfight O.S.P.", - "hq_hc": "Headquarters Hardcore", - "grnd": "Grind", - "grind": "Grind", - "ctf": "Capture the Flag", - "br_all": "All", - "br": "Battle Royale", - "br_dmz": "Plunder", - "br_dmz_38": "Plunder Quads", - "br_87": "BR Solos", - "br_dmz_104": "Blood Money", - "koth_hc": "Hardpoint Hardcore", - "br_25": "BR Trios", - "br_89": "BR Quads", - "br_dmz_76": "Plunder Quads", - "br_77": "BR Scopes & Scatterguns", - "br_dmz_85": "Plunder Duos", - "dd_hc": "Demolition Hardcore", - "dd": "Demolition", - "br_71": "BR Solos", - "br_74": "BR Trios", - "br_88": "BR Duos", - "brtdm_113": "Warzone Rumble", - "brtdm_rmbl": "Warzone Rumble", - "br_brsolo": "BR Solos", - "br_brduos": "BR Duos", - "br_brtrios": "BR Trios", - "br_brquads": "BR Quads", - "br_dmz_plnbld": "Blood Money", - "br_br_real": "Realism Battle Royale", - "br_86": "Realism Battle Royale", - "br_brthquad": "BR 200 Quads", - "br_jugg_brtriojugr": "Juggernaut Royal Trios", - "br_dmz_plunquad": "Plunder Quads", - "br_dmz_bldmnytrio": "Blood Money Trios", - "br_mini_miniroyale": "Mini Royale", - "br_brbbsolo": "BR Buyback Solos", - "br_jugg_brquadjugr": "Juggernaut Royal Quads", - "br_kingslayer_kingsltrios": "King Slayer Trios", - "br_truckwar_trwarsquads": "Armored Royale Quads", - "br_zxp_zmbroy": "Zombie Royale", - "br_brhwntrios": "BR Trick-Or-Trios", - "rugby": "Onslaughter", - "br_brsolohwn": "BR Solo Survivor", - "br_dmz_plndcndy": "Plunder: Candy Collector", - "br_jugg_jugpmpkn": "Juggourdnaut Royale", - "br_rebirth_rbrthtrios": "Resurgence Trio", - "br_rebirth_rbrthduos": "Resurgence Duos", - "br_rebirth_rbrthquad": "Rebirth Resurgance Quads", - "br_dmz_plndtrios": "Plunder Trios", - "br_rebirth_resurgence_trios": "Verdansk Resurgence Trios", - "br_mini_rebirth_mini_royale_quads": "Rebirth Mini Royale Quads", - "br_bodycount_pwergrb": "Power Grab", - "br_rebirth_resurgence_mini": "Verdansk Resurgence Mini", - "br_payload_payload": "Payload", - "br_mini_rebirth_mini_royale_trios": "Rebirth Mini Royale Trios", - "br_x2_br_reveal_x2_event/event_title_x2": "Battle of Verdansk", - "br_rumble_clash": "Clash", - "br_dbd_dbd": "Iron Trials '84", - "br_gxp_gov": "Ghosts of Verdansk", - # Weapons - "scorestreak": "Scorestreak", - "equipment": "Equipment", - "gear": "Gear", - "weapon_bare_hands": "Bare Hands", - "weapon_tactical_rifle": "Tactical Rifle", - "weapon_shotgun": "Shotgun", - "weapon_sniper": "Sniper", - "weapon_lmg": "Light Machine Guns", - "weapon_launcher": "Launcher", - "weapon_pistol": "Pistol", - "weapon_smg": "Submachine Guns", - "weapon_melee": "Melee", - "weapon_assault_rifle": "Assault Rifle", - "attachments": "Attachments", - "weapons": "Weapons", - "specialist": "Specialist", - "weapon": "Weapon", - "weapon_special": "Special", - "iw8_ar_akilo47": "AK-47", - "iw8_ar_kilo433": "Kilo-141", - "iw8_ar_mcharlie": "M13", - "iw8_ar_falima": "FAL", - "iw8_ar_asierra12": "Oden", - "iw8_sm_mpapa7": "MP7", - "iw8_sm_augolf": "AUG", - "iw8_sm_uzulu": "Uzi", - "iw8_sh_romeo870": "Model 680", - "iw8_sh_charlie725": "725", - "iw8_sh_aalpha12": "JAK-12", - "iw8_sh_oscar12": "Origin 12", - "iw8_lm_pkilo": "PKM", - "iw8_lm_mgolf34": "MG34", - "iw8_lm_lima86": "SA87", - "iw8_lm_dblmg": "MP Juggernaut", - "iw8_sn_mike14": "EBR-14", - "iw8_sn_delta": "Dragunov", - "iw8_sn_alpha50": "AX-50", - "iw8_sn_hdromeo": "HDR", - "iw8_sn_sbeta": "Mk2 Carbine", - "iw8_pi_papa320": "M19", - "iw8_pi_cpapa": ".357", - "iw8_la_rpapa7": "RPG-7", - "iw8_la_juliet": "JOKR", - "iw8_la_gromeo": "PILA", - "iw8_la_kgolf": "Strela-P", - "iw8_me_riotshield": "Riot Shield", - "equip_gas_grenade": "Gas Grenade", - "equip_snapshot_grenade": "Snapshot Grenade", - "equip_decoy": "Decoy Grenade", - "equip_smoke": "Smoke Grenade", - "equip_concussion": "Stun Grenade", - "equip_hb_sensor": "Heartbeat Sensor", - "equip_flash": "Flash Grenade", - "equip_adrenaline": "Stim", - "equip_frag": "Frag Grenade", - "equip_thermite": "Thermite", - "equip_semtex": "Semtex", - "equip_claymore": "Claymore", - "equip_c4": "C4", - "equip_at_mine": "Proximity Mine", - "equip_throwing_knife": "Throwing Knife", - "equip_molotov": "Molotov Cocktail", - "iw8_knife": "Combat Knife", - "weapon_other": "Primary Melee", - "iw8_ar_tango21": "RAM-7", - "iw8_ar_falpha": "FR 5.56", - "iw8_ar_mike4": "M4A1", - "iw8_sm_papa90": "P90", - "iw8_sm_mpapa5": "MP5", - "iw8_sm_beta": "PP19 Bizon", - "iw8_sh_dpapa12": "R9-0", - "iw8_lm_mgolf36": "Holger-26", - "iw8_sn_kilo98": "Kar98k", - "iw8_pi_mike1911": "1911", - "iw8_pi_golf21": "X16", - "iw8_pi_decho": ".50 GS", - "weapon_marksman": "Marksman Rifles", - "iw8_lm_kilo121": "M91", - "iw8_ar_scharlie": "FN Scar 17", - "iw8_ar_sierra552": "Grau 5.56", - "iw8_sm_smgolf45": "Striker 45", - "iw8_pi_mike9a3": "Renetti", - "iw8_lm_mkilo3": "Bruen MK9", - "iw8_sh_mike26": "VLK Rogue", - "iw8_sn_crossbow": "Crossbow", - "iw8_sn_sksierra": "SKS", - "iw8_ar_galima": "CR-56 AMAX", - "iw8_me_kalistick": "Kali Sticks", - "iw8_sm_victor": "Fennec Mk9", - "iw8_sn_xmike109": "Rytec AMR", - "iw8_pi_mike9": "Renetti", - "iw8_me_akimboblunt": "Kali Sticks", - "iw8_ar_anovember94": "AN-94", - "iw8_sm_charlie9": "ISO", - "iw8_me_akimboblades": "Dual Kodachis", - "iw8_lm_sierrax": "FiNN", - "iw8_ar_valpha": "AS VAL", - "iw8_sn_romeo700": "SP-R 208", - "cruise_predator": "Cruise Missile", - "manual_turret": "Shield Turret", - "toma_strike": "Cluster Strike", - "sentry_gun": "Sentry Gun", - "hover_jet": "VTOL Jet", - "precision_airstrike": "Precision Airstrike", - "juggernaut": "Juggernaut", - "pac_sentry": "", - "chopper_gunner": "Chopper Gunner", - "gunship": "Gunship", - "white_phosphorus": "White Phosphorus", - "nuke": "Nuke", - "chopper_support": "Support Helo", - "bradley": "Infantry Assault Vehicle", - "uav": "UAV", - "directional_uav": "Advanced UAV", - "airdrop": "Care Package", - "airdrop_multiple": "Emergency Airdrop", - "radar_drone_overwatch": "Personal Radar", - "scrambler_drone_guard": "Counter UAV", - "super_emp_drone": "EMP Drone", - "super_trophy": "Trophy System", - "super_ammo_drop": "Munitions Box", - "super_weapon_drop": "Weapon Drop", - "super_fulton": "Cash Deposit Balloon", - "super_armor_drop": "Armor Box", - "super_select": "Field Upgrade Pro (Any)", - "super_tac_insert": "Tactical Insertion", - "super_recon_drone": "Recon Drone", - "super_deadsilence": "Dead Silence", - "super_supply_drop": "Loadout Drop", ### Unsure if this is Loadout Drop - "super_tac_cover": "Deployable Cover", - "super_support_box": "Stopping Power Rounds", - # Accolades - # "accoladeData": "Accolades", - # "classChanges": "Most classes changed (Evolver)", - # "highestAvgAltitude": "Highest average altitude (High Command)", - # "killsFromBehind": "Most kills from behind (Flanker)", - # "lmgDeaths": "Most LMG deaths (Target Practice)", - # "riotShieldDamageAbsorbed": "Most damage absorbed with Riot Shield (Guardian)", - # "flashbangHits": "Most Flashbang hits (Blinder)", - # "meleeKills": "Most Melee kills (Brawler)", - # "tagsLargestBank": "Largest bank (Bank Account)", - # "shotgunKills": "Most Shotgun kills (Buckshot)", - # "sniperDeaths": "Most Sniper deaths (Zeroed In)", - # "timeProne": "Most time spent Prone (Grassy Knoll)", - # "killstreakWhitePhosphorousKillsAssists": "Most kills and assists with White Phosphorus (Burnout)", - # "shortestLife": "Shortest life (Terminal)", - # "deathsFromBehind": "Most deaths from behind (Blindsided)", - # "higherRankedKills": "Most kills on higher ranked scoreboard players (Upriser)", - # "mostAssists": "Most assists (Wingman)", - # "leastKills": "Fewest kills (The Fearful)", - # "tagsDenied": "Denied the most tags (Denied)", - # "killstreakWheelsonKills": "Most Wheelson kills", - # "sniperHeadshots": "Most Sniper headshots (Dead Aim)", - # "killstreakJuggernautKills": "Most Juggernaut kills (Heavy Metal)", - # "smokesUsed": "Most Smoke Grenades used (Chimney)", - # "avengerKills": "Most avenger kills (Avenger)", - # "decoyHits": "Most Decoy Grenade hits (Made You Look)", - # "killstreakCarePackageUsed": "Most Care Packages called in (Helping Hand)", - # "molotovKills": "Most Molotov kills (Arsonist)", - # "gasHits": "Most Gas Grenade hits (Gaseous)", - # "comebackKills": "Most comebacks (Rally)", - # "lmgHeadshots": "Most LMG headshots (LMG Expert)", - # "smgDeaths": "Most SMG deaths (Run and Gunned)", - # "carrierKills": "Most kills as carrier (Carrier)", - # "deployableCoverUsed": "Most Deployable Covers used (Combat Engineer)", - # "thermiteKills": "Most Thermite kills (Red Iron)", - # "arKills": "Most assault rifle kills (AR Specialist)", - # "c4Kills": "Most C4 kills (Handle With Care)", - # "suicides": "Most suicides (Accident Prone)", - # "clutch": "Most kills as the last alive (Clutched)", - # "survivorKills": "Most kills as survivor (Survivalist)", - # "killstreakGunshipKills": "Most Gunship kills (Death From Above)", - # "timeSpentAsPassenger": "Most time spent as a passenger (Navigator)", - # "returns": "Most flags returned (Flag Returner)", - # "smgHeadshots": "Most SMG headshots (SMG Expert)", - # "launcherDeaths": "Most launcher deaths (Fubar)", - # "oneShotOneKills": "Most one shot kills (One Shot Kill)", - # "ammoBoxUsed": "Most Munitions Boxes used (Provider)", - # #"spawnSelectSquad": "", - # "weaponPickups": "Most picked up weapons (Loaner)", - # "pointBlankKills": "Most point blank kills (Personal Space)", - # "tagsCaptured": "Collected the most tags (Confirmed Kills)", - # "killstreakGroundKills": "Most ground based killstreak kills (Ground Control)", - # "distanceTraveledInVehicle": "Longest distance travelled in a vehicle (Cross Country)", - # "longestLife": "Longest life (Lifer)", - # "stunHits": "Most Stun Grenade hits (Stunner)", - # "spawnSelectFlag": "Most FOB Spawns (Objective Focused)", # Unsure - # "shotgunHeadshots": "Most Shotgun headshots (Boomstick)", - # "bombDefused": "Most defuses (Defuser)", - # "snapshotHits": "Most Snapshot Grenade hits (Photographer)", - # "noKillsWithDeath": "No kills with at least 1 death (Participant)", - # "killstreakAUAVAssists": "Most Advanced UAV assists (Target Rich Environment)", - # "killstreakPersonalUAVKills": "Most kills with a Personal Radar active (Nothing Personal)", - # "tacticalInsertionSpawns": "Most Tactical Insertions used (Revenant)", - # "launcherKills": "Most Launcher kills (Explosive)", - # "spawnSelectVehicle": "Most vehicle spawns (Oscar Mike)", - # "mostKillsLeastDeaths": "Most kills and fewest deaths (MVP)", - # "mostKills": "Most kills (The Feared)", - # "defends": "Most defend kills (Defense)", - # "timeSpentAsDriver": "Most time spent driving (Driver)", - # "": "" # WIP - Still adding more -} - # Initiating the API class api = API() COOKIE_FILE = 'cookie.txt' @@ -383,7 +23,7 @@ def save_to_file(data, filename, dir_name='stats'): with open(os.path.join(dir_name, filename), 'w') as json_file: json.dump(data, json_file, indent=4) -def get_and_save_data(player_name=None, all_stats=False, season_loot=False, identities=False, maps=False): +def get_and_save_data(player_name=None, all_stats=False, season_loot=False, identities=False, maps=False, info=False, friendFeed=False, eventFeed=False, cod_points=False, connected_accounts=False, settings=False): # Create the stats directory if it doesn't exist DIR_NAME = 'stats' if not os.path.exists(DIR_NAME): @@ -398,6 +38,12 @@ def get_and_save_data(player_name=None, all_stats=False, season_loot=False, iden with open(COOKIE_FILE, 'w') as f: f.write(api_key) + # # Check if userInfo.json exists, create it if it doesn't + USER_INFO_FILE = os.path.join('userInfo.json') + # if not os.path.exists(USER_INFO_FILE): + # with open(USER_INFO_FILE, 'w') as f: + # pass # Creates an empty file + # If player_name is not provided via command line, get it from user input if not player_name: player_name = input("Please enter the player's username (with #1234567): ") @@ -407,25 +53,47 @@ def get_and_save_data(player_name=None, all_stats=False, season_loot=False, iden # Retrieve data from API # First, determine if any specific optional arguments were given - if not (all_stats or season_loot or identities or maps): + if not (all_stats or season_loot or identities or maps or info or friendFeed or eventFeed or cod_points or connected_accounts or settings): # If no specific optional arguments are given, then default behavior: player_stats = api.ModernWarfare.fullData(platforms.Activision, player_name) match_info = api.ModernWarfare.combatHistory(platforms.Activision, player_name) save_to_file(player_stats, 'stats.json') save_to_file(match_info, 'match_info.json') - elif all_stats: - # If the all_stats argument is given: - player_stats = api.ModernWarfare.fullData(platforms.Activision, player_name) - match_info = api.ModernWarfare.combatHistory(platforms.Activision, player_name) - match_info = api.ModernWarfare.combatHistory(platforms.Activision, player_name) - season_loot_data = api.ModernWarfare.seasonLoot(platforms.Activision, player_name) - map_list = api.ModernWarfare.mapList(platforms.Activision) - identities_data = api.Me.loggedInIdentities() - save_to_file(player_stats, 'stats.json') - save_to_file(match_info, 'match_info.json') - save_to_file(season_loot_data, 'season_loot.json') - save_to_file(map_list, 'map_list.json') - save_to_file(identities_data, 'identities.json') + elif all_stats: # If the all_stats argument is given: + if os.path.exists(USER_INFO_FILE): # Check if the userInfo.json file exists + player_stats = api.ModernWarfare.fullData(platforms.Activision, player_name) + match_info = api.ModernWarfare.combatHistory(platforms.Activision, player_name) + season_loot_data = api.ModernWarfare.seasonLoot(platforms.Activision, player_name) + identities_data = api.Me.loggedInIdentities() + map_list = api.ModernWarfare.mapList(platforms.Activision) + info = api.Me.info() + friendFeed = api.Me.friendFeed() + eventFeed = api.Me.eventFeed() + cod_points = api.Me.codPoints() + connectedAccounts = api.Me.connectedAccounts() + settings = api.Me.settings() + save_to_file(player_stats, 'stats.json') + save_to_file(match_info, 'match_info.json') + save_to_file(season_loot_data, 'season_loot.json') + save_to_file(map_list, 'map_list.json') + save_to_file(identities_data, 'identities.json') + save_to_file(info, 'info.json') + save_to_file(friendFeed, 'friendFeed.json') + save_to_file(eventFeed, 'eventFeed.json') + save_to_file(cod_points, 'cp.json') + save_to_file(connectedAccounts, 'connectedAccounts.json') + save_to_file(settings, 'settings.json') + else: + player_stats = api.ModernWarfare.fullData(platforms.Activision, player_name) + match_info = api.ModernWarfare.combatHistory(platforms.Activision, player_name) + season_loot_data = api.ModernWarfare.seasonLoot(platforms.Activision, player_name) + identities_data = api.Me.loggedInIdentities() + map_list = api.ModernWarfare.mapList(platforms.Activision) + save_to_file(player_stats, 'stats.json') + save_to_file(match_info, 'match_info.json') + save_to_file(season_loot_data, 'season_loot.json') + save_to_file(map_list, 'map_list.json') + save_to_file(identities_data, 'identities.json') else: # For other specific optional arguments: if season_loot: @@ -438,23 +106,58 @@ def get_and_save_data(player_name=None, all_stats=False, season_loot=False, iden map_list = api.ModernWarfare.mapList(platforms.Activision) save_to_file(map_list, 'map_list.json') + if info: + info = api.Me.info() + save_to_file(info, 'info.json') + if friendFeed: + friendFeed = api.Me.friendFeed() + save_to_file(friendFeed, 'friendFeed.json') + if eventFeed: + eventFeed = api.Me.eventFeed() + save_to_file(eventFeed, 'eventFeed.json') + if cod_points: + cod_points = api.Me.codPoints() + save_to_file(cod_points, 'cp.json') + if connected_accounts: + connectedAccounts = api.Me.connectedAccounts() + save_to_file(connectedAccounts, 'connectedAccounts.json') + if settings: + settings = api.Me.settings() + save_to_file(settings, 'settings.json') + # Save results to a JSON file inside the stats directory -def recursive_key_replace(obj, replacements): +def recursive_key_replace(obj): if isinstance(obj, dict): new_obj = {} for key, value in obj.items(): new_key = replacements.get(key, key) if isinstance(value, str): new_value = replacements.get(value, value) - new_obj[new_key] = recursive_key_replace(new_value, replacements) + new_obj[new_key] = recursive_key_replace(new_value) else: - new_obj[new_key] = recursive_key_replace(value, replacements) + new_obj[new_key] = recursive_key_replace(value) return new_obj elif isinstance(obj, list): - return [recursive_key_replace(item, replacements) for item in obj] + return [recursive_key_replace(item) for item in obj] else: return replacements.get(obj, obj) if isinstance(obj, str) else obj +def clean_json_files(*filenames, dir_name='stats'): + regex_pattern = r'<span class="|</span>|">|mp-stat-items|kills-value|headshots-value|username|game-mode|kdr-value' + replace = '' + + for filename in filenames: + file_path = os.path.join(dir_name, filename) + if os.path.exists(file_path): + with open(file_path, 'r') as file: + content = file.read() + modified_content = re.sub(regex_pattern, replace, content) + with open(file_path, 'w') as file: + file.write(modified_content) + print(f"Cleaned {filename}.") + else: + print(f"{filename} does not exist, skipping.") + def sort_data(data): if isinstance(data, dict): for key, value in data.items(): @@ -558,7 +261,7 @@ def beautify_data(): with open(file_path, 'r') as file: data = json.load(file) replace_time_and_duration_recursive(data) - data = recursive_key_replace(data, replacements) + data = recursive_key_replace(data) data = sort_data(data) with open(file_path, 'w') as file: json.dump(data, file, indent=4) @@ -569,7 +272,7 @@ def beautify_match_data(): with open(file_path, 'r') as file: data = json.load(file) replace_time_and_duration_recursive(data) - data = recursive_key_replace(data, replacements) + data = recursive_key_replace(data) with open(file_path, 'w') as file: json.dump(data, file, indent=4) print(f"Keys replaced in {file_path}.") @@ -639,14 +342,22 @@ def main(): group_data.add_argument("-p", "--player_name", type=str, help="Player's username (with #1234567)") group_data.add_argument("-a", "--all_stats", action="store_true", help="Fetch all the different types of stats data") group_data.add_argument("-sl", "--season_loot", action="store_true", help="Fetch only the season loot data") - group_data.add_argument("-i", "--identities", action="store_true", help="Fetch only the logged-in identities data") + group_data.add_argument("-id", "--identities", action="store_true", help="Fetch only the logged-in identities data") group_data.add_argument("-m", "--maps", action="store_true", help="Fetch only the map list data") + group_data.add_argument("-i", "--info", action="store_true", help="Fetch only general information") + group_data.add_argument("-f", "--friendFeed", action="store_true", help="Fetch only your friend feed") + group_data.add_argument("-e", "--eventFeed", action="store_true", help="Fetch only your event feed") + group_data.add_argument("-cp", "--cod_points", action="store_true", help="Fetch only your COD Point balance") + group_data.add_argument("-ca", "--connected_accounts", action="store_true", help="Fetch only the map list data") + group_data.add_argument("-s", "--settings", action="store_true", help="Fetch only your account settings") # Add arguments for Cleaning Options group_cleaning.add_argument("-c", "--clean", action="store_true", help="Beautify all data") group_cleaning.add_argument("-sm", "--split_matches", action="store_true", help="Split the matches into separate JSON files within the 'matches' subfolder") group_cleaning.add_argument("-csd", "--clean_stats_data", action="store_true", help="Beautify the data and convert to human-readable strings in stats.json") group_cleaning.add_argument("-cmd", "--clean_match_data", action="store_true", help="Beautify the match data and convert to human-readable strings in match_info.json") + group_cleaning.add_argument("-cff", "--clean_friend_feed", action="store_true", help="Clean the friend feed data") + group_cleaning.add_argument("-cef", "--clean_event_feed", action="store_true", help="Clean the event feed data") args = parser.parse_args() @@ -669,8 +380,13 @@ def main(): elif args.clean: beautify_data() beautify_match_data() + clean_json_files('friendFeed.json', 'eventFeed.json') + elif args.clean_friend_feed: + clean_json_files('friendFeed.json') + elif args.clean_event_feed: + clean_json_files('eventFeed.json') else: - get_and_save_data(args.player_name, args.all_stats, args.season_loot, args.identities, args.maps) + get_and_save_data(args.player_name, args.all_stats, args.season_loot, args.identities, args.maps, args.info, args.friendFeed, args.eventFeed, args.cod_points, args.connected_accounts, args.settings) if __name__ == "__main__": main() \ No newline at end of file