Пример #1
0
    def __init__(self, reader, writer, token):
        super().__init__(Platform.HumbleBundle, __version__, reader, writer,
                         token)
        self._api = AuthorizedHumbleAPI()
        self._download_resolver = HumbleDownloadResolver()
        self._app_finder = AppFinder()
        self._settings = Settings()
        self._library_resolver = None
        self._subscription_months: List[ChoiceMonth] = []

        self._owned_games: Dict[str, HumbleGame] = {}
        self._trove_games: Dict[str, TroveGame] = {}
        self._choice_games = {
        }  # for now model.subscription.ChoiceContet or Extras TODO consider adding to model.game

        self._local_games = {}
        self._cached_game_states = {}

        self._getting_owned_games = asyncio.Lock()
        self._owned_check: asyncio.Task = asyncio.create_task(asyncio.sleep(8))
        self._statuses_check: asyncio.Task = asyncio.create_task(
            asyncio.sleep(4))
        self._installed_check: asyncio.Task = asyncio.create_task(
            asyncio.sleep(4))

        self._rescan_needed = True
        self._under_installation = set()
    def __init__(self, reader, writer, token):
        super().__init__(Platform.HumbleBundle, __version__, reader, writer, token)
        self._api = AuthorizedHumbleAPI()
        self._download_resolver = HumbleDownloadResolver()
        self._app_finder = AppFinder
        self._settings = None
        self._library_resolver = None

        self._owned_games = {}
        self._local_games = {}
        self._cached_game_states = {}

        self._getting_owned_games = asyncio.Event()
        self._check_owned_task = asyncio.create_task(asyncio.sleep(0))
        self._check_installed_task = asyncio.create_task(asyncio.sleep(5))
        self._check_statuses_task = asyncio.create_task(asyncio.sleep(2))

        self.__under_instalation = set()
    def __init__(self, reader, writer, token):
        super().__init__(Platform.HumbleBundle, __version__, reader, writer,
                         token)
        self._api = AuthorizedHumbleAPI()
        self._download_resolver = HumbleDownloadResolver()
        self._app_finder = AppFinder()
        self._settings = Settings()
        self._library_resolver = None

        self._owned_games = {}
        self._local_games = {}
        self._cached_game_states = {}

        self._getting_owned_games = asyncio.Lock()
        self._owned_check: asyncio.Task = asyncio.create_task(asyncio.sleep(8))
        self._statuses_check: asyncio.Task = asyncio.create_task(
            asyncio.sleep(4))
        self._installed_check: asyncio.Task = asyncio.create_task(
            asyncio.sleep(4))

        self._rescan_needed = True
        self._under_installation = set()
