Skip to content

Commit

Permalink
feat: Add methods to invite and kick users to/from Group
Browse files Browse the repository at this point in the history
- Add methods to invite and kick users to/from Group
- Improve type hints for some methods in Group
- Add test for new features
- Re-run the relevant integration tests
  • Loading branch information
isFakeAccount committed Jul 14, 2024
1 parent 0469143 commit dc2ebd0
Show file tree
Hide file tree
Showing 15 changed files with 1,189 additions and 283 deletions.
126 changes: 108 additions & 18 deletions src/psnawp_api/models/group.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any, Iterable, Optional
from typing import TYPE_CHECKING, Any, Iterable, Optional, TypedDict

from typing_extensions import Self

Expand All @@ -17,6 +16,65 @@
from psnawp_api.models.user import User


class Sender(TypedDict):
accountId: str
onlineId: str


class LatestMessage(TypedDict):
alternativeMessageType: int
body: str
createdTimestamp: str
messageType: int
messageUid: str
sender: Sender


class MainThread(TypedDict):
latestMessage: LatestMessage
modifiedTimestamp: str
readMessageUid: str
threadId: str


class Member(TypedDict):
accountId: str
onlineId: str
role: str


class GroupIcon(TypedDict):
status: int


class GroupName(TypedDict):
status: int
value: str


class NotificationSetting(TypedDict):
isMute: bool


class GroupDetails(TypedDict):
existsNewArrival: bool
groupIcon: GroupIcon
groupId: str
groupName: GroupName
groupType: int
isFavorite: bool
joinedTimestamp: str
mainThread: MainThread
members: list[Member]
modifiedTimestamp: str
notificationSetting: NotificationSetting


class MessageResponse(TypedDict):
messageUid: str
createdTimestamp: str


class Group:
"""The Group class manages PSN group endpoints related to messages (Group and Direct Messages)."""

