Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
050445b
Empty commit
Paillat-dev Dec 26, 2025
d85bd66
:sparkles: Implement roles parameter of `GuildChannel.create_invite`
Paillat-dev Dec 26, 2025
a6251ca
Merge branch 'master' into feat/community-invites-api
Paillat-dev Jan 13, 2026
1c48ee7
Merge branch 'master' into feat/community-invites-api
Paillat-dev Jan 25, 2026
a4feeb1
feat: :sparkles: Add roles attributes and parsing to Invite class
Paillat-dev Jan 27, 2026
443992f
feat: :sparkles: Add `target_users_file` to `Channel.create_invite` m…
Paillat-dev Jan 27, 2026
63c8706
feat: :sparkles: Implement target users management for invites with j…
Paillat-dev Jan 27, 2026
099b9f6
Merge branch 'master' into feat/community-invites-api
Paillat-dev Jan 27, 2026
2f94bc8
:recycle: Simplify logic
Paillat-dev Jan 27, 2026
a563702
:memo: Docs and exports
Paillat-dev Jan 27, 2026
049e985
:memo: Docs fixes
Paillat-dev Jan 27, 2026
641c56c
:label: Not required fields
Paillat-dev Jan 27, 2026
db0100e
:recycle: parse_time
Paillat-dev Jan 27, 2026
1d16beb
:adhesive_bandage: Roles also when guild is not guilding
Paillat-dev Jan 27, 2026
c2aa2dd
Apply suggestions from code review
Paillat-dev Jan 27, 2026
b7032a5
Apply suggestions from code review
Paillat-dev Jan 27, 2026
25a3e3a
:adhesive_bandage: Copilot fixes
Paillat-dev Jan 27, 2026
bcc8fc0
:sparkles: Add `InviteTargetUsers.get_user_ids`
Paillat-dev Jan 27, 2026
88020e7
:sparkles: Add `users_to_csv` util to convert users to CSV format
Paillat-dev Jan 27, 2026
4520474
:bug: Copilot is dumb af
Paillat-dev Jan 28, 2026
7a5b4ee
:recycle: Use `target_users_file` consistently
Paillat-dev Jan 28, 2026
a912361
:memo: Add example
Paillat-dev Jan 28, 2026
2c4ac00
:memo: Add perms notice to `get_user_ids`
Paillat-dev Jan 28, 2026
308da6c
Update CHANGELOG.md
Paillat-dev Jan 28, 2026
6efadbc
:bug: Skip header when parsing user IDs in `Invite.get_user_ids`
Paillat-dev Feb 7, 2026
0def07b
:truck: Rename `get_user_ids` to `fetch_user_ids`
Paillat-dev Feb 7, 2026
d87f506
:memo: Clarify role object behavior in `Client.fetch_invite` document…
Paillat-dev Feb 7, 2026
25e9118
Merge branch 'master' into feat/community-invites-api
Paillat-dev Feb 13, 2026
30fcf35
:recycle: `json_or_text` -> `parse_response`
Paillat-dev Feb 13, 2026
fb18bda
Update discord/invite.py
Paillat-dev Feb 16, 2026
0bb3c3b
Update discord/invite.py
Paillat-dev Feb 16, 2026
e425f41
Merge branch 'master' into feat/community-invites-api
Paillat-dev Feb 20, 2026
a092334
Merge branch 'master' into feat/community-invites-api
Paillat-dev Feb 21, 2026
d77cb2e
:recycle: `fetch_user_ids` -> `as_user_ids` to clarify
Paillat-dev Feb 21, 2026
45733f9
:memo: Document `users_to_csv`
Paillat-dev Feb 21, 2026
87834f5
:recycle: Adjust `__repr__` in `InviteTargetUsersJobStatus` to improv…
Paillat-dev Feb 21, 2026
02cfb10
:memo: Add note on using `users_to_csv` in `create_invite` documentation
Paillat-dev Feb 21, 2026
6b1836c
:recycle: Move get job status and edit target users to `Invite`
Paillat-dev Feb 21, 2026
e1ab52c
:recycle: Update example accordingly, rename
Paillat-dev Feb 21, 2026
7ac070d
Update CHANGELOG.md
Paillat-dev Feb 22, 2026
9cbebc9
Update examples/community_invites.py
Paillat-dev Feb 22, 2026
e5c0a5e
Merge branch 'master' into feat/community-invites-api
Lulalaby Feb 28, 2026
86ec8e3
docs: :memo: clarify `users_to_csv` usage examples in docstring
Paillat-dev Feb 28, 2026
08ab8eb
Merge branch 'master' into feat/community-invites-api
Paillat-dev Feb 28, 2026
9cc6ee2
Merge branch 'master' into feat/community-invites-api
Soheab Mar 2, 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ These changes are available on the `master` branch, but have not yet been releas