Пример #4
0
class HumbleBundlePlugin(Plugin):
    def __init__(self, reader, writer, token):
        super().__init__(Platform.HumbleBundle, __version__, reader, writer,
                         token)
        self._api = AuthorizedHumbleAPI()
        self._download_resolver = HumbleDownloadResolver()
        self._app_finder = AppFinder()
        self._settings = Settings()
        self._library_resolver = None
        self._subscription_months: List[ChoiceMonth] = []

        self._owned_games: Dict[str, HumbleGame] = {}
        self._trove_games: Dict[str, TroveGame] = {}
        self._choice_games = {
        }  # for now model.subscription.ChoiceContet or Extras TODO consider adding to model.game

        self._local_games = {}
        self._cached_game_states = {}

        self._getting_owned_games = asyncio.Lock()
        self._owned_check: asyncio.Task = asyncio.create_task(asyncio.sleep(8))
        self._statuses_check: asyncio.Task = asyncio.create_task(
            asyncio.sleep(4))
        self._installed_check: asyncio.Task = asyncio.create_task(
            asyncio.sleep(4))

        self._rescan_needed = True
        self._under_installation = set()

    @property
    def _humble_games(self) -> t.Dict[str, HumbleGame]:
        """Alias for cached owned and subscription games mapped by id"""
        return {**self._owned_games, **self._trove_games}

    def _save_cache(self, key: str, data: t.Any):
        data = json.dumps(data)
        self.persistent_cache[key] = data
        self.push_cache()

    def _load_cache(self, key: str, default: t.Any = None) -> t.Any:
        if key in self.persistent_cache:
            return json.loads(self.persistent_cache[key])
        return default

    def handshake_complete(self):
        self._last_version = self._load_cache('last_version', default=None)
        self._trove_games = {
            g.machine_name: g
            for g in self._load_cache('trove_games', [])
        }
        self._library_resolver = LibraryResolver(
            api=self._api,
            settings=self._settings.library,
            cache=self._load_cache('library', {}),
            save_cache_callback=partial(self._save_cache, 'library'))

    async def _fetch_marketing_data(self) -> t.Optional[str]:
        try:
            subscription_infos = await self._api.get_choice_marketing_data()
            self._subscription_months = subscription_infos.month_details
            return subscription_infos.user_options['email']
        except KeyError:  # extra safety as this data is not crucial
            return None

    async def authenticate(self, stored_credentials=None):
        show_news = self.__is_after_minor_update()
        self._save_cache('last_version', __version__)

        if not stored_credentials:
            return NextStep(
                "web_session", {
                    "window_title":
                    "Login to HumbleBundle",
                    "window_width":
                    560,
                    "window_height":
                    610,
                    "start_uri":
                    "https://www.humblebundle.com/login?goto=/home/library",
                    "end_uri_regex":
                    "^" +
                    re.escape("https://www.humblebundle.com/home/library")
                })

        logging.info('Stored credentials found')
        user_id = await self._api.authenticate(stored_credentials)
        user_email = await self._fetch_marketing_data()

        if show_news:
            self._open_config(OPTIONS_MODE.NEWS)
        return Authentication(user_id, user_email or user_id)

    async def pass_login_credentials(self, step, credentials, cookies):
        auth_cookie = next(
            filter(lambda c: c['name'] == '_simpleauth_sess', cookies))
        user_id = await self._api.authenticate(auth_cookie)
        user_email = await self._fetch_marketing_data()
        self.store_credentials(auth_cookie)
        self._open_config(OPTIONS_MODE.WELCOME)
        return Authentication(user_id, user_email or user_id)

    def __is_after_minor_update(self) -> bool:
        def cut_to_minor(ver: str) -> LooseVersion:
            """3 part version assumed"""
            return LooseVersion(ver.rsplit('.', 1)[0])
        return self._last_version is None \
            or cut_to_minor(__version__) > cut_to_minor(self._last_version)

    async def get_owned_games(self):
        if not self._api.is_authenticated:
            raise AuthenticationRequired()

        async with self._getting_owned_games:
            logging.debug('getting owned games')
            self._owned_games = await self._library_resolver()
            return [g.in_galaxy_format() for g in self._owned_games.values()]

    @staticmethod
    def _normalize_subscription_name(machine_name):
        month_map = {
            'january': '01',
            'february': '02',
            'march': '03',
            'april': '04',
            'may': '05',
            'june': '06',
            'july': '07',
            'august': '08',
            'september': '09',
            'octover': '10',
            'november': '11',
            'december': '12'
        }
        month, year, type_ = machine_name.split('_')
        return f'Humble {type_.title()} {year}-{month_map[month]}'

    async def _get_current_user_subscription_plan(
            self, active_month_path: str) -> t.Optional[dict]:
        active_month_content = await self._api.get_choice_content_data(
            active_month_path)
        return active_month_content.user_subscription_plan

    async def get_subscriptions(self):
        subscriptions: List[Subscription] = []
        active_content_unlocked = False
        current_or_former_subscriber = await self._api.had_subscription()

        if current_or_former_subscriber:
            async for product in self._api.get_subscription_products_with_gamekeys(
            ):
                subscriptions.append(
                    Subscription(self._normalize_subscription_name(
                        product.product_machine_name),
                                 owned=True))
                if product.is_active_content:  # assuming there is one "active" month at a time
                    active_content_unlocked = True

        if not active_content_unlocked:
            '''
            - for not subscribers as potential discovery of current choice games
            - for subscribers who has not used "Early Unlock" yet:
              https://support.humblebundle.com/hc/en-us/articles/217300487-Humble-Choice-Early-Unlock-Games
            '''
            active_month = next(
                filter(lambda m: m.is_active == True,
                       self._subscription_months))
            current_user_plan = None
            if current_or_former_subscriber:
                current_user_plan = await self._get_current_user_subscription_plan(
                    active_month.last_url_part)

            subscriptions.append(
                Subscription(
                    self._normalize_subscription_name(
                        active_month.machine_name),
                    owned=bool(current_user_plan),  # #116: exclude Lite
                    end_time=
                    None  # #117: get_last_friday.timestamp() if user_plan not in [None, Lite] else None
                ))

        subscriptions.append(
            Subscription(subscription_name="Humble Trove",
                         owned=active_content_unlocked
                         or current_user_plan is not None))

        return subscriptions

    async def _get_trove_games(self):
        def parse_and_cache(troves):
            games: List[SubscriptionGame] = []
            for trove in troves:
                try:
                    trove_game = TroveGame(trove)
                    games.append(trove_game.in_galaxy_format())
                    self._trove_games[trove_game.machine_name] = trove_game
                except Exception as e:
                    logging.warning(
                        f"Error while parsing trove {repr(e)}: {trove}",
                        extra={'data': trove})
            return games

        newly_added = (await self._api.get_montly_trove_data()).get(
            'newlyAdded', [])
        if newly_added:
            yield parse_and_cache(newly_added)
        async for troves in self._api.get_trove_details():
            yield parse_and_cache(troves)

    async def prepare_subscription_games_context(
            self, subscription_names) -> t.Dict[str, ChoiceMonth]:
        name_url = {}
        for month in self._subscription_months:
            name_url[self._normalize_subscription_name(
                month.machine_name)] = month
        return name_url

    async def get_subscription_games(self, subscription_name,
                                     context: t.Dict[str, ChoiceMonth]):
        if subscription_name == "Humble Trove":
            async for troves in self._get_trove_games():
                yield troves
            return

        month: ChoiceMonth = context[subscription_name]
        choice_data = await self._api.get_choice_content_data(
            month.last_url_part)
        cco = choice_data.content_choice_options
        show_all = cco.remained_choices > 0
        if month.is_active:
            start_time = choice_data.active_content_start.timestamp()
        else:
            start_time = None  # TODO assume first friday of month

        content_choices = [
            SubscriptionGame(ch.title, ch.id, start_time)
            for ch in cco.content_choices
            if show_all or ch.id in cco.content_choices_made
        ]
        extrases = [
            SubscriptionGame(extr.human_name, extr.machine_name, start_time)
            for extr in cco.extrases
        ]
        month_choice_games = content_choices + extrases
        yield month_choice_games

    async def subscription_games_import_complete(self):
        sub_games_raw_data = [
            game.serialize() for game in self._trove_games.values
        ]
        self._save_cache('trove_games', sub_games_raw_data)

    async def get_local_games(self):
        self._rescan_needed = True
        return [g.in_galaxy_format() for g in self._local_games.values()]

    def _open_config(self, mode: OPTIONS_MODE = OPTIONS_MODE.NORMAL):
        """Synchonious wrapper for self._open_config_async"""
        self.create_task(self._open_config_async(mode), 'opening config')

    async def _open_config_async(self, mode: OPTIONS_MODE):
        try:
            await gui.show_options(mode)
        except Exception as e:
            logging.exception(e)
            self._settings.save_config()
            self._settings.open_config_file()

    @double_click_effect(timeout=0.5, effect='_open_config')
    async def install_game(self, game_id):
        if game_id in self._under_installation:
            return

        self._under_installation.add(game_id)
        try:
            game = self._humble_games.get(game_id)
            if game is None:
                logging.error(
                    f'Install game: game {game_id} not found. Owned games: {self._humble_games.keys()}'
                )
                return

            if isinstance(game, Key):
                try:
                    await gui.show_key(game)
                except Exception as e:
                    logging.error(e, extra={'platform_info': platform.uname()})
                    webbrowser.open('https://www.humblebundle.com/home/keys')
                return

            try:
                hp = HP.WINDOWS if IS_WINDOWS else HP.MAC
                curr_os_download = game.downloads[hp]
            except KeyError:
                raise UnknownError(
                    f'{game.human_name} has only downloads for {list(game.downloads.keys())}'
                )

            if isinstance(game, Subproduct):
                chosen_download_struct = self._download_resolver(
                    curr_os_download)
                urls = await self._api.sign_url_subproduct(
                    chosen_download_struct, curr_os_download.machine_name)
                webbrowser.open(urls['signed_url'])

            if isinstance(game, TroveGame):
                try:
                    urls = await self._api.sign_url_trove(
                        curr_os_download, game.machine_name)
                except AuthenticationRequired:
                    logging.info(
                        'Looks like your Humble Monthly subscription has expired.'
                    )
                    webbrowser.open(
                        'https://www.humblebundle.com/subscription/home')
                else:
                    webbrowser.open(urls['signed_url'])

        except Exception as e:
            logging.error(e, extra={'game': game})
            raise
        finally:
            self._under_installation.remove(game_id)

    async def get_game_library_settings(self, game_id: str,
                                        context: t.Any) -> GameLibrarySettings:
        gls = GameLibrarySettings(game_id, None, None)
        game = self._humble_games[game_id]
        if isinstance(game, Key):
            gls.tags = ['Key']
            if game.key_val is None:
                gls.tags.append('Unrevealed')
        if isinstance(game, TroveGame):
            gls.tags = [
            ]  # remove redundant tags since Galaxy support for subscripitons
        return gls

    async def launch_game(self, game_id):
        try:
            game = self._local_games[game_id]
        except KeyError as e:
            logging.error(e, extra={'local_games': self._local_games})
        else:
            game.run()

    async def uninstall_game(self, game_id):
        try:
            game = self._local_games[game_id]
        except KeyError as e:
            logging.error(e, extra={'local_games': self._local_games})
        else:
            game.uninstall()

    async def get_os_compatibility(
            self, game_id: str, context: t.Any) -> t.Optional[OSCompatibility]:
        try:
            game = self._humble_games[game_id]
        except KeyError as e:
            # silent issues until support for choice games in #93
            # logging.debug(self._humble_games)
            # logging.error(e, extra={'humble_games': self._humble_games})
            return None
        else:
            HP_OS_MAP = {
                HP.WINDOWS: OSCompatibility.Windows,
                HP.MAC: OSCompatibility.MacOS,
                HP.LINUX: OSCompatibility.Linux
            }
            osc = OSCompatibility(0)
            for humble_platform in game.downloads:
                osc |= HP_OS_MAP.get(humble_platform, OSCompatibility(0))
            return osc if osc else None

    async def _check_owned(self):
        async with self._getting_owned_games:
            old_ids = self._owned_games.keys()
            self._owned_games = await self._library_resolver(only_cache=True)
            for old_id in old_ids - self._owned_games.keys():
                self.remove_game(old_id)
            for new_id in self._owned_games.keys() - old_ids:
                self.add_game(self._owned_games[new_id].in_galaxy_format())
        # increased throttle to protect Galaxy from quick & heavy library changes
        await asyncio.sleep(3)

    async def _check_installed(self):
        """
        Owned games are needed to local games search. Galaxy methods call order is:
        get_local_games -> authenticate -> get_local_games -> get_owned_games (at the end!).
        That is why the plugin sets all logic of getting local games in perdiodic checks like this one.
        """
        if not self._humble_games:
            logging.debug(
                'Skipping perdiodic check for local games as owned/subscription games not found yet.'
            )
            return

        hp = HP.WINDOWS if IS_WINDOWS else HP.MAC
        installable_title_id = {
            game.human_name: uid
            for uid, game in self._humble_games.items()
            if not isinstance(game, Key) and game.os_compatibile(hp)
        }
        if self._rescan_needed:
            self._rescan_needed = False
            logging.debug(
                f'Checking installed games with path scanning in: {self._settings.installed.search_dirs}'
            )
            self._local_games = await self._app_finder(
                installable_title_id, self._settings.installed.search_dirs)
        else:
            self._local_games.update(await
                                     self._app_finder(installable_title_id,
                                                      None))
        await asyncio.sleep(4)

    async def _check_statuses(self):
        """Checks satuses of local games. Detects changes in local games when the game is:
        - installed (local games list appended in _check_installed)
        - uninstalled (exe no longer exists)
        - launched (via Galaxy - pid tracking started)
        - stopped (process no longer running/is zombie)
        """
        freezed_locals = list(self._local_games.values())
        for game in freezed_locals:
            state = game.state
            if state == self._cached_game_states.get(game.id):
                continue
            self.update_local_game_status(LocalGame(game.id, state))
            self._cached_game_states[game.id] = state
        await asyncio.sleep(0.5)

    def tick(self):
        self._settings.reload_config_if_changed()

        if self._owned_check.done() and self._settings.library.has_changed():
            self._owned_check = self.create_task(self._check_owned(),
                                                 'check owned')

        if self._settings.installed.has_changed():
            self._rescan_needed = True

        if self._installed_check.done():
            self._installed_check = asyncio.create_task(
                self._check_installed())

        if self._statuses_check.done():
            self._statuses_check = asyncio.create_task(self._check_statuses())

    async def shutdown(self):
        self._statuses_check.cancel()
        self._installed_check.cancel()
        await self._api.close_session()
