diff --git a/CHANGELOG.md b/CHANGELOG.md index 7647529eee..f461265343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,16 @@ These changes are available on the `master` branch, but have not yet been releas ### Changed +- Inline `_InviteMetadata` fields into `IncompleteInvite` and remove the now-unused + `_InviteMetadata` TypedDict. + ([#3313](https://github.com/Pycord-Development/pycord/pull/3313)) + ### Fixed +- Fix `Invite.code` handling when `None` (from `VanityInvitePayload`). `__str__` falls + back to `""`, `.url` and all code-dependent methods raise `ValueError`, and + `Invite.code` is typed as `str | None`. + ([#3313](https://github.com/Pycord-Development/pycord/pull/3313)) - Fix an attribute error in `RoleColours.is_holographic()` when `secondary` or `tertiary` is `None`. ([#3268](https://github.com/Pycord-Development/pycord/pull/3268)) diff --git a/discord/invite.py b/discord/invite.py index 198f0987bf..f0737dc145 100644 --- a/discord/invite.py +++ b/discord/invite.py @@ -545,7 +545,7 @@ def __init__( ): self._state: ConnectionState = state self.max_age: int | None = data.get("max_age") - self.code: str = data["code"] + self.code: str | None = data.get("code") self.guild: InviteGuildType | None = self._resolve_guild( data.get("guild"), guild ) @@ -689,7 +689,7 @@ def _resolve_roles( return [Object(role_id) for role_id in role_ids] def __str__(self) -> str: - return self.url + return self.code or "" def __repr__(self) -> str: return ( @@ -705,13 +705,15 @@ def __hash__(self) -> int: return hash(self.code) @property - def id(self) -> str: + def id(self) -> str: # type: ignore[override] """Returns the proper code portion of the invite.""" - return self.code + return self.code or "" @property def url(self) -> str: """A property that retrieves the invite URL.""" + if self.code is None: + raise ValueError("Cannot build an invite URL when code is None") return f"{self.BASE}/{self.code}{f'?event={self.scheduled_event.id}' if self.scheduled_event else ''}" @property @@ -719,7 +721,14 @@ def target_users(self) -> InviteTargetUsers: """An :class:`InviteTargetUsers` object for managing the target users list for this invite. .. versionadded:: 2.8 + + Raises + ------ + ValueError + The invite has no code. """ + if self.code is None: + raise ValueError("Cannot manage target users for an invite with no code") return InviteTargetUsers(invite_code=self.code, state=self._state) async def edit_target_users(self, target_users_file: File) -> None: @@ -738,6 +747,8 @@ async def edit_target_users(self, target_users_file: File) -> None: Raises ------ + ValueError + The invite has no code. HTTPException Updating the target users failed. Forbidden @@ -745,6 +756,8 @@ async def edit_target_users(self, target_users_file: File) -> None: NotFound The invite is invalid or expired. """ + if self.code is None: + raise ValueError("Cannot edit target users for an invite with no code") await self._state.http.update_invite_target_users( self.code, target_users_file=target_users_file ) @@ -764,6 +777,8 @@ async def fetch_target_users_job_status(self) -> InviteTargetUsersJobStatus: Raises ------ + ValueError + The invite has no code. HTTPException Fetching the job status failed. NotFound @@ -771,6 +786,10 @@ async def fetch_target_users_job_status(self) -> InviteTargetUsersJobStatus: Forbidden You do not have permission to view the target users. """ + if self.code is None: + raise ValueError( + "Cannot fetch target users job status for an invite with no code" + ) r = await self._state.http.get_invite_target_users_job_status(self.code) return InviteTargetUsersJobStatus(data=r) @@ -788,6 +807,8 @@ async def delete(self, *, reason: str | None = None): Raises ------ + ValueError + The invite has no code. Forbidden You do not have permissions to revoke invites. NotFound @@ -796,6 +817,8 @@ async def delete(self, *, reason: str | None = None): Revoking the invite failed. """ + if self.code is None: + raise ValueError("Cannot delete an invite with no code") await self._state.http.delete_invite(self.code, reason=reason) def set_scheduled_event(self, event: ScheduledEvent) -> None: diff --git a/discord/types/invite.py b/discord/types/invite.py index 584b573094..6d2b511b69 100644 --- a/discord/types/invite.py +++ b/discord/types/invite.py @@ -40,22 +40,20 @@ InviteTargetType = Literal[1, 2] -class _InviteMetadata(TypedDict, total=False): - uses: int - max_uses: int - max_age: int - temporary: bool - created_at: str - expires_at: str | None - - -class VanityInvite(_InviteMetadata): +class VanityInvite(TypedDict): code: str | None + uses: int -class IncompleteInvite(_InviteMetadata): +class IncompleteInvite(TypedDict): code: str channel: PartialChannel + uses: NotRequired[int] + max_uses: NotRequired[int] + max_age: NotRequired[int] + temporary: NotRequired[bool] + created_at: NotRequired[str] + expires_at: NotRequired[str | None] class Invite(IncompleteInvite):