diff --git a/README.md b/README.md index 4bba31c..296bd14 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ # galaxy-integration-humblebundle -COMING: WHEN IT'S READY \ No newline at end of file +Integration for GOG Galaxy 2.0 + +## Installation + +- git clone https://github.com/UncleGoogle/galaxy-integration-humblebundle.git +- cd galaxy-integration-humblebundle.git +- python tasks.py install +- python tasks.py dist + +## Features + +* Listing games from HumbleBundle library diff --git a/src/humblegame.py b/src/humblegame.py new file mode 100644 index 0000000..632213d --- /dev/null +++ b/src/humblegame.py @@ -0,0 +1,19 @@ +from galaxy.api.types import Game, LicenseType, LicenseInfo + + +class HumbleGame: + def __init__(self, data): + for k, v in data.items(): + setattr(self, k, v) + + def in_galaxy_format(self): + licence = LicenseInfo(LicenseType.SinglePurchase) + dlcs = [] # not supported for now + return Game(self.machine_name, self.human_name, dlcs, licence) + + def __repr__(self): + return str(self) + + def __str__(self): + return f"HumbleGame({self.human_name}, {self.downloads})" + diff --git a/src/plugin.py b/src/plugin.py index 7614742..fca6ea9 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -10,47 +10,65 @@ from galaxy.api.errors import InvalidCredentials from version import __version__ -from backend import Backend +from webservice import AuthorizedHumbleAPI +from humblegame import HumbleGame AUTH_PARAMS = { "window_title": "Login to HumbleBundle", "window_width": 560, "window_height": 610, - "start_uri": "https://www.humblebundle.com/login?goto=/home/library", # or https://www.humblebundle.com/account-start?goto=home" + "start_uri": "https://www.humblebundle.com/login?goto=/home/library", + # or https://www.humblebundle.com/account-start?goto=home" "end_uri_regex": "^" + re.escape("https://www.humblebundle.com/home/library") } class HumbleBundlePlugin(Plugin): + GAME_PLATFORMS = ['windows', 'mac', 'linux'] # TODO think about 'android' + DLC_PLATFORMS = ['ebook', 'audio'] # TODO push those with base game + def __init__(self, reader, writer, token): super().__init__(Platform.HumbleBundle, __version__, reader, writer, token) - self._backend = Backend() + self._api = AuthorizedHumbleAPI() + self._games = [] async def authenticate(self, stored_credentials=None): if not stored_credentials: return NextStep("web_session", AUTH_PARAMS) logging.info('stored credentials found') - user_id, user_name = await self._backend.authenticate(stored_credentials) + user_id, user_name = await self._api.authenticate(stored_credentials) return Authentication(user_id, user_name) async def pass_login_credentials(self, step, credentials, cookies): logging.info(json.dumps(cookies, indent=4)) auth_cookie = next(filter(lambda c: c['name'] == '_simpleauth_sess', cookies)) - logging.debug(f'===auth cookie, type {type(auth_cookie)}, val: {auth_cookie}') self.store_credentials(auth_cookie) - user_id, user_name = await self._backend.authenticate(auth_cookie) + user_id, user_name = await self._api.authenticate(auth_cookie) return Authentication(user_id, user_name) + def _is_game(self, sub): + whitelist = self.GAME_PLATFORMS + default = False + return next(filter(lambda x: x['platform'] in whitelist, sub['downloads']), default) + async def get_owned_games(self): - orders = await self._backend.get_orders_list() - log.info(orders) - return [] + 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)}') + for sub in details['subproducts']: + if self._is_game(sub): + games.append(HumbleGame(sub)) + + self.games = games + return [g.in_galaxy_format() for g in games] def shutdown(self): - self._backend._session.close() + self._api._session.close() def main(): create_and_run_plugin(HumbleBundlePlugin, sys.argv) diff --git a/src/backend.py b/src/webservice.py similarity index 51% rename from src/backend.py rename to src/webservice.py index da37235..1af6a0d 100644 --- a/src/backend.py +++ b/src/webservice.py @@ -6,14 +6,13 @@ from galaxy.http import create_client_session -PROCESS_LOGIN = "https://www.humblebundle.com/processlogin" -ORDER_LIST_URL = "https://www.humblebundle.com/api/v1/user/order" -ORDER_URL = "https://www.humblebundle.com/api/v1/order/{order_id}" - -class Backend: - _default_params = {"ajax": "true"} - _default_headers = { +class AuthorizedHumbleAPI: + _PROCESS_LOGIN = "https://www.humblebundle.com/processlogin" + _ORDER_LIST_URL = "https://www.humblebundle.com/api/v1/user/order" + _ORDER_URL = "https://www.humblebundle.com/api/v1/order/{}" + _DEFAULT_PARAMS = {"ajax": "true"} + _DEFAULT_HEADERS = { "Accept": "application/json", "Accept-Charset": "utf-8", "Keep-Alive": "true", @@ -22,36 +21,37 @@ class Backend: } def __init__(self): self._simpleauth_sess = None - self._session = create_client_session(headers=self._default_headers) - - async def authenticate(self, auth_cookie: dict): - cookie = SimpleCookie() - cookie[auth_cookie['name']] = auth_cookie['value'] - self._session.cookie_jar.update_cookies(cookie) - user_id = self.decode_user_id(auth_cookie['value']) - return (user_id, user_id) + self._session = create_client_session(headers=self._DEFAULT_HEADERS) - async def _request(*args, **kwargs): + async def _request(self, *args, **kwargs): if 'params' not in kwargs: - kwargs['params'] = self._default_params + kwargs['params'] = self._DEFAULT_PARAMS return await self._session.request(*args, **kwargs) - def decode_user_id(self, _simpleauth_sess): + def _decode_user_id(self, _simpleauth_sess): info = _simpleauth_sess.split('|')[0] info_padded = info + '==' decoded = json.loads(base64.b64decode(info_padded)) logging.debug(decoded) return decoded['user_id'] - async def get_orders_list(self): - res = await self._session.request('get', - 'https://www.humblebundle.com/api/v1/user/order?ajax=true') - return res.json() + async def authenticate(self, auth_cookie: dict): + cookie = SimpleCookie() + cookie[auth_cookie['name']] = auth_cookie['value'] + self._session.cookie_jar.update_cookies(cookie) + user_id = self._decode_user_id(auth_cookie['value']) + return (user_id, user_id) + + async def get_gamekeys(self): + res = await self._request('get', self._ORDER_LIST_URL) + parsed = await res.json() + logging.info(f"The order list:\n{parsed}") + gamekeys = [it["gamekey"] for it in parsed] + return gamekeys async def get_order_details(self, gamekey): - res = await self._session.request('get', - f'https://www.humblebundle.com/api/v1/order/{gamekey}?all_tpkds=true') - return res.json() + res = await self._request('get', self._ORDER_URL.format(gamekey)) + return await res.json() diff --git a/tasks.py b/tasks.py index a834068..0b225d7 100644 --- a/tasks.py +++ b/tasks.py @@ -19,6 +19,7 @@ def install(): subprocess.run(["pip", "install", "-r", REQUIREMENTS]) + def build(output=DIST_PLUGIN): print('removing', output) if os.path.exists(output): @@ -57,6 +58,7 @@ def build(output=DIST_PLUGIN): with open(os.path.join(output, "manifest.json"), "w") as file_: json.dump(manifest, file_, indent=4) + 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: @@ -70,14 +72,17 @@ def dist(output=DIST_PLUGIN, galaxy_path=GALAXY_PATH): print(f'Reopening Galaxy from {galaxy_path}') subprocess.run([galaxy_path]) + def debug(output=DIST_PLUGIN, galaxy_path=GALAXY_PATH): print('copying source code ...') for file_ in glob("src/*.py"): shutil.copy(file_, output) + def test(): subprocess.run(["pytest"]) + def main(): args = parser.parse_args() if args.command == 'install': @@ -90,11 +95,9 @@ def main(): debug() elif args.command == 'test': test() + else: + print(f'command {args.command} not exits') + if __name__ == "__main__": main() - - - - -