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

feat: Add region/country support for client and user models #95

Merged
merged 8 commits into from
Jan 12, 2025
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Retrieve User Information, Trophies, Game and Store data from the PlayStation Ne
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/license/MIT)

<!-- Pytest Coverage Comment:Begin -->
\n<!-- Pytest Coverage Comment:End -->
<!-- Pytest Coverage Comment:Begin -->
\n<!-- Pytest Coverage Comment:End -->

## How to install

Expand Down Expand Up @@ -60,6 +60,7 @@ psnawp = PSNAWP("<64 character npsso code>")
client = psnawp.me()
print(f"Online ID: {client.online_id}")
print(f"Account ID: {client.account_id}")
print(f"Region: {client.get_region()}")
print(f"Profile: {client.get_profile_legacy()} \n")

# Your Registered Devices
Expand Down Expand Up @@ -111,6 +112,7 @@ for title in titles_with_stats:
example_user_1 = psnawp.user(online_id="VaultTec-Co") # Get a PSN player by their Online ID
print(f"User 1 Online ID: {example_user_1.online_id}")
print(f"User 1 Account ID: {example_user_1.account_id}")
print(f"User 1 Region: {example_user_1.get_region()}")

print(example_user_1.profile())
print(example_user_1.prev_online_id)
Expand Down
23 changes: 12 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,32 @@ classifiers = [
packages = [{ include = "psnawp_api", from = "src" }]

[tool.poetry.dependencies]
python = "^3.9"
attrs = "23.2.0"
python = "^3.10"
attrs = "24.2.0"
requests = "^2.32.3"
typing-extensions = "^4.12.2"
requests-ratelimiter = "^0.7.0"
pyright = "^1.1.370"
pycountry = "^24.6.1"

[tool.poetry.group.typing.dependencies]
mypy = "^1.10.1"
mypy = "^1.14.1"
pyright = "^1.1.391"
types-requests = "^2.31.0.6"

[tool.poetry.group.linting.dependencies]
ruff = "^0.5.1"
pre-commit = "^3.7.1"
ruff = "^0.9.1"
pre-commit = "^4.0.0"

[tool.poetry.group.docs.dependencies]
sphinx = "^7.3.7"
furo = "^2024.04.27"
myst-parser = { extras = ["linkify"], version = "^3.0.1" }
sphinx = "^8.1.3"
furo = "^2024.8.6"
myst-parser = { extras = ["linkify"], version = "^4.0.0" }

[tool.poetry.group.tests.dependencies]
pytest = "^8.2.2"
python-dotenv = "^1.0.1"
vcrpy = "^6.0.1"
pytest-cov = "^5.0.0"
vcrpy = "^7.0.0"
pytest-cov = "^6.0.0"
pytest-vcr = "^1.0.2"
jsonschema = "^4.23.0"

Expand Down
2 changes: 1 addition & 1 deletion pytest.xml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="81" time="4.000" timestamp="2024-07-14T04:11:00.916633" hostname="fv-az1493-266"><testcase classname="tests.integration_tests.integration_test_psnawp_api.core.test_authenticator" name="test_authenticator__authentication" time="0.113" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.core.test_authenticator" name="test_authenticator__access_token_from_refresh_token" time="0.049" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.core.test_authenticator" name="test_authenticator__incorrect_npsso" time="0.016" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__online_id" time="0.031" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__account_id" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__get_profile_legacy" time="0.024" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__account_devices" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__get_friends" time="0.027" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__friend_requests" time="0.018" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__get_groups" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__available_to_play" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__blocked_list" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__trophy_summary" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__trophy_titles" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__trophy_titles_for_title" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__trophies" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__trophies_with_progress" time="0.019" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__trophy_groups_summary" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__trophy_groups_summary_with_progress" time="0.019" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__title_stats" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_client" name="test_client__repr_and_str" time="0.019" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_game_title" name="test_game_title__np_communication_id" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_game_title" name="test_game_title__wrong_title_id" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_game_title" name="test_game_title__get_title_details" time="0.019" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_game_title" name="test_game_title__trophies" time="0.020" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_game_title" name="test_game_title__trophy_groups_summary" time="0.019" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_game_title" name="test_game_title__trophies_game_not_owned_by_user" time="0.014" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_game_title" name="test_game_title__trophy_groups_summary_game_not_owned_by_user" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_game_title" name="test_game_title__trophies_invalid_np_communication_id" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_game_title" name="test_game_title__trophy_groups_summary_invalid_np_communication_id" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__group_incorrect_args_None" time="0.006" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__group_with_wrong_id" time="0.014" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__group_with_users" time="0.946" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__group_with_id" time="0.021" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__get_group_information" time="0.023" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__repr_and_str" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__change_name_dm" time="0.021" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__dming_blocked_user" time="0.364" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__change_name" time="0.020" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__kick_member" time="0.021" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__kick_member_not_found" time="0.021" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__invite_members" time="0.021" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__invite_members_blocked" time="0.021" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_group" name="test_group__leave_group" time="0.021" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_search" name="test_search__universal_search" time="0.016" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_search" name="test_search__get_game_content_id" time="0.017" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_search" name="test_search__get_addon_content_id" time="0.031" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__user" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__user_account_id" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__user_no_argument" time="0.006" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__user_wrong_acc_id" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__prev_online_id" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__user_not_found" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__user_acct_id_not_found" time="0.013" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__get_profile" time="0.021" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__get_presence" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__get_presence_forbidden" time="0.011" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__friendship" time="0.015" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__accept_friend_request" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__remove_friend" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__get_friends" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__get_friends_forbidden" time="0.011" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__is_blocked" time="0.011" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophy_summary" time="0.021" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophy_summary_forbidden" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophy_titles" time="0.032" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophy_titles_forbidden" time="0.011" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophy_titles_pagination_test" time="0.291" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophy_titles_for_title" time="0.022" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophy_titles_for_title_forbidden" time="0.012" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophies" time="0.040" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophies_with_progress_forbidden" time="0.018" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophies_pagination_test" time="0.041" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophy_groups_summary" time="0.040" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__trophy_groups_summary_forbidden" time="0.018" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__title_stats" time="0.011" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__title_stats_with_limit" time="0.224" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__title_stats_with_jump" time="0.043" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.models.test_user" name="test_user__repr_and_str" time="0.006" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.utils.test_utils_misc" name="test_play_duration_to_timedelta_valid_inputs" time="0.001" /><testcase classname="tests.integration_tests.integration_test_psnawp_api.utils.test_utils_misc" name="test_play_duration_to_timedelta_invalid_inputs" time="0.001" /></testsuite></testsuites>
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="0" time="0.130" timestamp="2025-01-11T22:28:23.349043-05:00" hostname="pop-os" /></testsuites>
23 changes: 22 additions & 1 deletion src/psnawp_api/models/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
TrophyWithProgressIterator,
)
from psnawp_api.models.user import User
from psnawp_api.utils import API_PATH, BASE_PATH
from psnawp_api.utils import API_PATH, BASE_PATH, extract_region_from_npid

