import aiohttp import async_timeout import os import zipfile import json import sqlite3 import pydest from pydest.dbase import DBase MANIFEST_ZIP = 'manifest_zip' class Manifest: def __init__(self, api): self.api = api self.manifest_files = {'en': '', 'fr': '', 'es': '', 'de': '', 'it': '', 'ja': '', 'pt-br': '', 'es-mx': '', 'ru': '', 'pl': '', 'zh-cht': ''} async def decode_hash(self, hash_id, definition, language): """Get the corresponding static info for an item given it's hash value Args: hash_id: The unique identifier of the entity to decode definition: The type of entity to be decoded (ex. 'DestinyClassDefinition') Returns: dict: json corresponding to the given hash_id and definition Raises: PydestException """ if language not in self.manifest_files.keys(): raise pydest.PydestException("Unsupported language: {}".format(language)) if self.manifest_files.get(language) == '': await self.update_manifest(language) # Identifier is different for the DestinyHistorialStatsDefinition table if definition == 'DestinyHistoricalStatsDefinition': hash_id = '"{}"'.format(hash_id) identifier = 'key' else: hash_id = self._twos_comp_32(hash_id) identifier = "id" with DBase(self.manifest_files.get(language)) as db: try: res = db.query(hash_id, definition, identifier) except sqlite3.OperationalError as e: if e.args[0].startswith('no such table'): raise pydest.PydestException("Invalid definition: {}".format(definition)) else: raise e if len(res) > 0: return json.loads(res[0][0]) else: raise pydest.PydestException("No entry found with id: {}".format(hash_id)) async def update_manifest(self, language): """Download the latest manifest file for the given language if necessary Args: language: The language corresponding to the manifest to update Raises: PydestException """ if language not in self.manifest_files.keys(): raise pydest.PydestException("Unsupported language: {}".format(language)) json = await self.api.get_destiny_manifest() if json['ErrorCode'] != 1: raise pydest.PydestException("Could not retrieve Manifest from Bungie.net") manifest_url = 'https://www.bungie.net' + json['Response']['mobileWorldContentPaths'][language] manifest_file_name = manifest_url.split('/')[-1] if not os.path.isfile(manifest_file_name): # Manifest doesn't exist, or isn't up to date # Download and extract the current manifest # Remove the zip file once finished filename = await self._download_file(manifest_url, MANIFEST_ZIP) if os.path.isfile('./{}'.format(MANIFEST_ZIP)): zip_ref = zipfile.ZipFile('./{}'.format(MANIFEST_ZIP), 'r') zip_ref.extractall('./') zip_ref.close() os.remove(MANIFEST_ZIP) else: raise pydest.PydestException("Could not retrieve Manifest from Bungie.net") self.manifest_files[language] = manifest_file_name async def _download_file(self, url, name): """Async file download Args: url (str): The URL from which to download the file name (str): The name to give to the downloaded file """ with async_timeout.timeout(10): async with self.api.session.get(url) as response: filename = os.path.basename(name) with open(filename, 'wb') as f_handle: while True: chunk = await response.content.read(1024) if not chunk: break f_handle.write(chunk) return await response.release() def _twos_comp_32(self, val): val = int(val) if (val & (1 << (32 - 1))) != 0: val = val - (1 << 32) return val