Skip to content

Commit

Permalink
version 0.35
Browse files Browse the repository at this point in the history
  • Loading branch information
FriendsOfGalaxy committed Apr 3, 2020
1 parent 90241cc commit a0499a0
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 7 deletions.
2 changes: 1 addition & 1 deletion requirements/app.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
galaxy.plugin.api==0.63
galaxy.plugin.api==0.65
pyobjc-framework-CoreServices==5.1.2; sys_platform == 'darwin'
65 changes: 63 additions & 2 deletions src/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
AccessDenied, AuthenticationRequired, BackendError, BackendNotAvailable, BackendTimeout, NetworkError,
UnknownBackendResponse
)
from galaxy.api.types import Achievement
from galaxy.api.types import Achievement, SubscriptionGame, Subscription
from galaxy.http import HttpClient
from yarl import URL

Expand Down Expand Up @@ -110,7 +110,6 @@ async def _get_access_token(self):
try:
data = await response.json(content_type=None)
self._access_token = data["access_token"]
self._log_session_details()
except (TypeError, ValueError, KeyError) as e:
self._log_session_details()
try:
Expand Down Expand Up @@ -490,3 +489,65 @@ async def get_hidden_games(self, user_id):
except (ET.ParseError, AttributeError, ValueError):
logging.exception("Can not parse backend response: %s", await response.text())
raise UnknownBackendResponse()

