def has_cached_app_depot_info(self, app_id): if app_id in self.app_depots: return True cached_appinfo = UserCacheFile("appinfo/{}.json".format(app_id)) if cached_appinfo.exists(): return True return False
def get_manifest(self, app_id, depot_id, manifest_gid, decrypt=True): key = (app_id, depot_id, manifest_gid) cached_manifest = UserCacheFile("manifests/{}_{}_{}".format(*key)) if key not in self.manifests: self.get_cached_manifest(*key) # if we still dont have a manifest, load it from CDN if key not in self.manifests: manifest = CDNClient.get_manifest(self, app_id, depot_id, manifest_gid, decrypt) # cache the manifest with cached_manifest.open('wb') as fp: # if we have the key decrypt the manifest before caching if manifest.filenames_encrypted and manifest.depot_id in self.depot_keys: manifest = self.DepotManifestClass( self, manifest.app_id, manifest.serialize(compress=False)) manifest.decrypt_filenames( self.depot_keys[manifest.depot_id]) fp.write(manifest.serialize(compress=False)) return self.manifests[key]
def get_product_info(self, apps=[], packages=[], *args, **kwargs): resp = {'apps': {}, 'packages': {}} # if we have cached info for all apps, just serve from cache if apps and all(map(self.has_cached_appinfo, apps)): self._LOG.debug("Serving appinfo from cache") for app_id in apps: resp['apps'][app_id] = self.get_cached_appinfo(app_id) apps = [] if apps or packages: self._LOG.debug("Fetching product info") fresh_resp = SteamClient.get_product_info(self, apps, packages, *args, **kwargs) if apps: for app_id, appinfo in fresh_resp['apps'].items(): if not appinfo['_missing_token']: UserCacheFile("appinfo/{}.json".format( app_id)).write_json(appinfo) resp = fresh_resp else: resp['packages'] = fresh_resp['packages'] return resp
def endpoint_autocomplete(prefix, parsed_args, **kwargs): interfaces = UserCacheFile('webapi_interfaces.json').read_json() if not interfaces: warn("To enable endpoint tab completion run: steamctl webapi list") return [] return ('{}.{}'.format(a['name'], b['name']) for a in interfaces for b in a['methods'])
def check_for_changes(self): changefile = UserCacheFile('last_change_number') change_number = 0 if changefile.exists(): try: change_number = int(changefile.read_full()) except: changefile.remove() self._LOG.debug("Checking PICS for app changes") resp = self.steam.get_changes_since(change_number, True, False) if resp.force_full_app_update: change_number = 0 if resp.current_change_number != change_number: with changefile.open('w') as fp: fp.write(str(resp.current_change_number)) changed_apps = set((entry.appid for entry in resp.app_changes)) if change_number == 0 or changed_apps: self._LOG.debug("Checking for outdated cached appinfo files") for appinfo_file in UserCacheDirectory('appinfo').iter_files( '*.json'): app_id = int(appinfo_file.filename[:-5]) if change_number == 0 or app_id in changed_apps: appinfo_file.remove()
def get_manifest(self, app_id, depot_id, manifest_gid, decrypt=True): key = (app_id, depot_id, manifest_gid) cached_manifest = UserCacheFile("manifests/{}_{}_{}".format(*key)) if decrypt and depot_id not in self.depot_keys: self.get_depot_key(app_id, depot_id) manifest = self.get_cached_manifest(*key) # if manifest not cached, download from CDN if not manifest: manifest = CDNClient.get_manifest(self, app_id, depot_id, manifest_gid, decrypt) # cache the manifest with cached_manifest.open('wb') as fp: fp.write(manifest.serialize(compress=False)) return self.manifests[key]
def get_cached_manifest(self, app_id, depot_id, manifest_gid): key = (app_id, depot_id, manifest_gid) if key in self.manifests: return self.manifests[key] # if we don't have the manifest loaded, check cache cached_manifest = UserCacheFile("manifests/{}_{}_{}".format(app_id, depot_id, manifest_gid)) # we have a cached manifest file, load it if cached_manifest.exists(): with cached_manifest.open('r+b') as fp: try: manifest = self.DepotManifestClass(self, app_id, fp.read()) except Exception as exp: self._LOG.debug("Error parsing cached manifest: %s", exp) else: # if its not empty, load it if manifest.gid > 0: self.manifests[key] = manifest # update cached file if we have depot key for it if manifest.filenames_encrypted and manifest.depot_id in self.depot_keys: manifest.decrypt_filenames(self.depot_keys[manifest.depot_id]) fp.seek(0) fp.write(manifest.serialize(compress=False)) fp.truncate() return manifest # empty manifest files shouldn't exist, handle it gracefully by removing the file if key not in self.manifests: self._LOG.debug("Found cached manifest, but encountered error or file is empty") cached_manifest.remove()
def get_app_depot_info(self, app_id): if app_id not in self.app_depots: cached_appinfo = UserCacheFile("appinfo/{}.json".format(app_id)) appinfo = None if cached_appinfo.exists(): appinfo = cached_appinfo.read_json() if not appinfo: app_req = {'appid': app_id} token = self.get_app_access_token(app_id) if token: app_req['access_token'] = token try: appinfo = self.steam.get_product_info([app_req])['apps'][app_id] except KeyError: raise SteamError("Invalid app id") if appinfo['_missing_token']: raise SteamError("No access token available") cached_appinfo.write_json(appinfo) self.app_depots[app_id] = appinfo.get('depots', {}) return self.app_depots[app_id]
def parameter_autocomplete(prefix, parsed_args, **kwargs): interfaces = UserCacheFile('webapi_interfaces.json').read_json() if not interfaces: warn("To enable endpoint tab completion run: steamctl webapi list") return [] parameters = [] ainterface, amethod = parsed_args.endpoint.split('.', 1) for interface in filter(lambda a: a['name'] == ainterface, interfaces): for method in filter(lambda b: b['name'] == amethod, interface['methods']): for param in method['parameters']: if param['name'][-3:] == '[0]': param['name'] = param['name'][:-3] parameters.append(param['name'] + '=') break return parameters
def get_app_depot_info(self, app_id): if app_id not in self.app_depots: cached_appinfo = UserCacheFile("appinfo/{}.json".format(app_id)) appinfo = None if cached_appinfo.exists(): appinfo = cached_appinfo.read_json() if not appinfo: appinfo = self.steam.get_product_info([app_id])['apps'][app_id] cached_appinfo.write_json(appinfo) self.app_depots[app_id] = appinfo['depots'] return self.app_depots[app_id]
def get_app_names(): papps = SqliteDict(UserCacheFile("app_names.sqlite3").path) try: last = int(papps[-7]) # use a key that will never be used except KeyError: last = 0 if last < time(): resp = webapi.get('ISteamApps', 'GetAppList', version=2) apps = resp.get('applist', {}).get('apps', []) if not apps and len(papps) == 0: raise RuntimeError("Failed to fetch apps") for app in apps: papps[int(app['appid'])] = app['name'] papps[-7] = str(int(time()) + 86400) papps.commit() return papps
def get_cached_appinfo(self, app_id): cache_file = UserCacheFile("appinfo/{}.json".format(app_id)) if cache_file.exists(): return cache_file.read_json()
def has_cached_appinfo(self, app_id): return UserCacheFile("appinfo/{}.json".format(app_id)).exists()
def cmd_webapi_call(args): # load key=value pairs. Stuff thats start with [ is a list, so parse as json try: params = { k: (json.loads(v) if v[0:1] == '[' else v) for k, v in args.params } except Exception as exp: LOG.error("Error parsing params: %s", str(exp)) return 1 # error apicall = webapi.get version = args.version or 1 if args.method == 'POST': apicall = webapi.post webapi_map = {} # load cache webapi_interfaces if available for interface in (UserCacheFile('webapi_interfaces.json').read_json() or {}): for method in interface['methods']: key = "{}.{}".format(interface['name'], method['name']) if key not in webapi_map or webapi_map[key][1] < method['version']: webapi_map[key] = method['httpmethod'], method['version'] # if --method or --version are unset, take them the cache # This will the call POST if needed with specifying explicity # This will prever the highest version of a method if args.endpoint in webapi_map: if args.method is None: if webapi_map[args.endpoint][0] == 'POST': apicall = webapi.post if args.version is None: version = webapi_map[args.endpoint][1] # drop reserved words. these have special meaning for steam.webapi for reserved in ('key', 'format', 'raw', 'http_timeout', 'apihost', 'https'): params.pop(reserved, None) # load key if available params.setdefault('key', args.apikey or get_webapi_key()) if args.format != 'text': params[ 'format'] = 'json' if args.format == 'json_line' else args.format params['raw'] = True try: interface, method = args.endpoint.split('.', 1) resp = apicall(interface, method, version, params=params) except Exception as exp: LOG.error("%s failed: %s", args.endpoint, str(exp)) if getattr(exp, 'response', None): LOG.error("Response body: %s", exp.response.text) return 1 # error # by default we print json, other formats are shown as returned from api if args.format == 'json': json.dump(json.loads(resp.rstrip('\n\t\x00 ')), sys.stdout, indent=4, sort_keys=True) print('') else: print(resp)
def cmd_webapi_list(args): params = {} params.setdefault('key', args.apikey or get_webapi_key()) if args.format != 'text': params[ 'format'] = 'json' if args.format == 'json_line' else args.format params['raw'] = True try: resp = webapi.get('ISteamWebAPIUtil', 'GetSupportedAPIList', params=params) if args.format == 'text': interfaces = resp['apilist']['interfaces'] UserCacheFile('webapi_interfaces.json').write_json(interfaces) except Exception as exp: LOG.error("GetSupportedAPIList failed: %s", str(exp)) if getattr(exp, 'response', None): LOG.error("Response body: %s", exp.response.text) return 1 # error if args.format != 'text': if args.format == 'json': json.dump(json.loads(resp), sys.stdout, indent=4, sort_keys=True) print('') else: print(resp) return for interface in interfaces: for method in interface['methods']: if args.search: if args.search.lower() not in "{}.{}".format( interface['name'], method['name']).lower(): continue out = "{:>4} {}.{} v{} {}".format( method['httpmethod'], interface['name'], method['name'], method['version'], ('- ' + method['description']) if 'description' in method else '', ) print(out) if args.verbose: for param in method.get('parameters', []): name = param['name'] if name[-3:] == '[0]': name = name[:-3] print(" {:<10} {}{:<10} {}".format( param['type'], ' ' if param['optional'] else '*', name, ('- ' + param['description']) if 'description' in param else '', )) print('')