Skip to content

Commit

Permalink
version 0.26
Browse files Browse the repository at this point in the history
  • Loading branch information
FriendsOfGalaxy committed Dec 3, 2019
1 parent c8140d3 commit f2cb1f5
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 299 deletions.
27 changes: 0 additions & 27 deletions .github/ISSUE_TEMPLATE/bug_report.md

This file was deleted.

2 changes: 1 addition & 1 deletion requirements/app.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
aiohttp==3.5.4
galaxy.plugin.api==0.40.1
galaxy.plugin.api==0.59
urllib3==1.24.1
certifi==2019.3.9
2 changes: 1 addition & 1 deletion requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-r app.txt
aioresponses
invoke==1.2.0
pytest==4.2.0
pytest==5.2.2
pytest-flakes==4.0.0
pytest-pythonpath==0.7.3
pytest-asyncio==0.10.0
Expand Down
62 changes: 23 additions & 39 deletions src/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
from collections import defaultdict

from galaxy.api.plugin import Plugin, create_and_run_plugin
from galaxy.api.types import Authentication, NextStep, Achievement
from galaxy.api.types import Authentication, NextStep, Achievement, UserPresence, PresenceState
from galaxy.api.consts import Platform
from galaxy.api.errors import ApplicationError, InvalidCredentials, UnknownError
from galaxy.api.jsonrpc import InvalidParams
from galaxy.api.errors import ApplicationError, InvalidCredentials, UnknownError, AuthenticationRequired

import serialization
from cache import Cache
Expand All @@ -19,7 +19,7 @@
CommunicationId, TitleId, TrophyTitles, UnixTimestamp,
PSNClient, MAX_TITLE_IDS_PER_REQUEST
)
from typing import Dict, List, Set, Iterable, Tuple, Optional
from typing import Dict, List, Set, Iterable, Tuple, Optional, Any
from version import __version__

from http_client import OAUTH_LOGIN_URL, OAUTH_LOGIN_REDIRECT_URL
Expand Down Expand Up @@ -122,31 +122,17 @@ async def filter_games(titles):
await self._psn_client.async_get_owned_games()
)

# TODO: backward compatibility. remove when GLX handles batch imports
async def get_unlocked_achievements(self, game_id: TitleId):
async def get_game_comm_id():
comm_ids: List[CommunicationId] = (await self.get_game_communication_ids([game_id]))[game_id]
if not self._is_game(comm_ids):
raise InvalidParams()
return comm_ids[0]
async def get_unlocked_achievements(self, game_id: str, context: Any) -> List[Achievement]:
if not context:
return []
comm_ids: List[CommunicationId] = (await self.get_game_communication_ids([game_id]))[game_id]
if not self._is_game(comm_ids):
raise InvalidParams()
return self._get_game_trophies_from_cache(comm_ids, context)[0]

return await self._psn_client.async_get_earned_trophies(
await get_game_comm_id()
)

async def start_achievements_import(self, game_ids: List[TitleId]):
if not self._http_client.is_authenticated:
raise AuthenticationRequired
await super().start_achievements_import(game_ids)

async def import_games_achievements(self, game_ids: Iterable[TitleId]):
try:
games_cids = await self.get_game_communication_ids(game_ids)
trophy_titles = await self._psn_client.get_trophy_titles()
except ApplicationError as error:
for title_id in game_ids:
self.game_achievements_import_failure(title_id, error)
return
async def prepare_achievements_context(self, game_ids: List[str]) -> Any:
games_cids = await self.get_game_communication_ids(game_ids)
trophy_titles = await self._psn_client.get_trophy_titles()

pending_cid_tids, pending_tid_cids, tid_trophies = self._process_trophies_cache(games_cids, trophy_titles)

Expand All @@ -167,10 +153,7 @@ async def import_games_achievements(self, game_ids: Iterable[TitleId]):
except (pickle.PicklingError, binascii.Error):
logging.error("Can not serialize trophies cache")

# log if some games has not been processed (it shouldn't happen)
for tid in pending_tid_cids.keys():
logging.error("Not fetched all trophies for game %s", tid)
self.game_achievements_import_failure(tid, UnknownError())
return trophy_titles

