diff --git a/.gitignore b/.gitignore index 97cd619..1cbccb9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ .env galaxy/ info.md -credentials.data +credentials* # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index 4f0c49a..bac952b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,15 @@ This plugin is currenly in early development stage. ## Installation +Unpack `humblebundle_v{}.zip` asset from latest [release][1] to: +- (WINDOWS) `%localappdata%\GOG.com\Galaxy\plugins\installed` +- (MACOS) `~/Library/Application Support/GOG.com/Galaxy/plugins/installed` + +or build from source code (python3.6 or higher required): + 1. `git clone --recursive https://github.com/UncleGoogle/galaxy-integration-humblebundle.git` 2. `cd galaxy-integration-humblebundle` 3. `python tasks.py install` 4. `python tasks.py dist` + +[1]: https://github.com/UncleGoogle/galaxy-integration-humblebundle/releases diff --git a/src/plugin.py b/src/plugin.py index a1d5538..7b5b1e6 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -1,4 +1,6 @@ import sys +import time +import asyncio import os import json import logging @@ -43,10 +45,11 @@ async def authenticate(self, stored_credentials=None): return Authentication(user_id, user_name) async def pass_login_credentials(self, step, credentials, cookies): + logging.debug(json.dumps(cookies, indent=2)) auth_cookie = next(filter(lambda c: c['name'] == '_simpleauth_sess', cookies)) - self.store_credentials(auth_cookie) user_id, user_name = await self._api.authenticate(auth_cookie) + self.store_credentials(auth_cookie) return Authentication(user_id, user_name) async def get_owned_games(self): @@ -57,12 +60,20 @@ def is_game(sub): games = {} gamekeys = await self._api.get_gamekeys() - for gamekey in gamekeys: - details = await self._api.get_order_details(gamekey) - # logging.info(f'Parsing details of order {gamekey}:\n{json.dumps(details, indent=4)}') + requests = [self._api.get_order_details(x) for x in gamekeys] + + logging.info(f'Fetching info about {len(requests)} orders started...') + all_games_details = await asyncio.gather(*requests) + logging.info('Fetching info finished') + + for details in all_games_details: for sub in details['subproducts']: - if is_game(sub): - games[sub['machine_name']] = HumbleGame(sub) + try: + if is_game(sub): + games[sub['machine_name']] = HumbleGame(sub) + except Exception as e: + logging.error(f'Error while parsing subproduct {sub}: {repr(e)}') + continue self._games = games return [g.in_galaxy_format() for g in games.values()] diff --git a/src/version.py b/src/version.py index 7141c42..48fef32 100644 --- a/src/version.py +++ b/src/version.py @@ -1 +1 @@ -__version__ = "0.1" \ No newline at end of file +__version__ = "0.1.2" \ No newline at end of file diff --git a/src/webservice.py b/src/webservice.py index 55f195b..dd40b97 100644 --- a/src/webservice.py +++ b/src/webservice.py @@ -31,18 +31,26 @@ async def _request(self, *args, **kwargs): def _decode_user_id(self, _simpleauth_sess): info = _simpleauth_sess.split('|')[0] - # get rid of escape characters - info = bytes(info, "utf-8").decode("unicode_escape") - info_padded = info + '==' - decoded = json.loads(base64.b64decode(info_padded)) - logging.debug(decoded) + logging.debug(f'user info cookie: {info}') + info += '==' # ensure full padding + decoded = json.loads(base64.b64decode(info)) return decoded['user_id'] async def authenticate(self, auth_cookie: dict): + # recreate original cookie cookie = SimpleCookie() - cookie[auth_cookie['name']] = auth_cookie['value'] + cookie_val = bytes(auth_cookie['value'], "utf-8").decode("unicode_escape") + # some users have cookies with escaped characters, some not... + # for the first group strip quotes: + cookie_val = cookie_val.replace('"', '') + cookie[auth_cookie['name']] = cookie_val + + user_id = self._decode_user_id(cookie_val) self._session.cookie_jar.update_cookies(cookie) - user_id = self._decode_user_id(auth_cookie['value']) + + # check if auth session is valid + await self.get_gamekeys() + return (user_id, user_id) async def get_gamekeys(self): diff --git a/test.py b/test.py index 28a84c8..79a907f 100644 --- a/test.py +++ b/test.py @@ -3,16 +3,20 @@ from pathlib import Path import asyncio + +CREDENTIALS_FILE = "credentials.data" + + if __name__ == "__main__": async def run_server_connection(reader, writer): credentials = "" - path = Path("credentials.data") + path = Path(CREDENTIALS_FILE) if not path.exists(): path.touch() - with open("credentials.data", "r") as f: + with open(CREDENTIALS_FILE, "r") as f: data = f.read() if data: credentials = json.loads(data) @@ -37,15 +41,16 @@ async def run_server_connection(reader, writer): tokens = json.loads(tokens.decode()) try: if 'method' in tokens and tokens['method'] == 'store_credentials': - with open('credentials.data', 'w') as f: + print(f'overwriting {CREDENTIALS_FILE}') + with open(CREDENTIALS_FILE, 'w') as f: f.write(json.dumps(tokens['params'])) print("tokens", tokens) ret = await reader.readline() print("ret", ret) except Exception as e: - print(f'{str(e)}: Removing credentials') - os.remove('credentials.data') + print(f'{str(e)}.\n Probably you need refresh it?') + print("owned") writer.write(b'{"jsonrpc": "2.0", "id": "3", "method": "import_owned_games"}\n') @@ -59,8 +64,8 @@ async def run_server_connection(reader, writer): # ret = await reader.readline() # print("ret", ret) - print("install_game") - writer.write(b'{"jsonrpc": "2.0", "method": "install_game", "params":{"game_id": "samorost2"}}\n') + # print("install_game") + # writer.write(b'{"jsonrpc": "2.0", "method": "install_game", "params":{"game_id": "samorost2"}}\n') # print("launch_game") # writer.write(b'{"jsonrpc": "2.0", "method": "launch_game", "params":{"game_id": "samorost2"}}\n')