diff --git a/discord/channel.py b/discord/channel.py index be3315cf..d741015c 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -45,7 +45,7 @@ import datetime import discord.abc from .permissions import PermissionOverwrite, Permissions -from .enums import ChannelType, StagePrivacyLevel, try_enum, VoiceRegion, VideoQualityMode +from .enums import ChannelType, StagePrivacyLevel, try_enum, VoiceRegion, VideoQualityMode, PartyType from .mixins import Hashable from .object import Object from . import utils @@ -1037,6 +1037,38 @@ class VoiceChannel(VocalGuildChannel): # the payload will always be the proper channel payload return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + async def create_party(self, application_id: PartyType, max_age: int = 86400 , max_uses: int = 0): + """|coro| + Creates a party in this voice channel. + + .. versionadded:: 2.0 + + Parameters + ---------- + application_id : :class:`PartyType` + The id of the application the party belongs to. currently any of + ``PartyType.youtube``, ``PartyType.poker``, ``PartyType.betrayal``, ``PartyType.fishing``, ``PartyType.chess``. + max_age : :class:`int`, optional + Duration in seconds after which the invite expires, by default 1 day. + max_uses : :class:`int`, optional + maximum number of times this invite can be used, by default unlimited. + + Raises + ------- + Forbidden + You do not have permissions to create a party. + HTTPException + Party creation failed. + + Returns + -------- + :class:`Party` + The created party. + """ + return Party(await self._state.http.create_party(self.id, application_id.value, max_age=max_age, max_uses=max_uses)) + + + class StageChannel(VocalGuildChannel): """Represents a Discord guild stage channel. @@ -2038,6 +2070,76 @@ class PartialMessageable(discord.abc.Messageable, Hashable): return PartialMessage(channel=self, id=message_id) +class Party: + """Represents a party in a voice channel. + + .. container:: operations + + .. describe:: x == y + + Checks if two party are equal. + + .. describe:: x != y + + Checks if two party are not equal. + + .. describe:: hash(x) + + Returns the party hash. + + .. describe:: str(x) + + Returns the party URL. + + Attributes + ----------- + code: :class:`str` + The URL fragment used for the party. + uses: :class:`int` + How many times the invite has been used. + max_uses: :class:`int` + How many times the invite can be used. + A value of ``0`` indicates that it has unlimited uses. + max_age: :class:`int` + How long before the party expires in seconds. + A value of ``0`` indicates that it doesn't expire. + temporary: :class:`bool` + Indicates that the invite grants temporary membership. + If ``True``, members who joined via this invite will be kicked upon disconnect. + created_at: :class:`datetime.datetime` + An aware UTC datetime object denoting the time the invite was created. + + Note + ---- + Parties are still in BETA so there are some limitations. + Currently this BETA feature is only supported on web and updated PC app versions of Discord and is not supported on mobile. + First someone has to to click the blue link itself and not the join button, then everyone else can click the join button normally. + """ + + __slots__ = ('code', 'uses', 'max_uses', 'max_age', 'temporary', 'created_at') + + def __init__(self, data): + self.code: str = data['code'] + self.uses: Optional[int] = data.get('uses') + self.max_uses: Optional[int] = data.get('max_uses') + self.max_age: Optional[int] = data.get('max_age') + self.temporary: Optional[bool] = data.get('temporary') + self.created_at: Optional[datetime.datetime] = utils.parse_time(data.get('created_at')) + # TODO: add more fields here such as guild. raw data: https://mystb.in/AdvertisersExperiencesMothers.json + + def __repr__(self): + return f'' + + def __str__(self): + return f'https://discord.gg/{self.code}' + + def __hash__(self) -> int: + return hash(self.code) + + def __eq__(self, other): + return isinstance(other, Party) and self.code == other.code + + def _guild_channel_factory(channel_type: int): value = try_enum(ChannelType, channel_type) diff --git a/discord/enums.py b/discord/enums.py index af8ee2b0..6a26bc41 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -30,6 +30,7 @@ __all__ = ( 'Enum', 'ChannelType', 'MessageType', + 'PartyType', 'VoiceRegion', 'SpeakingState', 'VerificationLevel', @@ -214,6 +215,14 @@ class MessageType(Enum): guild_invite_reminder = 22 +class PartyType(Enum): + youtube = 755600276941176913 + poker = 755827207812677713 + betrayal = 773336526917861400 + fishing = 814288819477020702 + chess = 832012774040141894 + + class VoiceRegion(Enum): us_west = 'us-west' us_east = 'us-east' diff --git a/discord/http.py b/discord/http.py index 7a4c2adc..625a206d 100644 --- a/discord/http.py +++ b/discord/http.py @@ -880,6 +880,24 @@ class HTTPClient: ) -> Response[None]: return self.request(Route('DELETE', '/channels/{channel_id}', channel_id=channel_id), reason=reason) + def create_party( + self, + channel_id: Snowflake, + application_id: Snowflake, + max_age: int, + max_uses: int, + ) -> Response[channel.Party]: + payload = { + 'max_age': max_age, + 'max_uses': max_uses, + 'target_application_id': application_id, + 'target_type': 2, + 'temporary': False, + 'validate': None + } + return self.request(Route("POST", "/channels/{channel_id}/invites", channel_id=channel_id), json=payload) + + # Thread management def start_thread_with_message( diff --git a/discord/types/channel.py b/discord/types/channel.py index a35351e4..c2b0b524 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -155,3 +155,10 @@ class StageInstance(TypedDict): topic: str privacy_level: PrivacyLevel discoverable_disabled: bool + +class Party(TypedDict): + uses: int + max_uses: int + max_age: int + temporary: bool + created_at: str