def _process_trophies_cache(
self,
Expand All @@ -182,9 +165,6 @@ def _process_trophies_cache(
tid_trophies: _TID_TROPHIES_DICT = {}

for title_id, comm_ids in games_cids.items():
if not self._is_game(comm_ids):
self.game_achievements_import_failure(title_id, InvalidParams())
continue

game_trophies, pending_comm_ids = self._get_game_trophies_from_cache(comm_ids, trophy_titles)

Expand All @@ -193,9 +173,6 @@ def _process_trophies_cache(
pending_cid_tids[comm_id].add(title_id)
pending_tid_cids[title_id].update(pending_comm_ids)
tid_trophies[title_id] = game_trophies
else:
# all trophies fetched from cache
self.game_achievements_import_success(title_id, game_trophies)

return pending_cid_tids, pending_tid_cids, tid_trophies

Expand Down Expand Up @@ -225,7 +202,6 @@ async def _import_trophies(
def handle_error(error_):
for tid_ in pending_tids:
del pending_tid_cids[tid_]
self.game_achievements_import_failure(tid_, error_)

try:
trophies: List[Achievement] = await self._psn_client.async_get_earned_trophies(comm_id)
Expand All @@ -238,14 +214,22 @@ def handle_error(error_):
pending_comm_ids.remove(comm_id)
if not pending_comm_ids:
# the game has already all comm ids processed
self.game_achievements_import_success(tid, game_trophies)
del pending_tid_cids[tid]
except ApplicationError as error:
handle_error(error)
except Exception:
logging.exception("Unhandled exception. Please report it to the plugin developers")
handle_error(UnknownError())

async def prepare_user_presence_context(self, user_ids: List[str]) -> Any:
return await self._psn_client.async_get_friends_presences()

async def get_user_presence(self, user_id: str, context: Any) -> UserPresence:
for user in context:
if user_id in user:
return user[user_id]
return UserPresence(PresenceState.Unknown)

async def get_friends(self):
return await self._psn_client.async_get_friends()

Expand Down
55 changes: 50 additions & 5 deletions src/psn_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import Dict, List, NewType, Tuple

from galaxy.api.errors import UnknownBackendResponse
from galaxy.api.types import Achievement, Game, LicenseInfo, FriendInfo
from galaxy.api.types import Achievement, Game, LicenseInfo, UserInfo, UserPresence, PresenceState
from galaxy.api.consts import LicenseType
from http_client import paginate_url

Expand Down Expand Up @@ -37,8 +37,13 @@
USER_INFO_URL = "https://pl-prof.np.community.playstation.net/userProfile/v1/users/{user_id}/profile2" \
"?fields=accountId,onlineId"

DEFAULT_AVATAR_SIZE = "l"
FRIENDS_URL = "https://us-prof.np.community.playstation.net/userProfile/v1/users/{user_id}/friends/profiles2" \
"?fields=accountId,onlineId"
"?fields=accountId,onlineId,avatarUrls&avatarSizes={avatar_size_list}"

FRIENDS_WITH_PRESENCE_URL = "https://us-prof.np.community.playstation.net/userProfile/v1/users/{user_id}/friends/profiles2" \
"?fields=accountId,onlineId,primaryOnlineStatus,presences(@titleInfo,lastOnlineDate)"


DEFAULT_LIMIT = 100
MAX_TITLE_IDS_PER_REQUEST = 5
Expand Down Expand Up @@ -195,18 +200,58 @@ def trophies_parser(response) -> List[Achievement]:

async def async_get_friends(self):
def friend_info_parser(profile):
return FriendInfo(

avatar_url = None
for avatar in profile["avatarUrls"]:
avatar_url = avatar["avatarUrl"]

return UserInfo(
user_id=str(profile["accountId"]),
user_name=str(profile["onlineId"])
user_name=str(profile["onlineId"]),
avatar_url=avatar_url,
profile_url=f"https://my.playstation.com/profile/{str(profile['onlineId'])}"
)

def friend_list_parser(response):
logging.info(response)
return [
friend_info_parser(profile) for profile in response.get("profiles", [])
] if response else []

return await self.fetch_paginated_data(
friend_list_parser,
FRIENDS_URL.format(user_id="me"),
FRIENDS_URL.format(user_id="me", avatar_size_list=DEFAULT_AVATAR_SIZE),
"totalResults"
)

async def async_get_friends_presences(self):
def friend_info_parser(profile):

if profile["primaryOnlineStatus"] == "online":
presence_state = PresenceState.Online
else:
presence_state = PresenceState.Offline

game_title = game_id = None

if "presences" in profile:
for presence in profile["presences"]:
try:
if presence["onlineStatus"] == "online" and presence["platform"] == "PS4":
game_title = presence["titleName"]
game_id = presence["npTitleId"]
except:
continue

return {profile['accountId']: UserPresence(presence_state, game_id, game_title)}

def friends_with_presence_parser(response):
return [
friend_info_parser(profile) for profile in response.get("profiles", [])
] if response else []

return await self.fetch_paginated_data(
friends_with_presence_parser,
FRIENDS_WITH_PRESENCE_URL.format(user_id="me"),
"totalResults"
)
2 changes: 1 addition & 1 deletion src/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.24.1"
__version__ = "0.26"
21 changes: 8 additions & 13 deletions tests/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,14 @@ def test_integration():
plugin_socket.sendall((json.dumps(request)+"\n").encode("utf-8"))
response = json.loads(plugin_socket.recv(4096))
print(response)
assert response == {
"id": "3",
"jsonrpc": "2.0",
"result": {
"platform_name": "psn",
"features": [
"ImportOwnedGames",
"ImportAchievements",
"ImportFriends"
],
"token": token
}
}, "Response differs from expected"
assert response["result"]["platform_name"] == "psn"
assert set(response["result"]["features"]) == set([
'ImportAchievements',
'ImportOwnedGames',
'ImportUserPresence',
'ImportFriends'
])
assert response["result"]["token"] == token

plugin_socket.close()
result.wait(TIMEOUT)
Expand Down
Loading

0 comments on commit f2cb1f5

Please sign in to comment.