if TYPE_CHECKING:
from pycountry.db import Country

from psnawp_api.core import Authenticator
from psnawp_api.models.trophies import PlatformType, TrophyGroupsSummary, TrophyGroupSummary, TrophyGroupSummaryWithProgress

Expand Down Expand Up @@ -68,6 +70,25 @@ def account_id(self) -> str:
account_id: str = response["accountId"]
return account_id

def get_region(self) -> Optional[Country]:
"""Gets the region of the client logged in the api.

:returns: Returns Country object from Pycountry for logged in user or None if not found.

.. code-block:: Python

client = psnawp.me()
print(client.get_region())

.. note::

See https://github.com/pycountry/pycountry for more info on Country object.

"""
response = self.get_profile_legacy()
npid = response.get("profile", {}).get("npId", "")
return extract_region_from_npid(npid)

def get_profile_legacy(self) -> dict[str, Any]:
"""Gets the profile info from legacy api endpoint. Useful for legacy console (PS3, PS4) presence.

Expand Down
5 changes: 1 addition & 4 deletions src/psnawp_api/models/trophies/trophy_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,7 @@ def __init__(

def __str__(self) -> str:
return (
f"TrophyGroupsSummary(Title: {self.trophy_title_name}, "
f"Defined: {self.defined_trophies}, "
f"Earned: {self.earned_trophies}, "
f"Progress: {self.progress})"
f"TrophyGroupsSummary(Title: {self.trophy_title_name}, Defined: {self.defined_trophies}, Earned: {self.earned_trophies}, Progress: {self.progress})"
)

def __repr__(self) -> str:
Expand Down
52 changes: 51 additions & 1 deletion src/psnawp_api/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
TrophyTitleIterator,
TrophyWithProgressIterator,
)
from psnawp_api.utils import API_PATH, BASE_PATH
from psnawp_api.utils import API_PATH, BASE_PATH, extract_region_from_npid

if TYPE_CHECKING:
from pycountry.db import Country

from psnawp_api.core import Authenticator
from psnawp_api.models.trophies import PlatformType, TrophyGroupsSummary, TrophyGroupSummary, TrophyGroupSummaryWithProgress

Expand Down Expand Up @@ -111,6 +113,54 @@ def profile(self) -> dict[str, Any]:
response: dict[str, Any] = self.authenticator.get(url=f"{BASE_PATH['profile_uri']}{API_PATH['profiles'].format(account_id=self.account_id)}").json()
return response

def get_region(self) -> Optional[Country]:
"""Gets the region of the user.