async def _get_subscription_status(self, subscription_uri):
def parse_timestamp(timestamp: str) -> Timestamp:
return Timestamp(
int((datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S") - datetime(1970, 1, 1)).total_seconds()))

response = await self._http_client.get(subscription_uri)
try:
data = await response.json()
if data and data['Subscription']['status'].lower() == 'enabled':
return {'tier': data['Subscription']['subscriptionLevel'].lower(),
'end_time': parse_timestamp(data['Subscription']['nextBillingDate'])}
else:
return None
except (ValueError, KeyError) as e:
logging.exception("Can not parse backend response while getting subs details: %s, error %s", await response.text(), repr(e))
raise UnknownBackendResponse()

async def _get_subscription_uri(self, user_id):
url = f"https://gateway.ea.com/proxy/subscription/pids/{user_id}/subscriptionsv2/groups/Origin Membership"
response = await self._http_client.get(url)
try:
data = await response.json()
if 'subscriptionUri' in data:
return f"https://gateway.ea.com/proxy/subscription/pids/{user_id}{data['subscriptionUri'][0]}"
else:
return None
except (ValueError, KeyError) as e:
logging.exception("Can not parse backend response while getting subs uri: %s, error %s", await response.text(), repr(e))
raise UnknownBackendResponse()

async def get_subscriptions(self, user_id) -> List[Subscription]:
subs = {'standard': Subscription(subscription_name='Origin Access Basic', owned=False),
'premium': Subscription(subscription_name='Origin Access Premier', owned=False)}

subscription_uri = await self._get_subscription_uri(user_id)
if subscription_uri:
sub_status = await self._get_subscription_status(subscription_uri)
logging.debug(f'sub_status: {sub_status}')
try:
if sub_status:
subs[sub_status['tier']].owned = True
subs[sub_status['tier']].end_time = sub_status['end_time']
except (ValueError, KeyError) as e:
logging.exception("Unknown subscription tier, error %s", repr(e))
raise UnknownBackendResponse()
else:
logging.debug(f'no subscription active')
return [subs['standard'], subs['premium']]

async def get_games_in_subscription(self, tier):
url = f"https://api3.origin.com/ecommerce2/vaultInfo/Origin Membership/tiers/{tier}"
headers = {
"Accept": "application/vnd.origin.v3+json; x-cache/force-write"
}
response = await self._http_client.get(url, headers=headers)
try:
games = await response.json()
return [SubscriptionGame(game_title=game['displayName'], game_id=game['offerId']) for game in games['game']]
except (ValueError, KeyError) as e:
logging.exception("Can not parse backend response while getting subs games: %s, error %s", await response.text(), repr(e))
raise UnknownBackendResponse()
28 changes: 26 additions & 2 deletions src/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import webbrowser
from collections import namedtuple
from functools import partial
from typing import Any, Callable, Dict, List, NewType, Optional
from typing import Any, Callable, Dict, List, NewType, Optional, AsyncGenerator

from galaxy.api.consts import LicenseType, Platform
from galaxy.api.errors import (
AccessDenied, AuthenticationRequired, InvalidCredentials, UnknownBackendResponse, UnknownError
)
from galaxy.api.plugin import create_and_run_plugin, Plugin
from galaxy.api.types import Achievement, Authentication, FriendInfo, Game, GameTime, LicenseInfo, NextStep, GameLibrarySettings
from galaxy.api.types import Achievement, Authentication, FriendInfo, Game, GameTime, LicenseInfo, NextStep, GameLibrarySettings, Subscription, SubscriptionGame

from backend import AuthenticatedHttpClient, MasterTitleId, OfferId, OriginBackendClient, Timestamp
from local_games import get_local_content_path, LocalGames
Expand Down Expand Up @@ -204,6 +204,30 @@ async def _get_owned_offers(self):
offer_ids = [entitlement["offerId"] for entitlement in entitlements]
return await self._get_offers(offer_ids)

async def get_subscriptions(self) -> List[Subscription]:
self._check_authenticated()
return await self._backend_client.get_subscriptions(user_id=self._user_id)

async def prepare_subscription_games_context(self, subscription_names: List[str]) -> Any:
self._check_authenticated()
subscription_name_to_tier = {
'Origin Access Basic': 'standard',
'Origin Access Premier': 'premium'
}
subscriptions = {}
for sub_name in subscription_names:
try:
tier = subscription_name_to_tier[sub_name]
except KeyError:
logging.error(f"Assertion: 'Galaxy passed unknown subscription name {sub_name}. This should not happen!")
raise UnknownError(f'Unknown subscription name {sub_name}!')
subscriptions[sub_name] = await self._backend_client.get_games_in_subscription(tier)
return subscriptions

async def get_subscription_games(self, subscription_name: str, context: Any) -> AsyncGenerator[List[SubscriptionGame],None]:
if context and subscription_name:
yield context[subscription_name]

async def get_local_games(self):
if self._local_games_update_in_progress:
logging.debug("LocalGames.update in progress, returning cached values")
Expand Down
6 changes: 5 additions & 1 deletion src/version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
__version__ = "0.34.1"
__version__ = "0.35"

__changelog__ = {
"0.35":
"""
- added support for subscriptions
""",
"0.34.1":
"""
- add extended logging to find session expiration time mechanism
Expand Down
3 changes: 2 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def backend_client():
mock.get_lastplayed_games = MagicMock()
mock.get_hidden_games = AsyncMock()
mock.get_favorite_games = AsyncMock()

mock.get_games_in_subscription = AsyncMock()
mock.get_subscriptions = AsyncMock()
return mock


Expand Down
2 changes: 2 additions & 0 deletions tests/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def test_integration():
"ImportOwnedGames",
"ImportAchievements",
"ImportInstalledGames",
"ImportSubscriptions",
"ImportSubscriptionGames",
"ImportGameLibrarySettings",
"LaunchGame",
"InstallGame",
Expand Down
77 changes: 77 additions & 0 deletions tests/test_subscriptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import pytest
from galaxy.api.types import SubscriptionGame, Subscription
from galaxy.api.errors import BackendError


SUBSCRIPTION_OWNED_ID = 'Origin Access Premier'
SUBSCRIPTIONS_NOT_OWNED = [Subscription(subscription_name='Origin Access Basic', owned=False, end_time=None),
Subscription(subscription_name='Origin Access Premier', owned=False, end_time=None)]
SUBSCRIPTIONS_OWNED = [Subscription(subscription_name='Origin Access Basic', owned=False, end_time=None),
Subscription(subscription_name='Origin Access Premier', owned=True, end_time=1581331712)]

SUBSCRIPTIONS_CONTEXT = {'Origin Access Premier': [
SubscriptionGame(game_title='Mass Effect 3 N7 Digital Deluxe Edition - PC - WW (Origin/3PDD)',
game_id='DR:230773600', start_time=None, end_time=None),
SubscriptionGame(game_title='LEGRAND LEGACY: Tale of the Fatebounds - PC - WW - (Origin)',
game_id='Origin.OFR.50.0003727', start_time=None, end_time=None),
SubscriptionGame(game_title='Mable & the Wood - PC - WW - (Origin)', game_id='Origin.OFR.50.0003777',
start_time=None, end_time=None),
SubscriptionGame(game_title='Worms W.M.D - PC - WW - (Origin)', game_id='Origin.OFR.50.0003802', start_time=None,
end_time=None)]}

SUBSCRIPTION_GAMES = [
SubscriptionGame(game_title='Mass Effect 3 N7 Digital Deluxe Edition - PC - WW (Origin/3PDD)',
game_id='DR:230773600', start_time=None, end_time=None),
SubscriptionGame(game_title='LEGRAND LEGACY: Tale of the Fatebounds - PC - WW - (Origin)',
game_id='Origin.OFR.50.0003727', start_time=None, end_time=None),
SubscriptionGame(game_title='Mable & the Wood - PC - WW - (Origin)', game_id='Origin.OFR.50.0003777',
start_time=None, end_time=None),
SubscriptionGame(game_title='Worms W.M.D - PC - WW - (Origin)', game_id='Origin.OFR.50.0003802', start_time=None,
end_time=None)]


@pytest.mark.asyncio
async def test_subscription_not_owned(
authenticated_plugin,
backend_client,
):
backend_client.get_subscriptions.return_value = SUBSCRIPTIONS_NOT_OWNED
assert SUBSCRIPTIONS_NOT_OWNED == await authenticated_plugin.get_subscriptions()


@pytest.mark.asyncio
async def test_subscription_owned(
authenticated_plugin,
backend_client,
):
backend_client.get_subscriptions.return_value = SUBSCRIPTIONS_OWNED
assert SUBSCRIPTIONS_OWNED == await authenticated_plugin.get_subscriptions()


@pytest.mark.asyncio
async def test_prepare_subscription_games_context(
authenticated_plugin,
backend_client,
):
backend_client.get_games_in_subscription.return_value = SUBSCRIPTION_GAMES
assert SUBSCRIPTIONS_CONTEXT == await authenticated_plugin.prepare_subscription_games_context([SUBSCRIPTION_OWNED_ID])


@pytest.mark.asyncio
async def test_prepare_subscription_games_context_error(
authenticated_plugin,
backend_client,
):
backend_client.get_games_in_subscription.side_effect = BackendError()
with pytest.raises(BackendError):
await authenticated_plugin.prepare_subscription_games_context([SUBSCRIPTION_OWNED_ID])


@pytest.mark.asyncio
async def test_subscription_games(
authenticated_plugin,
):
async for sub_games in authenticated_plugin.get_subscription_games(SUBSCRIPTION_OWNED_ID, SUBSCRIPTIONS_CONTEXT):
assert sub_games == SUBSCRIPTION_GAMES


0 comments on commit a0499a0

Please sign in to comment.