diff --git a/roblox/bases/basegroup.py b/roblox/bases/basegroup.py index c216add0..ca4363da 100644 --- a/roblox/bases/basegroup.py +++ b/roblox/bases/basegroup.py @@ -10,6 +10,7 @@ from datetime import datetime from dateutil.parser import parse +from enum import Enum from .baseitem import BaseItem from ..members import Member, MemberRelationship @@ -18,13 +19,13 @@ from ..shout import Shout from ..sociallinks import SocialLink from ..utilities.exceptions import InvalidRole -from ..utilities.iterators import PageIterator, SortOrder +from ..utilities.iterators import PageIterator, RowIterator, SortOrder from ..wall import WallPost, WallPostRelationship if TYPE_CHECKING: from ..client import Client from .baseuser import BaseUser - from ..utilities.types import UserOrUserId, RoleOrRoleId + from ..utilities.types import UserOrUserId, RoleOrRoleId, GroupOrGroupId class JoinRequest: @@ -118,6 +119,96 @@ def __repr__(self): return f"<{self.__class__.__name__} name={self.name!r} created={self.created}>" +class GroupRelationshipType(Enum): + """ + Represents a type of relationship between two groups. + """ + + allies = "allies" + enemies = "enemies" + + +class GroupRelationship: + """ + Represents a relationship between two groups. + + Attributes: + relationship_type: The type of relationship between both groups. + group: The group. + related_group: The related group. + """ + + def __init__(self, client: Client, data: dict, group: BaseGroup, relationship_type: GroupRelationshipType): + """ + Arguments: + client: The Client this object belongs to. + data: A GroupDetailResponse Object. + group: The group getting queried for it's relationships. + relationship_type: The type of relationship between both groups. + """ + + from ..groups import Group + + self._client: Client = client + self.relationship_type: GroupRelationshipType = relationship_type + self.group: BaseGroup = group + self.related_group: Group = Group(client=client, data=data) + + async def remove(self): + """ + Severs the relationship between both groups. + """ + + await self._client.requests.delete( + url=self._client.url_generator.get_url("groups", f"v1/groups/{self.group.id}/relationships/{self.relationship_type.value}/{self.related_group.id}") + ) + + +class GroupRelationshipRequest: + """ + Represents a request to establish a relationship with another group. + + Attributes: + relationship_type: The type of relationship to be established between both groups. + group: The group that received the request. + related_group: The group that sent the request. + """ + + def __init__(self, client: Client, data: dict, group: BaseGroup, relationship_type: GroupRelationshipType): + """ + Arguments: + client: The Client this object belongs to. + data: A GroupDetailResponse Object. + group: The group that received the request. + relationship_type: The type of relationship to be established between both groups. + """ + + from ..groups import Group + + self._client: Client = client + self.relationship_type: GroupRelationshipType = relationship_type + self.group: BaseGroup = group + self.related_group: Group = Group(client=client, data=data) + + async def accept(self): + """ + Accepts the relationship request. + """ + + await self._client.requests.post( + url=self._client.url_generator.get_url("groups", f"v1/groups/{self.group.id}/relationships/{self.relationship_type.value}/requests/{self.related_group.id}") + ) + + async def decline(self): + """ + Declines the relationship request. + """ + + await self._client.requests.delete( + url=self._client.url_generator.get_url("groups", f"v1/groups/{self.group.id}/relationships/{self.relationship_type.value}/requests/{self.related_group.id}") + ) + + class BaseGroup(BaseItem): """ Represents a Roblox group ID. @@ -473,4 +564,121 @@ def get_name_history( sort_order=sort_order, max_items=max_items, handler=lambda client, data: GroupNameHistoryItem(client=client, data=data), - ) \ No newline at end of file + ) + + def get_ally_relationship_requests( + self, + rows: int = 50, + sort_order: SortOrder = SortOrder.Ascending, + max_items: int = None + ) -> RowIterator: + """ + Returns the group's pending ally requests from other groups. + + Arguments: + rows: How many ally requests should be returned for each iteration. + sort_order: Order in which data should be grabbed. + max_items: The maximum items to return when looping through this object. + + Returns: + A RowIterator containing the groups's pending relationship requests. + """ + + return RowIterator( + client=self._client, + url=self._client._url_generator.get_url("groups", f"v1/groups/{self.id}/relationships/{GroupRelationshipType.allies.value}/requests"), + data_field_name="relatedGroups", + sort_order=sort_order, + rows=rows, + max_rows=max_items, + handler=lambda client, data, group, relationship_type: GroupRelationshipRequest(client, data, group, relationship_type), + handler_kwargs={"group": self, "relationship_type": GroupRelationshipType.allies} + ) + + def get_relationships( + self, + relationshipType: GroupRelationshipType = GroupRelationshipType.allies, + rows: int = 50, + sort_order: SortOrder = SortOrder.Ascending, + max_items: int = None, + ) -> RowIterator: + """ + Returns established relationships with other groups. + + Arguments: + relationshipType: The type of relationship established between both groups. + rows: How many relationships should be returned for each iteration. + sort_order: Order in which data should be grabbed. + max_items: The maximum items to return when looping through this object. + + Returns: + A RowIterator containing the groups's relationships. + """ + + return RowIterator( + client=self._client, + url=self._client._url_generator.get_url("groups", f"v1/groups/{self.id}/relationships/{relationshipType.value}"), + data_field_name="relatedGroups", + sort_order=sort_order, + rows=rows, + max_rows=max_items, + handler=lambda client, data, group, relationship_type: GroupRelationship(client, data, group, relationship_type), + handler_kwargs={"group": self, "relationship_type": relationshipType} + ) + + async def request_relationship( + self, + group: GroupOrGroupId, + relationshipType: GroupRelationshipType = GroupRelationshipType.allies + ): + """ + Requests to establish a relationship with another group. + + Arguments: + group: The group to prepose the relationship with. + relationshipType: The type of relationship to be established. + """ + + await self._client.requests.post( + url=self._client.url_generator.get_url("groups", f"v1/groups/{self.id}/relationships/{relationshipType.value}/{int(group)}") + ) + + async def decline_relationship_requests( + self, + groups: List[GroupOrGroupId], + relationshipType: GroupRelationshipType = GroupRelationshipType.allies + ): + """ + Declines all relationship requests from a list of groups. + + Arguments: + groups: A list of groups to decline. + relationshipType: The type of relationship. + """ + + await self._client.requests.delete( + url=self._client.url_generator.get_url("groups", f"v1/groups/{self.id}/relationships/{relationshipType.value}/requests"), + json={ + "GroupIds": list(map(int, groups)) + } + ) + + async def accept_relationship_requests( + self, + groups: List[GroupOrGroupId], + relationshipType: GroupRelationshipType = GroupRelationshipType.allies + ): + """ + Accepts all relationship requests from a list of groups. + + Arguments: + groups: A list of groups to accept. + relationshipType: The type of relationship. + """ + + await self._client.requests.post( + url=self._client.url_generator.get_url("groups", f"v1/groups/{self.id}/relationships/{relationshipType.value}/requests"), + json={ + "GroupIds": list(map(int, groups)) + } + ) diff --git a/roblox/utilities/iterators.py b/roblox/utilities/iterators.py index 11ec82f3..54d06dfd 100644 --- a/roblox/utilities/iterators.py +++ b/roblox/utilities/iterators.py @@ -5,7 +5,7 @@ """ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, List if TYPE_CHECKING: from ..client import Client @@ -323,3 +323,81 @@ async def next(self): ] return data + +class RowIterator(RobloxIterator): + """ + Represents an iterator that is meant to iterate over rows, like those seen on groups.roblox.com. + + Attributes: + url: The endpoint to hit for new row data. + data_field_name: The name of the field containing the data. + sort_order: The sort order to use for returned data. + row_index: The starting row index. + rows: The maximum amount of rows to return on each iteration. + max_rows: The maximum amount of items to return when this iterator is looped through. + extra_parameters: Extra parameters to pass to the endpoint. + handler: A callable object to use to convert raw endpoint data to parsed objects. + handler_kwargs: Extra keyword arguments to pass to the handler. + """ + + def __init__( + self, + client: Client, + url: str, + data_field_name: str, + sort_order: SortOrder = SortOrder.Ascending, + row_index: int = 0, + rows: int = 50, + max_rows: int = None, + extra_parameters: Optional[dict] = None, + handler: Optional[Callable] = None, + handler_kwargs: Optional[dict] = None + ): + super().__init__() + + self._client: Client = client + + self.url: str = url + self.data_field_name: str = data_field_name + self.sort_order: SortOrder = sort_order + self.row_index: int = row_index + self.rows: int = rows + self.max_items: int = max_rows + + self.extra_parameters: Dict = extra_parameters or {} + self.handler: Callable = handler + self.handler_kwargs: Dict = handler_kwargs or {} + + async def next(self) -> List: + """ + Advances the iterator to the next set of rows. + """ + + page_response = await self._client.requests.get( + url=self.url, + params={ + "startRowIndex": self.row_index, + "maxRows": self.rows, + "sortOrder": self.sort_order.value, + **self.extra_parameters + } + ) + body: dict = page_response.json() + next_row_index: int = body.get("nextRowIndex") + data = body.get(self.data_field_name) + + if len(data) == 0: + raise NoMoreItems("No more items.") + + self.row_index = next_row_index + + if self.handler: + data = [ + self.handler( + client=self._client, + data=item_data, + **self.handler_kwargs + ) for item_data in data + ] + + return data