Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix subscription discoverability for no-subscribers #164

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/model/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(self, data: dict):
self.machine_name = data['machine_name']
self.human_name = data['human_name']


class ChoiceMonth:
"""Below example of month from `data['monthDetails']['previous_months']`
{
Expand Down
8 changes: 8 additions & 0 deletions src/model/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,11 @@ class Tier(enum.Enum):
BASIC = 'basic'
PREMIUM = 'premium'
CLASSIC = 'premiumv1'


class SubscriptionStatus(enum.Enum):
NeverSubscribed = enum.auto()
Expired = enum.auto()
Active = enum.auto()
Unknown = enum.auto()

52 changes: 39 additions & 13 deletions src/plugin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from build.model.subscription import UserSubscriptionPlan
import sys
import platform
import asyncio
Expand All @@ -16,15 +17,15 @@
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
from galaxy.api.plugin import Plugin, create_and_run_plugin
from galaxy.api.consts import Platform, OSCompatibility
from galaxy.api.consts import Platform, OSCompatibility, SubscriptionDiscovery
from galaxy.api.types import Authentication, NextStep, LocalGame, GameLibrarySettings, Subscription, SubscriptionGame
from galaxy.api.errors import AuthenticationRequired, UnknownBackendResponse, UnknownError, BackendError

from consts import IS_WINDOWS, TROVE_SUBSCRIPTION_NAME
from settings import Settings
from webservice import AuthorizedHumbleAPI
from webservice import AuthorizedHumbleAPI, NeverSubscriberError
from model.game import TroveGame, Key, Subproduct, HumbleGame, ChoiceGame
from model.types import HP, Tier
from model.types import HP, Tier, SubscriptionStatus
from humbledownloader import HumbleDownloadResolver
from library import LibraryResolver
from local import AppFinder
Expand Down Expand Up @@ -194,11 +195,27 @@ def _choice_name_to_slug(subscription_name: str):
async def _get_active_month_machine_name(self) -> str:
marketing_data = await self._api.get_choice_marketing_data()
return marketing_data['activeContentMachineName']

async def get_subscriptions(self):
# TODO - before deciding on data structures, check how expired subs work and how to know if it is expired
subscriptions: t.List[Subscription] = []
current_plan = await self._api.get_subscription_plan()
active_content_unlocked = False
sub_status: SubscriptionStatus
sub_plan: t.Optional[UserSubscriptionPlan] = None
is_active_content_unlocked: bool = False

try:
sub_plan = await self._api.get_subscription_plan()
except NeverSubscriberError:
sub_status = SubscriptionStatus.NeverSubscribed
except Exception as e:
logger.error("Can't fetch user subscription plan: %s", repr(e))
sub_status = SubscriptionStatus.Unknown
else:
sub_status = SubscriptionStatus.Active

discovery = SubscriptionDiscovery.USER_ENABLED
if sub_status != SubscriptionStatus.Unknown:
discovery |= SubscriptionDiscovery.AUTOMATIC

async for product in self._api.get_subscription_products_with_gamekeys():
if 'contentChoiceData' not in product:
Expand All @@ -207,26 +224,35 @@ async def get_subscriptions(self):
is_active = product.get('isActiveContent', False)
subscriptions.append(Subscription(
self._normalize_subscription_name(product['productMachineName']),
owned='gamekey' in product
owned = 'gamekey' in product,
subscription_discovery = discovery
))
active_content_unlocked |= is_active # assuming there is only one "active" month at a time
is_active_content_unlocked |= is_active # assuming there is only one "active" month at a time

if not active_content_unlocked:
if not is_active_content_unlocked:
'''
- for not subscribers as potential discovery of current choice games
- for not this month 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_machine_name = await self._get_active_month_machine_name()
if sub_status == SubscriptionStatus.Unknown:
active_month_owned = None
else:
active_month_owned = sub_status == SubscriptionStatus.Active and sub_plan.tier != Tier.LITE,

subscriptions.append(Subscription(
self._normalize_subscription_name(active_month_machine_name),
owned = current_plan is not None and current_plan.tier != Tier.LITE, # TODO: last month of not payed subs are still returned
end_time = None # #117: get_last_friday.timestamp() if user_plan not in [None, Lite] else None
# TODO: last month of not payed subs are still returned
owned = active_month_owned,
end_time = None, # #117: get_last_friday.timestamp() if user_plan not in [None, Lite] else None
subscription_discovery = discovery
))

subscriptions.append(Subscription(
subscription_name = TROVE_SUBSCRIPTION_NAME,
owned = current_plan is not None
owned = sub_status == SubscriptionStatus.Active,
subscription_discovery = discovery
))

return subscriptions
Expand Down
15 changes: 10 additions & 5 deletions src/webservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class Redirected(Exception):
pass


class NeverSubscriberError(Exception):
pass


class AuthorizedHumbleAPI:
_AUTHORITY = "https://www.humblebundle.com/"
_PROCESS_LOGIN = "processlogin"
Expand Down Expand Up @@ -90,7 +94,7 @@ def _decode_user_id(self, _simpleauth_sess):

async def authenticate(self, auth_cookie: dict) -> t.Optional[str]:
# recreate original cookie
cookie = SimpleCookie()
cookie: SimpleCookie = SimpleCookie()
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:
Expand Down Expand Up @@ -175,13 +179,14 @@ async def _get_webpack_data(self, path: str, webpack_id: str) -> dict:
raise UnknownBackendResponse('cannot parse webpack data') from e
return parsed

async def get_subscription_plan(self) -> t.Optional[UserSubscriptionPlan]:
async def get_subscription_plan(self) -> UserSubscriptionPlan:
try:
sub_hub_data = await self.get_subscriber_hub_data()
return UserSubscriptionPlan(sub_hub_data["userSubscriptionPlan"])
except (UnknownBackendResponse, KeyError) as e:
logger.warning("Can't fetch userSubscriptionPlan details. %s", repr(e))
return None
except UnknownBackendResponse as e:
raise NeverSubscriberError("Can't fetch user subscription plan: %s", repr(e))
except KeyError:
raise UnknownBackendResponse("Can't fetch user subscription plan: %s", repr(e))

async def get_subscriber_hub_data(self) -> dict:
webpack_id = "webpack-subscriber-hub-data"
Expand Down