:returns: Returns Country object from Pycountry of the User or None if not found.

.. code-block:: Python

user_example = psnawp.user(online_id="VaultTec_Trading")
print(user_example.get_region())

.. note::

See https://github.com/pycountry/pycountry for more info on Country object.

"""
response = self.get_profile_legacy()
npid = response.get("profile", {}).get("npId", "")
return extract_region_from_npid(npid)

def get_profile_legacy(self) -> dict[str, Any]:
"""Gets the user profile info from legacy api endpoint. Useful for legacy console (PS3, PS4) presence.

:returns: A dict containing info similar to what is shown below:

.. literalinclude:: examples/client/get_profile_legacy.json
:language: json


.. code-block:: Python

user_example = psnawp.user(online_id="VaultTec_Trading")
print(user_example.get_profile_legacy())

"""
params = {
"fields": "npId,onlineId,accountId,avatarUrls,plus,aboutMe,languagesUsed,trophySummary(@default,level,progress,earnedTrophies),"
"isOfficiallyVerified,personalDetail(@default,profilePictureUrls),personalDetailSharing,personalDetailSharingRequestMessageFlag,"
"primaryOnlineStatus,presences(@default,@titleInfo,platform,lastOnlineDate,hasBroadcastData),requestMessageFlag,blocking,friendRelation,"
"following,consoleAvailability"
}

response: dict[str, Any] = self.authenticator.get(
url=f"{BASE_PATH['legacy_profile_uri']}{API_PATH['legacy_profile'].format(online_id=self.online_id)}",
params=params,
).json()

return response

def get_presence(self) -> dict[str, Any]:
"""Gets the presences of a user. If the profile is private

Expand Down
4 changes: 2 additions & 2 deletions src/psnawp_api/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from psnawp_api.utils.endpoints import API_PATH, BASE_PATH
from psnawp_api.utils.misc import iso_format_to_datetime
from psnawp_api.utils.misc import extract_region_from_npid, iso_format_to_datetime

__all__ = ["BASE_PATH", "API_PATH", "iso_format_to_datetime"]
__all__ = ["BASE_PATH", "API_PATH", "iso_format_to_datetime", "extract_region_from_npid"]
Loading
Loading