Expand All @@ -41,7 +99,6 @@ def __init__(

self.authenticator = authenticator
self.group_id = group_id
self._group_common_header = {"Content-Type": "application/json"}

@classmethod
def create_from_group_id(cls, authenticator: Authenticator, group_id: str) -> Self:
Expand All @@ -54,18 +111,17 @@ def create_from_users(cls, authenticator: Authenticator, users: Iterable[User])
:raises PSNAWPForbidden: If you are sending message a user who has blocked you.
"""
invitees = [{"accountId": user.account_id} for user in users]
data = {"invitees": invitees}
account_ids = [{"accountId": user.account_id} for user in users]
invitees = {"invitees": account_ids}

try:
response = authenticator.post(
url=f"{BASE_PATH['gaming_lounge']}{API_PATH['create_group']}",
data=json.dumps(data),
headers={"Content-Type": "application/json"},
json=invitees,
).json()
return cls(authenticator, response["groupId"])
except PSNAWPForbidden as forbidden:
raise PSNAWPForbidden("The group cannot be created because the user has either set messages to private or has blocked you.") from forbidden
raise PSNAWPForbidden("Can't create group because of the players' privacy settings.") from forbidden

def change_name(self, group_name: str) -> None:
"""Changes the group name to one specified in arguments.
Expand All @@ -86,13 +142,12 @@ def change_name(self, group_name: str) -> None:
try:
self.authenticator.patch(
url=f"{BASE_PATH['gaming_lounge']}{API_PATH['group_settings'].format(group_id=self.group_id)}",
data=json.dumps(data),
headers=self._group_common_header,
json=data,
)
except PSNAWPBadRequest as bad_req:
raise PSNAWPBadRequest(f"The group name of Group ID {self.group_id} does cannot be changed. Group is either a dm or does not exist.") from bad_req

def get_group_information(self) -> dict[str, Any]:
def get_group_information(self) -> GroupDetails:
"""Gets the group chat information such as about me, avatars, languages etc...
:returns: A dict containing info similar to what is shown below:
Expand All @@ -105,12 +160,11 @@ def get_group_information(self) -> dict[str, Any]:
"""

param = {
"includeFields": "groupName,groupIcon,members,mainThread,joinedTimestamp,modifiedTimestamp,isFavorite,existsNewArrival,partySessions,"
"notificationSetting"
"includeFields": "groupName,groupIcon,members,mainThread,joinedTimestamp,modifiedTimestamp,isFavorite,existsNewArrival,notificationSetting",
}

try:
response: dict[str, Any] = self.authenticator.get(
response: GroupDetails = self.authenticator.get(
url=f"{BASE_PATH['gaming_lounge']}{API_PATH['group_members'].format(group_id=self.group_id)}",
params=param,
).json()
Expand All @@ -119,7 +173,7 @@ def get_group_information(self) -> dict[str, Any]:
except PSNAWPNotFound as not_found:
raise PSNAWPNotFound(f"Group ID {self.group_id} does not exist.") from not_found

def send_message(self, message: str) -> dict[str, str]:
def send_message(self, message: str) -> MessageResponse:
"""Sends a message in the group.
.. note::
Expand All @@ -141,10 +195,9 @@ def send_message(self, message: str) -> dict[str, str]:

data = {"messageType": 1, "body": message}

response: dict[str, str] = self.authenticator.post(
response: MessageResponse = self.authenticator.post(
url=f"{BASE_PATH['gaming_lounge']}{API_PATH['send_group_message'].format(group_id=self.group_id)}",
data=json.dumps(data),
headers=self._group_common_header,
json=data,
).json()

return response
Expand Down Expand Up @@ -198,6 +251,43 @@ def leave_group(self) -> None:

self.authenticator.delete(url=f"{BASE_PATH['gaming_lounge']}{API_PATH['leave_group'].format(group_id=self.group_id)}")

def invite_members(self, users: Iterable[User]) -> None:
"""
Invite users to join the group.
If all users in the invite list have blocked you, a PSNAWPForbidden
exception is raised. Users who have blocked you are skipped, and
the remaining users are invited.
:param users: An iterable of User objects to be invited.
:type users: Iterable[User]
:raises PSNAWPForbidden: If all users in the invite list have blocked you.
"""
account_ids = [{"accountId": user.account_id} for user in users]
invitees = {"invitees": account_ids}

try:
self.authenticator.post(
url=f"{BASE_PATH['gaming_lounge']}{API_PATH['create_group']}",
json=invitees,
).json()
except PSNAWPForbidden as forbidden:
raise PSNAWPForbidden("Can't add users to group because of the players' privacy settings.") from forbidden

def kick_member(self, user: User) -> None:
"""
Remove a user from the group.
:param user: The User object representing the member to be removed.
:raises PSNAWPNotFound: If the user is not in the group.
"""
try:
self.authenticator.delete(url=f"{BASE_PATH['gaming_lounge']}{API_PATH['kick_member'].format(group_id=self.group_id, account_id=user.account_id)}")
except PSNAWPNotFound as not_found:
raise PSNAWPNotFound(f'User "{user.online_id}" is not a member of the group.') from not_found

def __repr__(self) -> str:
return f"<Group group_id:{self.group_id}>"

Expand Down
3 changes: 2 additions & 1 deletion src/psnawp_api/utils/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"account_uri": "https://dms.api.playstation.com/api",
"legacy_profile_uri": "https://us-prof.np.community.playstation.net/userProfile/v1/users",
"gaming_lounge": "https://m.np.playstation.com/api/gamingLoungeGroups/v1",
"group_messaging": "https://us-gmsg.np.community.playstation.net/groupMessaging/v1",
"universal_search": "https://m.np.playstation.com/api/search/v1/universalSearch",
"game_titles": "https://m.np.playstation.com/api/catalog/v2/titles",
"trophies": "https://m.np.playstation.com/api/trophy/v1",
Expand Down Expand Up @@ -32,6 +31,8 @@
"group_settings": "/groups/{group_id}",
"create_group": "/groups",
"group_members": "/members/me/groups/{group_id}",
"invite_members": "/groups/{group_id}/invitees",
"kick_member": "/groups/{group_id}/members/{account_id}",
"send_group_message": "/groups/{group_id}/threads/{group_id}/messages",
"conversation": "/members/me/groups/{group_id}/threads/{group_id}/messages",
"leave_group": "/groups/{group_id}/members/me",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
"Country": [
"US"
],
"Content-Type": [
"application/json"
],
"Content-Length": [
"90"
],
"Content-Type": [
"application/json"
]
}
},
Expand All @@ -39,50 +39,50 @@
"message": "OK"
},
"headers": {
"Connection": [
"keep-alive"
"Cache-Control": [
"private"
],
"Access-Control-Allow-Headers": [
"Authorization, Content-Type"
],
"Access-Control-Allow-Methods": [
"GET, PUT, POST, DELETE, PATCH, OPTIONS"
],
"Server": [
"Apache"
],
"Cache-Control": [
"private"
],
"Content-Length": [
"159"
],
"Date": [
"Sun, 14 Jul 2024 02:19:18 GMT"
],
"Strict-Transport-Security": [
"max-age=63072000; includeSubDomains"
],
"Access-Control-Allow-Headers": [
"Authorization, Content-Type"
],
"X-Content-Type-Options": [
"nosniff"
],
"Access-Control-Allow-Methods": [
"GET, PUT, POST, DELETE, PATCH, OPTIONS"
],
"Content-Type": [
"application/json"
],
"Set-Cookie": "REDACTED",
"Date": [
"Sun, 16 Jun 2024 22:59:24 GMT"
"Connection": [
"keep-alive"
],
"Access-Control-Allow-Origin": [
"*"
],
"Set-Cookie": "REDACTED",
"Content-Type": [
"application/json"
]
},
"body": {
"string": "{\"groupId\": \"1fdfc50ef4c99b51f8885c3caf2b3feedb7e5323-597\", \"mainThread\": {\"threadId\": \"1fdfc50ef4c99b51f8885c3caf2b3feedb7e5323-597\"}, \"hasAllAccountInvited\": true}"
"string": "{\"groupId\": \"dc6266af0b99f2f7e0a24441e4ee9058c0034b87-961\", \"mainThread\": {\"threadId\": \"dc6266af0b99f2f7e0a24441e4ee9058c0034b87-961\"}, \"hasAllAccountInvited\": true}"
}
}
},
{
"request": {
"method": "PATCH",
"uri": "https://m.np.playstation.com/api/gamingLoungeGroups/v1/groups/1fdfc50ef4c99b51f8885c3caf2b3feedb7e5323-597",
"uri": "https://m.np.playstation.com/api/gamingLoungeGroups/v1/groups/dc6266af0b99f2f7e0a24441e4ee9058c0034b87-961",
"body": "{\"groupName\": {\"value\": \"Testing API\"}}",
"headers": {
"User-Agent": [
Expand All @@ -103,11 +103,11 @@
"Country": [
"US"
],
"Content-Type": [
"application/json"
],
"Content-Length": [
"39"
],
"Content-Type": [
"application/json"
]
}
},
Expand All @@ -117,33 +117,33 @@
"message": "No Content"
},
"headers": {
"Connection": [
"keep-alive"
"Cache-Control": [
"private"
],
"Access-Control-Allow-Headers": [
"Authorization, Content-Type"
],
"Access-Control-Allow-Methods": [
"GET, PUT, POST, DELETE, PATCH, OPTIONS"
],
"Server": [
"Apache"
],
"Access-Control-Allow-Origin": [
"*"
],
"Cache-Control": [
"private"
"Date": [
"Sun, 14 Jul 2024 02:19:18 GMT"
],
"Strict-Transport-Security": [
"max-age=63072000; includeSubDomains"
],
"X-Content-Type-Options": [
"nosniff"
],
"Access-Control-Allow-Methods": [
"GET, PUT, POST, DELETE, PATCH, OPTIONS"
],
"Set-Cookie": "REDACTED",
"Date": [
"Sun, 16 Jun 2024 22:59:24 GMT"
"Connection": [
"keep-alive"
],
"Access-Control-Allow-Headers": [
"Authorization, Content-Type"
"Access-Control-Allow-Origin": [
"*"
],
"X-Content-Type-Options": [
"nosniff"
]
},
"body": {
Expand Down
Loading

0 comments on commit dc2ebd0

Please sign in to comment.