Skip to content

Commit

Permalink
version 0.55.3
Browse files Browse the repository at this point in the history
  • Loading branch information
FriendsOfGalaxy committed Jul 5, 2021
1 parent 41cd311 commit 628af47
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 445 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ plugin-uplay.spec

# development stuff
venv
.venv
.DS_Store
.vscode
.idea
Expand Down
63 changes: 49 additions & 14 deletions src/backend.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from datetime import datetime
import logging as log
from galaxy.http import handle_exception, create_tcp_connector, create_client_session
Expand All @@ -23,14 +24,14 @@ def __init__(self, plugin):
self.user_name = None
self.__refresh_in_progress = False
connector = create_tcp_connector(limit=30)
self._session = create_client_session(connector=connector, timeout=aiohttp.ClientTimeout(total=120), cookie_jar=None)

self._session.headers = {
headers = {
'Authorization': None,
'Ubi-AppId': CLUB_APPID,
"User-Agent": CHROME_USERAGENT,
'Ubi-SessionId': None
}
self._session = create_client_session(connector=connector, timeout=aiohttp.ClientTimeout(total=120),
cookie_jar=None, headers=headers)

async def close(self):
# If closing is attempted while plugin is inside refresh workflow then give it a chance to finish it.
Expand Down Expand Up @@ -181,11 +182,10 @@ def restore_credentials(self, data):
if data.get('rememberMeTicket'):
self.refresh_token = data['rememberMeTicket']

self._session.headers = {
'Ubi-AppId': CLUB_APPID,
self._session.headers.update({
"Authorization": f"Ubi_v1 t={self.token}",
"Ubi-SessionId": self.session_id
}
})

def get_credentials(self):
creds = {"ticket": self.token,
Expand Down Expand Up @@ -213,10 +213,10 @@ async def authorise_with_stored_credentials(self, credentials):
async def authorise_with_local_storage(self, storage_jsons):
user_data = {}
tasty_storage_values = ['userId', 'nameOnPlatform', 'ticket', 'rememberMeTicket', 'sessionId']
for json in storage_jsons:
for key in json:
for json_ in storage_jsons:
for key in json_:
if key in tasty_storage_values:
user_data[key] = json[key]
user_data[key] = json_[key]

user_data['userId'] = user_data.pop('userId')
user_data['username'] = user_data.pop('nameOnPlatform')
Expand All @@ -235,7 +235,46 @@ async def get_friends(self):
return r

async def get_club_titles(self):
return await self._do_request_safe('get', "https://public-ubiservices.ubi.com/v1/profiles/me/club/aggregation/website/games/owned")
payload = {
"operationName": "AllGames",
"variables": {"owned": True},
"query": "query AllGames {"
"viewer {"
" id"
" ...ownedGamesList"
" }"
"}"
"fragment gameProps on Game {"
" id"
" spaceId"
" name"
"}"
"fragment ownedGameProps on Game {"
" ...gameProps"
" viewer {"
" meta {"
" id"
" ownedPlatformGroups {"
" id"
" name"
" type"
" }"
" }"
" }"
"}"
"fragment ownedGamesList on User {"
" ownedGames: games(filterBy: {isOwned: true}) {"
" totalCount"
" nodes {"
" ...ownedGameProps"
" }"
" }"
"}"
}
payload = json.dumps(payload)
headers = {'Content-Type': 'application/json'}
return await self._do_request_safe('post', "https://public-ubiservices.ubi.com/v3/profiles/me/uplay/graphql",
add_to_headers=headers, data=payload)

async def get_game_stats(self, space_id):
url = f"https://public-ubiservices.ubi.com/v1/profiles/{self.user_id}/statscard?spaceId={space_id}"
Expand All @@ -252,10 +291,6 @@ async def get_applications(self, spaces):
j = await self._do_request_safe('get', f"https://api-ubiservices.ubi.com/v2/applications?spaceIds={space_string}")
return j

async def get_challenges(self, space_id):
j = await self._do_request_safe('get', f"https://public-ubiservices.ubi.com/v1/profiles/{self.user_id}/club/actions?limit=100&locale=en-US&spaceId={space_id}")
return j

async def get_configuration(self):
r = await self._do_request_safe('get', 'https://uplaywebcenter.ubi.com/v1/configuration')
return r.json()
Expand Down
6 changes: 3 additions & 3 deletions src/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
CLUB_GENOME_ID = "fbd6791c-a6c6-4206-a75e-77234080b87b"

AUTH_PARAMS = {
"window_title": "Login to Uplay",
"window_width": 400,
"window_height": 680,
"window_title": "Login | Ubisoft WebAuth",
"window_width": 460,
"window_height": 690,
"start_uri": f"https://connect.ubisoft.com/login?appId={CLUB_APPID}&genomeId={CLUB_GENOME_ID}&lang=en-US&nextUrl=https:%2F%2Fconnect.ubisoft.com%2Fready",
"end_uri_regex": r".*rememberMeTicket.*"
}
Expand Down
77 changes: 35 additions & 42 deletions src/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
import subprocess
import sys
import webbrowser
import datetime
import dateutil.parser
from yaml import scanner
from urllib.parse import unquote
from typing import Any, List, AsyncGenerator, Optional

from galaxy.api.consts import Platform
from galaxy.api.jsonrpc import ApplicationError
from galaxy.api.errors import InvalidCredentials, AuthenticationRequired, AccessDenied, UnknownError
from galaxy.api.errors import InvalidCredentials, AuthenticationRequired, AccessDenied, UnknownError, \
UnknownBackendResponse
from galaxy.api.plugin import Plugin, create_and_run_plugin
from galaxy.api.types import Authentication, GameTime, Achievement, NextStep, FriendInfo, GameLibrarySettings, \
from galaxy.api.types import Authentication, GameTime, NextStep, FriendInfo, GameLibrarySettings, \
SubscriptionGame, Subscription, SubscriptionDiscovery

from backend import BackendClient
Expand Down Expand Up @@ -50,8 +49,6 @@ def __init__(self, reader, writer, token):
self.parsing_club_games = False
self.parsed_local_games = False



def auth_lost(self):
self.lost_authentication()

Expand Down Expand Up @@ -128,33 +125,45 @@ async def _parse_subscription_games(self):
self.games_collection.extend(subscription_games)

async def _parse_club_games(self):
def get_platforms(game):
platform_groups = game['viewer']['meta']['ownedPlatformGroups']
platforms = []
for group in platform_groups:
for platform in group:
platforms.append(platform.get('type', ''))
return platforms

if not self.parsing_club_games:
try:
self.parsing_club_games = True
games = await self.client.get_club_titles()
data = await self.client.get_club_titles()
games = data['data']['viewer']['ownedGames'].get('nodes', [])
club_games = []
for game in games:
if "platform" in game:
if game["platform"] == "PC":
log.info(f"Parsed game from Club Request {game['title']}")
club_games.append(
UbisoftGame(
space_id=game['spaceId'],
launch_id='',
install_id='',
third_party_id='',
name=game['title'],
path='',
type=GameType.New,
special_registry_path='',
exe='',
status=GameStatus.Unknown,
owned=True
))
else:
log.debug(f"Skipped game from Club Request for {game['platform']}: {game['spaceId']}, {game['title']}")
platforms = get_platforms(game)
if "PC" in platforms:
log.info(f"Parsed game from Club Request {game['name']}")
club_games.append(
UbisoftGame(
space_id=game['spaceId'],
launch_id='',
install_id='',
third_party_id='',
name=game['name'],
path='',
type=GameType.New,
special_registry_path='',
exe='',
status=GameStatus.Unknown,
owned=True
))
else:
log.debug(f"Skipped game from Club Request for {platforms}: {game['spaceId']}, {game['name']}")

self.games_collection.extend(club_games)
except (KeyError, TypeError) as e:
log.error(f"Unknown response from Ubisoft during parsing club games {repr(e)}")
raise UnknownBackendResponse()
except ApplicationError as e:
log.error(f"Encountered exception while parsing club games {repr(e)}")
raise e
Expand Down Expand Up @@ -307,22 +316,6 @@ async def get_playtime(self, game_ids):
self.push_cache()
return games_playtime

async def get_unlocked_challenges(self, game_id):
"""Challenges are a unique uplay club feature and don't directly translate to achievements"""
if not self.client.is_authenticated():
raise AuthenticationRequired()
for game in self.games_collection:
if game.space_id == game_id or game.launch_id == game_id:
if not game.space_id:
return []
challenges = await self.client.get_challenges(game.space_id)
return [
Achievement(achievement_id=challenge["id"], achievement_name=challenge["name"],
unlock_time=int(
datetime.datetime.timestamp(dateutil.parser.parse(challenge["completionDate"]))))
for challenge in challenges["actions"] if challenge["isCompleted"] and not challenge["isBadge"]
]

if SYSTEM == System.WINDOWS:
async def launch_game(self, game_id):
if not self.parsed_local_games:
Expand Down
17 changes: 9 additions & 8 deletions src/stats.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import dateutil.parser
import math
import logging
from typing import Tuple, Optional


logger = logging.getLogger(__name__)


def _normalize_last_played(card):
iso_datetime = card.get('lastModified', None)
if iso_datetime:
Expand All @@ -29,7 +33,7 @@ def _normalize_playtime(card):
elif unit == 'Miliseconds':
factor = 60000
else:
logging.warning(f'Playtime: Unexpected unit [{unit}] with value: [{value}]')
logger.warning(f'Playtime: Unexpected unit [{unit}] with value: [{value}]')
return None

if value == "":
Expand All @@ -46,14 +50,16 @@ def _get_playtime_heuristics(time_stats):
""" Tested on most of UplayClub games
:param time_stats: cards with format 'longTimestamp'
"""
TOTAL_PLAYTIME_DISPLAYNAMES = ['playtime', 'time played', 'play time', 'total play time', 'total playtime']

cards = []
if len(time_stats) == 0:
pass
elif len(time_stats) == 1:
cards = [time_stats[0]]
else:
for st in time_stats:
if st['displayName'].lower() in ['playtime', 'time played', 'play time']:
if st['displayName'].lower() in TOTAL_PLAYTIME_DISPLAYNAMES:
cards = [st]
break
else:
Expand Down Expand Up @@ -86,7 +92,7 @@ def _get_playtime_heuristics(time_stats):
time_sum = card_time if time_sum is None else card_time + time_sum

if type(time_sum) == float:
time_sum = round(time_sum)
time_sum = math.floor(time_sum)

return time_sum

Expand Down Expand Up @@ -119,11 +125,6 @@ def find_times(statscards: dict, game_id: str = None) -> Tuple[Optional[int], Op
if time_stats:
playtime = _get_playtime_heuristics(time_stats)

# +1 hour for forhonor
if game_id == '882ad5b5-f549-44a1-a434-c465d22fe4bf':
if playtime is not None:
playtime += 60

if playtime and playtime <= 0:
playtime = 0

Expand Down
9 changes: 8 additions & 1 deletion src/version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
__version__ = '0.55.2'
__version__ = '0.55.3'

__changelog__ = {
"0.55.3": """
- changed login window's title
- replaced deprecated owned games endpoint
- fix problem with doubled gametimes for some games (eg. Trackmania, Division 2)
- remove +1h gametime fix for For Honour as it is already fixed on Ubisoft side
- round down gametime to full minutes to better reflect Ubisoft logic
""",
"0.55.2": """
- bump galaxy.plugin.api version for more stable `get_local_size`
""",
Expand Down
9 changes: 7 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,15 @@ def local_client():


@pytest.fixture()
def create_plugin(local_client):
def backend_client():
return BackendClientMock()


@pytest.fixture()
def create_plugin(local_client, backend_client):
def function():
with patch("plugin.LocalClient", return_value=local_client):
with patch("plugin.BackendClient", new=BackendClientMock):
with patch("plugin.BackendClient", return_value=backend_client):
return plugin.UplayPlugin(MagicMock(), MagicMock(), None)
return function

Expand Down

0 comments on commit 628af47

Please sign in to comment.