Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bbb8e45
fix(invite): guard against None code
vmphase Jul 21, 2026
2bd7ace
chore: update changelog
vmphase Jul 21, 2026
43af42c
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 21, 2026
ed05041
fix(invite): return "" on id property when code is None
vmphase Jul 21, 2026
d348ffc
refactor(types/invite): do not inherit VanityInvite from _InviteMetadata
vmphase Jul 21, 2026
70099ec
fix(invite): raise ValueError when code is None
vmphase Jul 21, 2026
6a5cfa1
refactor(types/invite): inline _InviteMetadata into IncompleteInvite
vmphase Jul 21, 2026
1812204
chore: update changelog
vmphase Jul 21, 2026
bc00082
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 21, 2026
6b657a8
Merge branch 'master' into fix/invite-optional-code
vmphase Jul 26, 2026
a665bcf
chore: simplify changelog
vmphase Jul 27, 2026
a1c109e
fix(invite): preserve `__str__` return value for invites with a code
vmphase Jul 27, 2026
5a04ef4
Merge branch 'master' into fix/invite-optional-code
vmphase Jul 27, 2026
442ccbc
feat(invite): deprecate Invite.id
vmphase Jul 27, 2026
c921a59
chore: fix changelog
vmphase Jul 27, 2026
7ab228a
feat(invite; guild): revert guards, cast data to IncompleteInvite
vmphase Jul 27, 2026
6a51a9b
chore: update changelog
vmphase Jul 27, 2026
0e6b178
Merge branch 'master' into fix/invite-optional-code
Lulalaby Jul 30, 2026
c648a25
Merge branch 'master' into fix/invite-optional-code
Lulalaby Jul 30, 2026
ad37500
revert: `__str__` and `.id` checks
vmphase Aug 3, 2026
2d2c542
chore(changelog): remove "changed" log
vmphase Aug 3, 2026
708e200
Merge branch 'master' into fix/invite-optional-code
vmphase Aug 7, 2026
a395fe4
Merge branch 'master' into fix/invite-optional-code
NeloBlivion Aug 7, 2026
9b43c49
chore(invite): remove leftovers; improve deprecation message
vmphase Aug 10, 2026
25f6ce1
Merge branch 'master' into fix/invite-optional-code
vmphase Aug 10, 2026
e9abe29
Update discord/invite.py
vmphase Aug 10, 2026
7d0a069
Update CHANGELOG.md
vmphase Aug 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Comment thread
vmphase marked this conversation as resolved.
Outdated
- Fix an attribute error in `RoleColours.is_holographic()` when `secondary` or
`tertiary` is `None`.
([#3268](https://github.com/Pycord-Development/pycord/pull/3268))
Expand Down
31 changes: 27 additions & 4 deletions discord/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -689,7 +689,7 @@ def _resolve_roles(
return [Object(role_id) for role_id in role_ids]

def __str__(self) -> str:
return self.url
Comment thread
vmphase marked this conversation as resolved.
Comment thread
vmphase marked this conversation as resolved.
return self.code or ""

def __repr__(self) -> str:
return (
Expand All @@ -705,21 +705,30 @@ def __hash__(self) -> int:
return hash(self.code)

@property
def id(self) -> str:
def id(self) -> str: # type: ignore[override]
Comment thread
vmphase marked this conversation as resolved.
"""Returns the proper code portion of the invite."""
return self.code
return self.code or ""
Comment thread
vmphase marked this conversation as resolved.
Outdated

@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")
Comment thread
vmphase marked this conversation as resolved.
Outdated
return f"{self.BASE}/{self.code}{f'?event={self.scheduled_event.id}' if self.scheduled_event else ''}"

@property
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:
Expand All @@ -738,13 +747,17 @@ 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
You do not have permissions to edit this invite.
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
)
Expand All @@ -764,13 +777,19 @@ async def fetch_target_users_job_status(self) -> InviteTargetUsersJobStatus:

Raises
------
ValueError
The invite has no code.
HTTPException
Fetching the job status failed.
NotFound
The invite is invalid or expired.
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)

Expand All @@ -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
Expand All @@ -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:
Expand Down
20 changes: 9 additions & 11 deletions discord/types/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down