class HumbleBundlePlugin(Plugin):
    def __init__(self, reader, writer, token):
        super().__init__(Platform.HumbleBundle, __version__, reader, writer, token)
        self._api = AuthorizedHumbleAPI()
        self._download_resolver = HumbleDownloadResolver()
        self._app_finder = AppFinder
        self._settings = None
        self._library_resolver = None

        self._owned_games = {}
        self._local_games = {}
        self._cached_game_states = {}

        self._getting_owned_games = asyncio.Event()
        self._check_owned_task = asyncio.create_task(asyncio.sleep(0))
        self._check_installed_task = asyncio.create_task(asyncio.sleep(5))
        self._check_statuses_task = asyncio.create_task(asyncio.sleep(2))

        self.__under_instalation = set()

    def _save_cache(self, key: str, data: Any):
        if type(data) != str:
            data = json.dumps(data)
        self.persistent_cache[key] = data
        self.push_cache()

    def handshake_complete(self):
        # tmp migration to fix 0.4.0 cache error
        library = json.loads(self.persistent_cache.get('library', '{}'))
        if library and type(library.get('orders')) == list:
            logging.info('Old cache migration')
            self._save_cache('library', {})

        self._settings = Settings(
            cache=self.persistent_cache,
            save_cache_callback=self.push_cache
        )
        self._library_resolver = LibraryResolver(
            api=self._api,
            settings=self._settings.library,
            cache=json.loads(self.persistent_cache.get('library', '{}')),
            save_cache_callback=partial(self._save_cache, 'library')
        )

    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._api.authenticate(stored_credentials)
        return Authentication(user_id, user_name)

    async def pass_login_credentials(self, step, credentials, cookies):
        auth_cookie = next(filter(lambda c: c['name'] == '_simpleauth_sess', cookies))

        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):
        self._getting_owned_games.set()
        self._owned_games = await self._library_resolver()
        self._getting_owned_games.clear()
        return [g.in_galaxy_format() for g in self._owned_games.values()]

    async def install_game(self, game_id):
        if game_id in self.__under_instalation:
            return
        self.__under_instalation.add(game_id)

        try:
            game = self._owned_games.get(game_id)
            if game is None:
                raise RuntimeError(f'Install game: game {game_id} not found. Owned games: {self._owned_games.keys()}')

            if isinstance(game, Key):
                args = [str(pathlib.Path(__file__).parent / 'keysgui.py'),
                    game.human_name, game.key_type_human_name, str(game.key_val)
                ]
                process = await asyncio.create_subprocess_exec(sys.executable, *args,
                    stderr=asyncio.subprocess.PIPE)
                _, stderr_data = await process.communicate()
                if stderr_data:
                    logging.debug(args)
                    logging.debug(stderr_data)
                return

            chosen_download = self._download_resolver(game)
            if isinstance(game, Subproduct):
                webbrowser.open(chosen_download.web)

            if isinstance(game, TroveGame):
                try:
                    url = await self._api.get_trove_sign_url(chosen_download, game.machine_name)
                except AuthenticationRequired:
                    logging.info('Looks like your Humble Monthly subscription has expired. Refer to config.ini to manage showed games.')
                    webbrowser.open('https://www.humblebundle.com/monthly/subscriber')
                else:
                    webbrowser.open(url['signed_url'])

        except Exception as e:
            report_problem(e, extra=game)
            logging.exception(e)
        finally:
            self.__under_instalation.remove(game_id)

    async def get_local_games(self):
        if not self._app_finder or not self._owned_games:
            return []

        try:
            self._app_finder.refresh()
        except Exception as e:
            report_problem(e, None)
            return []

        local_games = await self._app_finder.find_local_games(list(self._owned_games.values()))
        self._local_games.update({game.machine_name: game for game in local_games})

        return [g.in_galaxy_format() for g in self._local_games.values()]

    async def launch_game(self, game_id):
        try:
            game = self._local_games[game_id]
        except KeyError as e:
            report_problem(e, {'local_games': self._local_games})
        else:
            game.run()

    async def uninstall_game(self, game_id):
        try:
            game = self._local_games[game_id]
        except KeyError as e:
            report_problem(e, {'local_games': self._local_games})
        else:
            game.uninstall()

    async def _check_owned(self):
        """ self.get_owned_games is called periodically by galaxy too rarely.
        This method check for new orders more often and also when relevant option in config file was changed.
        """
        old_settings = astuple(self._settings.library)
        self._settings.reload_local_config_if_changed()
        if old_settings != astuple(self._settings.library):
            logging.info(f'Library settings has changed: {self._settings.library}')
            old_ids = self._owned_games.keys()
            self._owned_games = await self._library_resolver(only_cache=True)

            for old_id in old_ids - self._owned_games.keys():
                self.remove_game(old_id)
            for new_id in self._owned_games.keys() - old_ids:
                self.add_game(self._owned_games[new_id].in_galaxy_format())


    async def _check_statuses(self):
        """Check satuses of already found installed (local) games.
        Detects events when game is:
        - launched (via Galaxy for now)
        - stopped
        - uninstalled
        """
        freezed_locals = list(self._local_games.values())
        for game in freezed_locals:
            state = game.state
            if state == self._cached_game_states.get(game.id):
                continue
            self.update_local_game_status(LocalGame(game.id, state))
            self._cached_game_states[game.id] = state
        await asyncio.sleep(0.5)

    async def _check_installed(self):
        """Searches for installed games and updates self._local_games"""
        await self.get_local_games()
        await asyncio.sleep(5)

    def tick(self):
        if self._check_owned_task.done() and not self._getting_owned_games.is_set():
            self._check_owned_task = asyncio.create_task(self._check_owned())

        if self._check_statuses_task.done():
            self._check_statuses_task = asyncio.create_task(self._check_statuses())

        if self._check_installed_task.done():
            self._check_installed_task = asyncio.create_task(self._check_installed())


    def shutdown(self):
        asyncio.create_task(self._api.close_session())
        self._check_owned_task.cancel()
        self._check_installed_task.cancel()
        self._check_statuses_task.cancel()
 def __init__(self, reader, writer, token):
     super().__init__(Platform.HumbleBundle, __version__, reader, writer, token)
     self._api = AuthorizedHumbleAPI()
     self._games = {}
     self._downloader = HumbleDownloader()
