From ab243b3c4beb2c2431d5e92e8b7e624964114137 Mon Sep 17 00:00:00 2001 From: Mesco Date: Fri, 19 Jul 2019 12:33:14 +0200 Subject: [PATCH 1/3] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) 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 From 1a8a1953640bee487fa5b7aa2df2d982ceff0c1b Mon Sep 17 00:00:00 2001 From: UncleGoogle Date: Fri, 19 Jul 2019 12:38:07 +0200 Subject: [PATCH 2/3] Fix auth with escaped cookies handle web exceptions possible fix when user cookie is escaped by galaxy cef rm sensitive data; check cookie valid during auth bump ver --- .gitignore | 2 +- src/plugin.py | 5 +++-- src/version.py | 2 +- src/webservice.py | 31 ++++++++++++++++++++----------- tasks.py | 3 +++ test.py | 19 ++++++++++++------- 6 files changed, 40 insertions(+), 22 deletions(-) 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/src/plugin.py b/src/plugin.py index a1d5538..966954f 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -43,10 +43,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): @@ -59,7 +60,7 @@ def is_game(sub): 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)}') + logging.info(f'Parsing details of order {gamekey}:\n{json.dumps(details, indent=4)}') for sub in details['subproducts']: if is_game(sub): games[sub['machine_name']] = HumbleGame(sub) diff --git a/src/version.py b/src/version.py index 7141c42..d1f2e39 100644 --- a/src/version.py +++ b/src/version.py @@ -1 +1 @@ -__version__ = "0.1" \ No newline at end of file +__version__ = "0.1.1" \ No newline at end of file diff --git a/src/webservice.py b/src/webservice.py index 6dc9a4b..dd40b97 100644 --- a/src/webservice.py +++ b/src/webservice.py @@ -4,7 +4,7 @@ import base64 import logging -from galaxy.http import create_client_session +from galaxy.http import create_client_session, handle_exception class AuthorizedHumbleAPI: @@ -24,24 +24,33 @@ def __init__(self): self._session = create_client_session(headers=self._DEFAULT_HEADERS) async def _request(self, *args, **kwargs): - if 'params' not in kwargs: - kwargs['params'] = self._DEFAULT_PARAMS - return await self._session.request(*args, **kwargs) + with handle_exception(): + if 'params' not in kwargs: + kwargs['params'] = self._DEFAULT_PARAMS + return await self._session.request(*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/tasks.py b/tasks.py index e578ddb..15859a3 100644 --- a/tasks.py +++ b/tasks.py @@ -69,7 +69,10 @@ def dist(output=DIST_PLUGIN, galaxy_path=GALAXY_PATH): for proc in psutil.process_iter(attrs=['exe'], ad_value=''): if proc.info['exe'] == galaxy_path: print(f'Galaxy at {galaxy_path} is running!. Terminating...') + for child in proc.children(): + child.terminate() proc.terminate() + break else: print('Galaxy instance not found.') diff --git a/test.py b/test.py index 28a84c8..54afbfc 100644 --- a/test.py +++ b/test.py @@ -3,16 +3,20 @@ from pathlib import Path import asyncio + +CREDENTIALS_FILE = "credentials-other" + + 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') From f8dab9de91adae213bfddafe3fa6d49d2b813eb9 Mon Sep 17 00:00:00 2001 From: Mieszko Date: Sun, 21 Jul 2019 19:17:59 +0200 Subject: [PATCH 3/3] optimize fetching order details --- src/plugin.py | 20 +++++++++++++++----- src/version.py | 2 +- test.py | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/plugin.py b/src/plugin.py index 966954f..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 @@ -58,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 d1f2e39..48fef32 100644 --- a/src/version.py +++ b/src/version.py @@ -1 +1 @@ -__version__ = "0.1.1" \ No newline at end of file +__version__ = "0.1.2" \ No newline at end of file diff --git a/test.py b/test.py index 54afbfc..79a907f 100644 --- a/test.py +++ b/test.py @@ -4,7 +4,7 @@ import asyncio -CREDENTIALS_FILE = "credentials-other" +CREDENTIALS_FILE = "credentials.data" if __name__ == "__main__":