### Added

- Added support for community invites.
([#3044](https://github.com/Pycord-Development/pycord/pull/3044))
- Added `Member.colours` and `Member.colors` properties.
([#3063](https://github.com/Pycord-Development/pycord/pull/3063))
- Added `RadioGroup`, `CheckboxGroup`, and `Checkbox` for modals.
Expand Down
20 changes: 20 additions & 0 deletions discord/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from .invite import Invite
from .iterators import HistoryIterator, MessagePinIterator
from .mentions import AllowedMentions
from .object import Object
from .partial_emoji import PartialEmoji, _EmojiTag
from .permissions import PermissionOverwrite, Permissions
from .role import Role
Expand Down Expand Up @@ -1208,6 +1209,8 @@ async def create_invite(
target_type: InviteTarget | None = None,
target_user: User | None = None,
target_application_id: int | None = None,
roles: list[Role | Object] | None = None,
target_users_file: File | None = None,
) -> Invite:
"""|coro|

Expand Down Expand Up @@ -1259,6 +1262,20 @@ async def create_invite(

.. versionadded:: 2.0

roles: Optional[List[Union[:class:`.Role`, :class:`.Object`]]]
Comment thread
Paillat-dev marked this conversation as resolved.
The roles to give a user when joining through this invite.

You must have the :attr:`~Permissions.manage_roles` permission to do this and roles cannot be higher than your own.

.. versionadded:: 2.8

target_users_file: Optional[:class:`File`]
A CSV file with a single column of user IDs for all the users able to accept this invite.

You can use :func:`utils.users_to_csv` to generate a virtual CSV file from a sequence of user IDs.

.. versionadded:: 2.8

Comment thread
plun1331 marked this conversation as resolved.
Returns
-------
:class:`~discord.Invite`
Expand All @@ -1283,8 +1300,11 @@ async def create_invite(
target_type=target_type.value if target_type else None,
target_user_id=target_user.id if target_user else None,
target_application_id=target_application_id,
roles=[str(r.id) for r in roles] if roles else None,
target_users_file=target_users_file,
Comment thread
plun1331 marked this conversation as resolved.
)
invite = Invite.from_incomplete(data=data, state=self._state)

if target_event:
invite.set_scheduled_event(target_event)
return invite
Expand Down
8 changes: 8 additions & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"SubscriptionStatus",
"SeparatorSpacingSize",
"SelectDefaultValueType",
"InviteTargetUsersJobStatusCode",
)


Expand Down Expand Up @@ -1133,6 +1134,13 @@ class SelectDefaultValueType(Enum):
user = "user"


class InviteTargetUsersJobStatusCode(Enum):
unspecified = 0
processing = 1
completed = 2
failed = 3


T = TypeVar("T")


Expand Down
85 changes: 76 additions & 9 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@
welcome_screen,
widget,
)
from .types.invite import (
InviteTargetUsersJobStatus as InviteTargetUsersJobStatusPayload,
)
from .types.snowflake import Snowflake, SnowflakeList
from .types.soundboard import SoundboardSound as SoundboardSoundPayload

Expand All @@ -103,16 +106,19 @@
API_VERSION: int = 10


async def json_or_text(response: aiohttp.ClientResponse) -> dict[str, Any] | str:
text = await response.text(encoding="utf-8")
async def parse_response(
response: aiohttp.ClientResponse,
) -> dict[str, Any] | str | bytes:
try:
if response.headers["content-type"] == "application/json":
return utils._from_json(text)
return utils._from_json(await response.text(encoding="utf-8"))
elif response.headers["content-type"] == "text/csv":
return await response.read()
except KeyError:
# Thanks Cloudflare
pass

return text
return await response.text(encoding="utf-8")


class Route:
Expand Down Expand Up @@ -281,7 +287,7 @@ async def request(
await self._global_over.wait()

response: aiohttp.ClientResponse | None = None
data: dict[str, Any] | str | None = None
data: dict[str, Any] | str | bytes | None = None
await lock.acquire()
with MaybeUnlock(lock) as maybe_lock:
for tries in range(5):
Expand All @@ -308,7 +314,7 @@ async def request(
)

# even errors have text involved in them so this is safe to call
data = await json_or_text(response)
data = await parse_response(response)

# check if we have rate limit header information
remaining = response.headers.get("X-Ratelimit-Remaining")
Expand Down Expand Up @@ -2046,9 +2052,10 @@ def create_invite(
target_type: invite.InviteTargetType | None = None,
target_user_id: Snowflake | None = None,
target_application_id: Snowflake | None = None,
roles: list[Snowflake] | None = None,
target_users_file: File | None = None,
) -> Response[invite.Invite]:
r = Route("POST", "/channels/{channel_id}/invites", channel_id=channel_id)
payload = {
payload: dict[str, Any] = {
"max_age": max_age,
"max_uses": max_uses,
"temporary": temporary,
Expand All @@ -2064,7 +2071,27 @@ def create_invite(
if target_application_id:
payload["target_application_id"] = str(target_application_id)

return self.request(r, reason=reason, json=payload)
if roles:
payload["role_ids"] = roles

route = Route("POST", "/channels/{channel_id}/invites", channel_id=channel_id)

if target_users_file is not None:
form = [
{
"name": "target_users_file",
"value": target_users_file.fp,
"filename": target_users_file.filename,
"content_type": "text/csv",
},
{
"name": "payload_json",
"value": utils._to_json(payload),
},
]
return self.request(route, reason=reason, form=form)
Comment thread
plun1331 marked this conversation as resolved.

return self.request(route, reason=reason, json=payload)

def get_invite(
self,
Expand All @@ -2086,6 +2113,46 @@ def get_invite(
Route("GET", "/invites/{invite_id}", invite_id=invite_id), params=params
)

def get_invite_target_users(
self,
invite_id: str,
) -> Response[bytes]:
return self.request(
Route("GET", "/invites/{invite_id}/target-users", invite_id=invite_id)
)

def update_invite_target_users(
self,
invite_id: str,
*,
target_users_file: File,
) -> Response[invite.Invite]:
form = [
{
"name": "target_users_file",
"value": target_users_file.fp,
"filename": target_users_file.filename,
"content_type": "text/csv",
},
]

return self.request(
Route("PUT", "/invites/{invite_id}/target-users", invite_id=invite_id),
form=form,
)

def get_invite_target_users_job_status(
self,
invite_id: str,
) -> Response[InviteTargetUsersJobStatusPayload]:
return self.request(
Route(
"GET",
"/invites/{invite_id}/target-users/job-status",
invite_id=invite_id,
),
)

def invites_from(self, guild_id: Snowflake) -> Response[list[invite.Invite]]:
return self.request(
Route("GET", "/guilds/{guild_id}/invites", guild_id=guild_id)
Expand Down
Loading