class HumbleBundlePlugin(Plugin):

    def __init__(self, reader, writer, token):
        super().__init__(Platform.HumbleBundle, __version__, reader, writer, token)
        self._api = AuthorizedHumbleAPI()
        self._games = {}
        self._downloader = HumbleDownloader()

    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._api.authenticate(stored_credentials)
        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))

        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):

        def is_game(sub):
            default = False
            return next(filter(lambda x: x['platform'] in GAME_PLATFORMS, sub['downloads']), default)

        games = {}
        gamekeys = await self._api.get_gamekeys()
        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']:
                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()]

    async def install_game(self, game_id):
        game = self._games.get(game_id)
        if game is None:
            logging.error(f'Install game: game {game_id} not found')
            return

        try:
            url = self._downloader.find_best_url(game.downloads)
        except Exception as e:
            logging.exception(e)
        else:
            webbrowser.open(url['web'])

    # async def launch_game(self, game_id):
    #     pass

    # async def uninstall_game(self, game_id):
    #     pass


    def shutdown(self):
        self._api._session.close()
Пример #8
0
def test_filename_from_web_link():
    web_link = 'https://dl.humble.com/Almost_There_Windows.zip?gamekey=AbR9TcsD4ecueNGw&ttl=1587335864&t=a04a9b4f6512b7958f6357cb7b628452'
    expected = 'Almost_There_Windows.zip'
    assert expected == AuthorizedHumbleAPI._filename_from_web_link(web_link)