Browse Source

Implement Guild Scheduled Events

pull/7492/head
Nadir Chowdhury 3 years ago
committed by GitHub
parent
commit
3c6279b947
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      discord/__init__.py
  2. 9
      discord/asset.py
  3. 4
      discord/audit_logs.py
  4. 19
      discord/channel.py
  5. 37
      discord/client.py
  6. 120
      discord/enums.py
  7. 16
      discord/flags.py
  8. 218
      discord/guild.py
  9. 59
      discord/http.py
  10. 51
      discord/invite.py
  11. 549
      discord/scheduled_event.py
  12. 15
      discord/stage_instance.py
  13. 81
      discord/state.py
  14. 30
      discord/types/audit_log.py
  15. 2
      discord/types/channel.py
  16. 14
      discord/types/gateway.py
  17. 8
      discord/types/guild.py
  18. 3
      discord/types/invite.py
  19. 15
      discord/types/scheduled_event.py
  20. 31
      discord/utils.py
  21. 4
      discord/widget.py
  22. 137
      docs/api.rst

1
discord/__init__.py

@ -56,6 +56,7 @@ from .raw_models import *
from .team import * from .team import *
from .sticker import * from .sticker import *
from .stage_instance import * from .stage_instance import *
from .scheduled_event import *
from .interactions import * from .interactions import *
from .components import * from .components import *
from .threads import * from .threads import *

9
discord/asset.py

@ -208,6 +208,15 @@ class Asset(AssetMixin):
animated=False, animated=False,
) )
@classmethod
def _from_scheduled_event_cover_image(cls, state, scheduled_event_id: int, cover_image_hash: str) -> Asset:
return cls(
state,
url=f'{cls.BASE}/guild-events/{scheduled_event_id}/{cover_image_hash}.png?size=1024',
key=cover_image_hash,
animated=False,
)
@classmethod @classmethod
def _from_guild_image(cls, state, guild_id: int, image: str, path: str) -> Asset: def _from_guild_image(cls, state, guild_id: int, image: str, path: str) -> Asset:
animated = image.startswith('a_') animated = image.startswith('a_')

4
discord/audit_logs.py

@ -229,12 +229,14 @@ class AuditLogChanges:
'tags': ('emoji', None), 'tags': ('emoji', None),
'default_message_notifications': ('default_notifications', _enum_transformer(enums.NotificationLevel)), 'default_message_notifications': ('default_notifications', _enum_transformer(enums.NotificationLevel)),
'video_quality_mode': (None, _enum_transformer(enums.VideoQualityMode)), 'video_quality_mode': (None, _enum_transformer(enums.VideoQualityMode)),
'privacy_level': (None, _enum_transformer(enums.StagePrivacyLevel)), 'privacy_level': (None, _enum_transformer(enums.PrivacyLevel)),
'format_type': (None, _enum_transformer(enums.StickerFormatType)), 'format_type': (None, _enum_transformer(enums.StickerFormatType)),
'type': (None, _transform_type), 'type': (None, _transform_type),
'communication_disabled_until': ('timed_out_until', _transform_timestamp), 'communication_disabled_until': ('timed_out_until', _transform_timestamp),
'expire_behavior': (None, _enum_transformer(enums.ExpireBehaviour)), 'expire_behavior': (None, _enum_transformer(enums.ExpireBehaviour)),
'mfa_level': (None, _enum_transformer(enums.MFALevel)), 'mfa_level': (None, _enum_transformer(enums.MFALevel)),
'status': (None, _enum_transformer(enums.EventStatus)),
'entity_type': (None, _enum_transformer(enums.EntityType)),
} }
# fmt: on # fmt: on

19
discord/channel.py

@ -45,8 +45,9 @@ from typing import (
import datetime import datetime
import discord.abc import discord.abc
from .scheduled_event import ScheduledEvent
from .permissions import PermissionOverwrite, Permissions from .permissions import PermissionOverwrite, Permissions
from .enums import ChannelType, StagePrivacyLevel, try_enum, VideoQualityMode from .enums import ChannelType, PrivacyLevel, try_enum, VideoQualityMode
from .mixins import Hashable from .mixins import Hashable
from .object import Object from .object import Object
from . import utils from . import utils
@ -950,6 +951,14 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha
} }
# fmt: on # fmt: on
@property
def scheduled_events(self) -> List[ScheduledEvent]:
"""List[:class:`ScheduledEvent`]: Returns all scheduled events for this channel.
.. versionadded:: 2.0
"""
return [event for event in self.guild.scheduled_events if event.channel_id == self.id]
@utils.copy_doc(discord.abc.GuildChannel.permissions_for) @utils.copy_doc(discord.abc.GuildChannel.permissions_for)
def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: def permissions_for(self, obj: Union[Member, Role], /) -> Permissions:
base = super().permissions_for(obj) base = super().permissions_for(obj)
@ -1259,7 +1268,7 @@ class StageChannel(VocalGuildChannel):
return utils.get(self.guild.stage_instances, channel_id=self.id) return utils.get(self.guild.stage_instances, channel_id=self.id)
async def create_instance( async def create_instance(
self, *, topic: str, privacy_level: StagePrivacyLevel = MISSING, reason: Optional[str] = None self, *, topic: str, privacy_level: PrivacyLevel = MISSING, reason: Optional[str] = None
) -> StageInstance: ) -> StageInstance:
"""|coro| """|coro|
@ -1274,8 +1283,8 @@ class StageChannel(VocalGuildChannel):
----------- -----------
topic: :class:`str` topic: :class:`str`
The stage instance's topic. The stage instance's topic.
privacy_level: :class:`StagePrivacyLevel` privacy_level: :class:`PrivacyLevel`
The stage instance's privacy level. Defaults to :attr:`StagePrivacyLevel.guild_only`. The stage instance's privacy level. Defaults to :attr:`PrivacyLevel.guild_only`.
reason: :class:`str` reason: :class:`str`
The reason the stage instance was created. Shows up on the audit log. The reason the stage instance was created. Shows up on the audit log.
@ -1297,7 +1306,7 @@ class StageChannel(VocalGuildChannel):
payload: Dict[str, Any] = {'channel_id': self.id, 'topic': topic} payload: Dict[str, Any] = {'channel_id': self.id, 'topic': topic}
if privacy_level is not MISSING: if privacy_level is not MISSING:
if not isinstance(privacy_level, StagePrivacyLevel): if not isinstance(privacy_level, PrivacyLevel):
raise TypeError('privacy_level field must be of type PrivacyLevel') raise TypeError('privacy_level field must be of type PrivacyLevel')
payload['privacy_level'] = privacy_level.value payload['privacy_level'] = privacy_level.value

37
discord/client.py

@ -1437,7 +1437,12 @@ class Client:
# Invite management # Invite management
async def fetch_invite( async def fetch_invite(
self, url: Union[Invite, str], *, with_counts: bool = True, with_expiration: bool = True self,
url: Union[Invite, str],
*,
with_counts: bool = True,
with_expiration: bool = True,
scheduled_event_id: Optional[int] = None,
) -> Invite: ) -> Invite:
"""|coro| """|coro|
@ -1462,9 +1467,20 @@ class Client:
:attr:`.Invite.expires_at` field. :attr:`.Invite.expires_at` field.
.. versionadded:: 2.0 .. versionadded:: 2.0
scheduled_event_id: Optional[:class:`int`]
The ID of the scheduled event this invite is for.
.. note::
It is not possible to provide a url that contains an ``event_id`` parameter
when using this parameter.
.. versionadded:: 2.0
Raises Raises
------- -------
ValueError
The url contains an ``event_id``, but ``scheduled_event_id`` has also been provided.
NotFound NotFound
The invite has expired or is invalid. The invite has expired or is invalid.
HTTPException HTTPException
@ -1476,8 +1492,19 @@ class Client:
The invite from the URL/ID. The invite from the URL/ID.
""" """
invite_id = utils.resolve_invite(url) resolved = utils.resolve_invite(url)
data = await self.http.get_invite(invite_id, with_counts=with_counts, with_expiration=with_expiration)
if scheduled_event_id and resolved.event:
raise ValueError('Cannot specify scheduled_event_id and contain an event_id in the url.')
scheduled_event_id = scheduled_event_id or resolved.event
data = await self.http.get_invite(
resolved.code,
with_counts=with_counts,
with_expiration=with_expiration,
guild_scheduled_event_id=scheduled_event_id,
)
return Invite.from_incomplete(state=self._connection, data=data) return Invite.from_incomplete(state=self._connection, data=data)
async def delete_invite(self, invite: Union[Invite, str], /) -> None: async def delete_invite(self, invite: Union[Invite, str], /) -> None:
@ -1507,8 +1534,8 @@ class Client:
Revoking the invite failed. Revoking the invite failed.
""" """
invite_id = utils.resolve_invite(invite) resolved = utils.resolve_invite(invite)
await self.http.delete_invite(invite_id) await self.http.delete_invite(resolved.code)
# Miscellaneous stuff # Miscellaneous stuff

120
discord/enums.py

@ -51,12 +51,14 @@ __all__ = (
'ComponentType', 'ComponentType',
'ButtonStyle', 'ButtonStyle',
'TextStyle', 'TextStyle',
'StagePrivacyLevel', 'PrivacyLevel',
'InteractionType', 'InteractionType',
'InteractionResponseType', 'InteractionResponseType',
'NSFWLevel', 'NSFWLevel',
'MFALevel', 'MFALevel',
'Locale', 'Locale',
'EntityType',
'EventStatus',
) )
@ -330,6 +332,9 @@ class AuditLogAction(Enum):
sticker_create = 90 sticker_create = 90
sticker_update = 91 sticker_update = 91
sticker_delete = 92 sticker_delete = 92
scheduled_event_create = 100
scheduled_event_update = 101
scheduled_event_delete = 102
thread_create = 110 thread_create = 110
thread_update = 111 thread_update = 111
thread_delete = 112 thread_delete = 112
@ -339,50 +344,53 @@ class AuditLogAction(Enum):
def category(self) -> Optional[AuditLogActionCategory]: def category(self) -> Optional[AuditLogActionCategory]:
# fmt: off # fmt: off
lookup: Dict[AuditLogAction, Optional[AuditLogActionCategory]] = { lookup: Dict[AuditLogAction, Optional[AuditLogActionCategory]] = {
AuditLogAction.guild_update: AuditLogActionCategory.update, AuditLogAction.guild_update: AuditLogActionCategory.update,
AuditLogAction.channel_create: AuditLogActionCategory.create, AuditLogAction.channel_create: AuditLogActionCategory.create,
AuditLogAction.channel_update: AuditLogActionCategory.update, AuditLogAction.channel_update: AuditLogActionCategory.update,
AuditLogAction.channel_delete: AuditLogActionCategory.delete, AuditLogAction.channel_delete: AuditLogActionCategory.delete,
AuditLogAction.overwrite_create: AuditLogActionCategory.create, AuditLogAction.overwrite_create: AuditLogActionCategory.create,
AuditLogAction.overwrite_update: AuditLogActionCategory.update, AuditLogAction.overwrite_update: AuditLogActionCategory.update,
AuditLogAction.overwrite_delete: AuditLogActionCategory.delete, AuditLogAction.overwrite_delete: AuditLogActionCategory.delete,
AuditLogAction.kick: None, AuditLogAction.kick: None,
AuditLogAction.member_prune: None, AuditLogAction.member_prune: None,
AuditLogAction.ban: None, AuditLogAction.ban: None,
AuditLogAction.unban: None, AuditLogAction.unban: None,
AuditLogAction.member_update: AuditLogActionCategory.update, AuditLogAction.member_update: AuditLogActionCategory.update,
AuditLogAction.member_role_update: AuditLogActionCategory.update, AuditLogAction.member_role_update: AuditLogActionCategory.update,
AuditLogAction.member_move: None, AuditLogAction.member_move: None,
AuditLogAction.member_disconnect: None, AuditLogAction.member_disconnect: None,
AuditLogAction.bot_add: None, AuditLogAction.bot_add: None,
AuditLogAction.role_create: AuditLogActionCategory.create, AuditLogAction.role_create: AuditLogActionCategory.create,
AuditLogAction.role_update: AuditLogActionCategory.update, AuditLogAction.role_update: AuditLogActionCategory.update,
AuditLogAction.role_delete: AuditLogActionCategory.delete, AuditLogAction.role_delete: AuditLogActionCategory.delete,
AuditLogAction.invite_create: AuditLogActionCategory.create, AuditLogAction.invite_create: AuditLogActionCategory.create,
AuditLogAction.invite_update: AuditLogActionCategory.update, AuditLogAction.invite_update: AuditLogActionCategory.update,
AuditLogAction.invite_delete: AuditLogActionCategory.delete, AuditLogAction.invite_delete: AuditLogActionCategory.delete,
AuditLogAction.webhook_create: AuditLogActionCategory.create, AuditLogAction.webhook_create: AuditLogActionCategory.create,
AuditLogAction.webhook_update: AuditLogActionCategory.update, AuditLogAction.webhook_update: AuditLogActionCategory.update,
AuditLogAction.webhook_delete: AuditLogActionCategory.delete, AuditLogAction.webhook_delete: AuditLogActionCategory.delete,
AuditLogAction.emoji_create: AuditLogActionCategory.create, AuditLogAction.emoji_create: AuditLogActionCategory.create,
AuditLogAction.emoji_update: AuditLogActionCategory.update, AuditLogAction.emoji_update: AuditLogActionCategory.update,
AuditLogAction.emoji_delete: AuditLogActionCategory.delete, AuditLogAction.emoji_delete: AuditLogActionCategory.delete,
AuditLogAction.message_delete: AuditLogActionCategory.delete, AuditLogAction.message_delete: AuditLogActionCategory.delete,
AuditLogAction.message_bulk_delete: AuditLogActionCategory.delete, AuditLogAction.message_bulk_delete: AuditLogActionCategory.delete,
AuditLogAction.message_pin: None, AuditLogAction.message_pin: None,
AuditLogAction.message_unpin: None, AuditLogAction.message_unpin: None,
AuditLogAction.integration_create: AuditLogActionCategory.create, AuditLogAction.integration_create: AuditLogActionCategory.create,
AuditLogAction.integration_update: AuditLogActionCategory.update, AuditLogAction.integration_update: AuditLogActionCategory.update,
AuditLogAction.integration_delete: AuditLogActionCategory.delete, AuditLogAction.integration_delete: AuditLogActionCategory.delete,
AuditLogAction.stage_instance_create: AuditLogActionCategory.create, AuditLogAction.stage_instance_create: AuditLogActionCategory.create,
AuditLogAction.stage_instance_update: AuditLogActionCategory.update, AuditLogAction.stage_instance_update: AuditLogActionCategory.update,
AuditLogAction.stage_instance_delete: AuditLogActionCategory.delete, AuditLogAction.stage_instance_delete: AuditLogActionCategory.delete,
AuditLogAction.sticker_create: AuditLogActionCategory.create, AuditLogAction.sticker_create: AuditLogActionCategory.create,
AuditLogAction.sticker_update: AuditLogActionCategory.update, AuditLogAction.sticker_update: AuditLogActionCategory.update,
AuditLogAction.sticker_delete: AuditLogActionCategory.delete, AuditLogAction.sticker_delete: AuditLogActionCategory.delete,
AuditLogAction.thread_create: AuditLogActionCategory.create, AuditLogAction.scheduled_event_create: AuditLogActionCategory.create,
AuditLogAction.thread_update: AuditLogActionCategory.update, AuditLogAction.scheduled_event_update: AuditLogActionCategory.update,
AuditLogAction.thread_delete: AuditLogActionCategory.delete, AuditLogAction.scheduled_event_delete: AuditLogActionCategory.delete,
AuditLogAction.thread_create: AuditLogActionCategory.create,
AuditLogAction.thread_update: AuditLogActionCategory.update,
AuditLogAction.thread_delete: AuditLogActionCategory.delete,
} }
# fmt: on # fmt: on
return lookup[self] return lookup[self]
@ -416,6 +424,8 @@ class AuditLogAction(Enum):
return 'stage_instance' return 'stage_instance'
elif v < 93: elif v < 93:
return 'sticker' return 'sticker'
elif v < 103:
return 'guild_scheduled_event'
elif v < 113: elif v < 113:
return 'thread' return 'thread'
@ -568,9 +578,7 @@ class TextStyle(Enum):
return self.value return self.value
class StagePrivacyLevel(Enum): class PrivacyLevel(Enum):
public = 1
closed = 2
guild_only = 2 guild_only = 2
@ -625,6 +633,22 @@ class Locale(Enum):
E = TypeVar('E', bound='Enum') E = TypeVar('E', bound='Enum')
class EntityType(Enum):
stage_instance = 1
voice = 2
external = 3
class EventStatus(Enum):
scheduled = 1
active = 2
completed = 3
canceled = 4
ended = 3
cancelled = 4
def create_unknown_value(cls: Type[E], val: Any) -> E: def create_unknown_value(cls: Type[E], val: Any) -> E:
value_cls = cls._enum_value_cls_ # type: ignore - This is narrowed below value_cls = cls._enum_value_cls_ # type: ignore - This is narrowed below
name = f'unknown_{val}' name = f'unknown_{val}'

16
discord/flags.py

@ -938,6 +938,22 @@ class Intents(BaseFlags):
""" """
return 1 << 15 return 1 << 15
@flag_value
def guild_scheduled_events(self):
""":class:`bool`: Whether guild scheduled event related events are enabled.
This corresponds to the following events:
- :func:`on_scheduled_event_create`
- :func:`on_scheduled_event_update`
- :func:`on_scheduled_event_delete`
- :func:`on_scheduled_event_user_add`
- :func:`on_scheduled_event_user_remove`
.. versionadded:: 2.0
"""
return 1 << 16
@fill_with_flags() @fill_with_flags()
class MemberCacheFlags(BaseFlags): class MemberCacheFlags(BaseFlags):

218
discord/guild.py

@ -60,6 +60,8 @@ from .enums import (
AuditLogAction, AuditLogAction,
VideoQualityMode, VideoQualityMode,
ChannelType, ChannelType,
EntityType,
PrivacyLevel,
try_enum, try_enum,
VerificationLevel, VerificationLevel,
ContentFilter, ContentFilter,
@ -74,6 +76,7 @@ from .widget import Widget
from .asset import Asset from .asset import Asset
from .flags import SystemChannelFlags from .flags import SystemChannelFlags
from .integrations import Integration, _integration_factory from .integrations import Integration, _integration_factory
from .scheduled_event import ScheduledEvent
from .stage_instance import StageInstance from .stage_instance import StageInstance
from .threads import Thread, ThreadMember from .threads import Thread, ThreadMember
from .sticker import GuildSticker from .sticker import GuildSticker
@ -293,6 +296,7 @@ class Guild(Hashable):
'_rules_channel_id', '_rules_channel_id',
'_public_updates_channel_id', '_public_updates_channel_id',
'_stage_instances', '_stage_instances',
'_scheduled_events',
'_threads', '_threads',
) )
@ -466,6 +470,11 @@ class Guild(Hashable):
stage_instance = StageInstance(guild=self, data=s, state=state) stage_instance = StageInstance(guild=self, data=s, state=state)
self._stage_instances[stage_instance.id] = stage_instance self._stage_instances[stage_instance.id] = stage_instance
self._scheduled_events: Dict[int, ScheduledEvent] = {}
for s in guild.get('guild_scheduled_events', []):
scheduled_event = ScheduledEvent(data=s, state=state)
self._scheduled_events[scheduled_event.id] = scheduled_event
cache_joined = self._state.member_cache_flags.joined cache_joined = self._state.member_cache_flags.joined
self_id = self._state.self_id self_id = self._state.self_id
for mdata in guild.get('members', []): for mdata in guild.get('members', []):
@ -867,6 +876,31 @@ class Guild(Hashable):
""" """
return self._stage_instances.get(stage_instance_id) return self._stage_instances.get(stage_instance_id)
@property
def scheduled_events(self) -> List[ScheduledEvent]:
"""List[:class:`ScheduledEvent`]: Returns a :class:`list` of the guild's scheduled events.
.. versionadded:: 2.0
"""
return list(self._scheduled_events.values())
def get_scheduled_event(self, scheduled_event_id: int, /) -> Optional[ScheduledEvent]:
"""Returns a scheduled event with the given ID.
.. versionadded:: 2.0
Parameters
-----------
scheduled_event_id: :class:`int`
The ID to search for.
Returns
--------
Optional[:class:`ScheduledEvent`]
The scheduled event or ``None`` if not found.
"""
return self._scheduled_events.get(scheduled_event_id)
@property @property
def owner(self) -> Optional[Member]: def owner(self) -> Optional[Member]:
"""Optional[:class:`Member`]: The member that owns the guild.""" """Optional[:class:`Member`]: The member that owns the guild."""
@ -2413,6 +2447,190 @@ class Guild(Hashable):
""" """
await self._state.http.delete_guild_sticker(self.id, sticker.id, reason) await self._state.http.delete_guild_sticker(self.id, sticker.id, reason)
async def fetch_scheduled_events(self, *, with_counts: bool = True) -> List[ScheduledEvent]:
"""|coro|
Retrieves a list of all scheduled events for the guild.
.. versionadded:: 2.0
Parameters
------------
with_counts: :class:`bool`
Whether to include the number of users that are subscribed to the event.
Defaults to ``True``.
Raises
-------
HTTPException
Retrieving the scheduled events failed.
Returns
--------
List[:class:`ScheduledEvent`]
The scheduled events.
"""
data = await self._state.http.get_scheduled_events(self.id, with_counts)
return [ScheduledEvent(state=self._state, data=d) for d in data]
async def fetch_scheduled_event(self, scheduled_event_id: int, /, *, with_counts: bool = True) -> ScheduledEvent:
"""|coro|
Retrieves a scheduled event from the guild.
.. versionadded:: 2.0
Parameters
------------
id: :class:`int`
The scheduled event ID.
with_counts: :class:`bool`
Whether to include the number of users that are subscribed to the event.
Defaults to ``True``.
Raises
-------
NotFound
The scheduled event was not found.
HTTPException
Retrieving the scheduled event failed.
Returns
--------
:class:`ScheduledEvent`
The scheduled event.
"""
data = await self._state.http.get_scheduled_event(self.id, scheduled_event_id, with_counts)
return ScheduledEvent(state=self._state, data=data)
async def create_scheduled_event(
self,
*,
name: str,
start_time: datetime.datetime,
entity_type: EntityType,
privacy_level: PrivacyLevel = MISSING,
channel: Optional[Snowflake] = MISSING,
location: str = MISSING,
end_time: datetime.datetime = MISSING,
description: str = MISSING,
image: bytes = MISSING,
reason: Optional[str] = None,
) -> ScheduledEvent:
r"""|coro|
Creates a scheduled event for the guild.
Requires :attr:`~Permissions.manage_events` permissions.
.. versionadded:: 2.0
Parameters
------------
name: :class:`str`
The name of the scheduled event.
description: :class:`str`
The description of the scheduled event.
channel: Optional[:class:`abc.Snowflake`]
The channel to send the scheduled event to.
Required if ``entity_type`` is either :attr:`EntityType.voice` or
:attr:`EntityType.stage_instance`.
start_time: :class:`datetime.datetime`
The scheduled start time of the scheduled event. This must be a timezone-aware
datetime object. Consider using :func:`utils.utcnow`.
end_time: :class:`datetime.datetime`
The scheduled end time of the scheduled event. This must be a timezone-aware
datetime object. Consider using :func:`utils.utcnow`.
Required if the entity type is :attr:`EntityType.external`.
entity_type: :class:`EntityType`
The entity type of the scheduled event.
image: :class:`bytes`
The image of the scheduled event.
location: :class:`str`
The location of the scheduled event.
Required if the ``entity_type`` is :attr:`EntityType.external`.
reason: Optional[:class:`str`]
The reason for creating this scheduled event. Shows up on the audit log.
Raises
-------
TypeError
`image` was not a :term:`py:bytes-like object`, or ``privacy_level``
was not a :class:`PrivacyLevel`, or ``entity_type`` was not an
:class:`EntityType`, ``status`` was not an :class:`EventStatus`,
or an argument was provided that was incompatible with the provided
``entity_type``.
ValueError
``start_time`` or ``end_time`` was not a timezone-aware datetime object.
Forbidden
You are not allowed to create scheduled events.
HTTPException
Creating the scheduled event failed.
Returns
--------
:class:`ScheduledEvent`
The created scheduled event.
"""
payload = {}
metadata = {}
payload['name'] = name
if start_time is not MISSING:
if start_time.tzinfo is None:
raise ValueError(
'start_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.'
)
payload['scheduled_start_time'] = start_time.isoformat()
if not isinstance(entity_type, EntityType):
raise TypeError('entity_type must be of type EntityType')
payload['entity_type'] = entity_type.value
payload['privacy_level'] = PrivacyLevel.guild_only.value
if description is not MISSING:
payload['description'] = description
if image is not MISSING:
image_as_str: str = utils._bytes_to_base64_data(image)
payload['image'] = image_as_str
if entity_type in (EntityType.stage_instance, EntityType.voice):
if channel is MISSING or channel is None:
raise TypeError('channel must be set when entity_type is voice or stage_instance')
payload['channel_id'] = channel.id
if location is not MISSING:
raise TypeError('location cannot be set when entity_type is voice or stage_instance')
else:
if channel is not MISSING:
raise TypeError('channel cannot be set when entity_type is external')
if location is MISSING or location is None:
raise TypeError('location must be set when entity_type is external')
metadata['location'] = location
if end_time is not MISSING:
if end_time.tzinfo is None:
raise ValueError(
'end_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.'
)
payload['scheduled_end_time'] = end_time.isoformat()
if metadata:
payload['entity_metadata'] = metadata
data = await self._state.http.create_guild_scheduled_event(self.id, **payload, reason=reason)
return ScheduledEvent(state=self._state, data=data)
async def fetch_emojis(self) -> List[Emoji]: async def fetch_emojis(self) -> List[Emoji]:
r"""|coro| r"""|coro|

59
discord/http.py

@ -1426,12 +1426,21 @@ class HTTPClient:
return self.request(r, reason=reason, json=payload) return self.request(r, reason=reason, json=payload)
def get_invite( def get_invite(
self, invite_id: str, *, with_counts: bool = True, with_expiration: bool = True self,
invite_id: str,
*,
with_counts: bool = True,
with_expiration: bool = True,
guild_scheduled_event_id: Optional[Snowflake] = None,
) -> Response[invite.Invite]: ) -> Response[invite.Invite]:
params = { params: Dict[str, Any] = {
'with_counts': int(with_counts), 'with_counts': int(with_counts),
'with_expiration': int(with_expiration), 'with_expiration': int(with_expiration),
} }
if guild_scheduled_event_id:
params['guild_scheduled_event_id'] = guild_scheduled_event_id
return self.request(Route('GET', '/invites/{invite_id}', invite_id=invite_id), params=params) return self.request(Route('GET', '/invites/{invite_id}', invite_id=invite_id), params=params)
def invites_from(self, guild_id: Snowflake) -> Response[List[invite.Invite]]: def invites_from(self, guild_id: Snowflake) -> Response[List[invite.Invite]]:
@ -1583,8 +1592,16 @@ class HTTPClient:
) -> Response[List[scheduled_event.GuildScheduledEvent]]: ) -> Response[List[scheduled_event.GuildScheduledEvent]]:
... ...
@overload
def get_scheduled_events(
self, guild_id: Snowflake, with_user_count: bool
) -> Union[
Response[List[scheduled_event.GuildScheduledEventWithUserCount]], Response[List[scheduled_event.GuildScheduledEvent]]
]:
...
def get_scheduled_events(self, guild_id: Snowflake, with_user_count: bool) -> Response[Any]: def get_scheduled_events(self, guild_id: Snowflake, with_user_count: bool) -> Response[Any]:
params = {'with_user_count': with_user_count} params = {'with_user_count': int(with_user_count)}
return self.request(Route('GET', '/guilds/{guild_id}/scheduled-events', guild_id=guild_id), params=params) return self.request(Route('GET', '/guilds/{guild_id}/scheduled-events', guild_id=guild_id), params=params)
def create_guild_scheduled_event( def create_guild_scheduled_event(
@ -1619,10 +1636,16 @@ class HTTPClient:
) -> Response[scheduled_event.GuildScheduledEvent]: ) -> Response[scheduled_event.GuildScheduledEvent]:
... ...
@overload
def get_scheduled_event(
self, guild_id: Snowflake, guild_scheduled_event_id: Snowflake, with_user_count: bool
) -> Union[Response[scheduled_event.GuildScheduledEventWithUserCount], Response[scheduled_event.GuildScheduledEvent]]:
...
def get_scheduled_event( def get_scheduled_event(
self, guild_id: Snowflake, guild_scheduled_event_id: Snowflake, with_user_count: bool self, guild_id: Snowflake, guild_scheduled_event_id: Snowflake, with_user_count: bool
) -> Response[Any]: ) -> Response[Any]:
params = {'with_user_count': with_user_count} params = {'with_user_count': int(with_user_count)}
return self.request( return self.request(
Route( Route(
'GET', 'GET',
@ -1643,6 +1666,7 @@ class HTTPClient:
'privacy_level', 'privacy_level',
'scheduled_start_time', 'scheduled_start_time',
'scheduled_end_time', 'scheduled_end_time',
'status',
'description', 'description',
'entity_type', 'entity_type',
'image', 'image',
@ -1664,6 +1688,8 @@ class HTTPClient:
self, self,
guild_id: Snowflake, guild_id: Snowflake,
guild_scheduled_event_id: Snowflake, guild_scheduled_event_id: Snowflake,
*,
reason: Optional[str] = None,
) -> Response[None]: ) -> Response[None]:
return self.request( return self.request(
Route( Route(
@ -1672,10 +1698,11 @@ class HTTPClient:
guild_id=guild_id, guild_id=guild_id,
guild_scheduled_event_id=guild_scheduled_event_id, guild_scheduled_event_id=guild_scheduled_event_id,
), ),
reason=reason,
) )
@overload @overload
def get_scheduled_users( def get_scheduled_event_users(
self, self,
guild_id: Snowflake, guild_id: Snowflake,
guild_scheduled_event_id: Snowflake, guild_scheduled_event_id: Snowflake,
@ -1683,11 +1710,11 @@ class HTTPClient:
with_member: Literal[True], with_member: Literal[True],
before: Optional[Snowflake] = ..., before: Optional[Snowflake] = ...,
after: Optional[Snowflake] = ..., after: Optional[Snowflake] = ...,
) -> Response[scheduled_event.ScheduledEventUserWithMember]: ) -> Response[scheduled_event.ScheduledEventUsersWithMember]:
... ...
@overload @overload
def get_scheduled_users( def get_scheduled_event_users(
self, self,
guild_id: Snowflake, guild_id: Snowflake,
guild_scheduled_event_id: Snowflake, guild_scheduled_event_id: Snowflake,
@ -1695,10 +1722,22 @@ class HTTPClient:
with_member: Literal[False], with_member: Literal[False],
before: Optional[Snowflake] = ..., before: Optional[Snowflake] = ...,
after: Optional[Snowflake] = ..., after: Optional[Snowflake] = ...,
) -> Response[scheduled_event.ScheduledEventUser]: ) -> Response[scheduled_event.ScheduledEventUsers]:
...
@overload
def get_scheduled_event_users(
self,
guild_id: Snowflake,
guild_scheduled_event_id: Snowflake,
limit: int,
with_member: bool,
before: Optional[Snowflake] = ...,
after: Optional[Snowflake] = ...,
) -> Union[Response[scheduled_event.ScheduledEventUsersWithMember], Response[scheduled_event.ScheduledEventUsers]]:
... ...
def get_scheduled_users( def get_scheduled_event_users(
self, self,
guild_id: Snowflake, guild_id: Snowflake,
guild_scheduled_event_id: Snowflake, guild_scheduled_event_id: Snowflake,
@ -1709,7 +1748,7 @@ class HTTPClient:
) -> Response[Any]: ) -> Response[Any]:
params: Dict[str, Any] = { params: Dict[str, Any] = {
'limit': limit, 'limit': limit,
'with_member': with_member, 'with_member': int(with_member),
} }
if before is not None: if before is not None:

51
discord/invite.py

@ -31,6 +31,7 @@ from .object import Object
from .mixins import Hashable from .mixins import Hashable
from .enums import ChannelType, VerificationLevel, InviteTarget, try_enum from .enums import ChannelType, VerificationLevel, InviteTarget, try_enum
from .appinfo import PartialAppInfo from .appinfo import PartialAppInfo
from .scheduled_event import ScheduledEvent
__all__ = ( __all__ = (
'PartialInviteChannel', 'PartialInviteChannel',
@ -51,6 +52,7 @@ if TYPE_CHECKING:
from .guild import Guild from .guild import Guild
from .abc import GuildChannel from .abc import GuildChannel
from .user import User from .user import User
from .abc import Snowflake
InviteGuildType = Union[Guild, 'PartialInviteGuild', Object] InviteGuildType = Union[Guild, 'PartialInviteGuild', Object]
InviteChannelType = Union[GuildChannel, 'PartialInviteChannel', Object] InviteChannelType = Union[GuildChannel, 'PartialInviteChannel', Object]
@ -303,6 +305,14 @@ class Invite(Hashable):
target_application: Optional[:class:`PartialAppInfo`] target_application: Optional[:class:`PartialAppInfo`]
The embedded application the invite targets, if any. The embedded application the invite targets, if any.
.. versionadded:: 2.0
scheduled_event: Optional[:class:`ScheduledEvent`]
The scheduled event associated with this invite, if any.
.. versionadded:: 2.0
scheduled_event_id: Optional[:class:`int`]
The ID of the scheduled event associated with this invite, if any.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
@ -324,6 +334,8 @@ class Invite(Hashable):
'approximate_presence_count', 'approximate_presence_count',
'target_application', 'target_application',
'expires_at', 'expires_at',
'scheduled_event',
'scheduled_event_id',
) )
BASE = 'https://discord.gg' BASE = 'https://discord.gg'
@ -366,6 +378,17 @@ class Invite(Hashable):
PartialAppInfo(data=application, state=state) if application else None PartialAppInfo(data=application, state=state) if application else None
) )
scheduled_event = data.get('guild_scheduled_event')
self.scheduled_event: Optional[ScheduledEvent] = (
ScheduledEvent(
state=self._state,
data=scheduled_event,
)
if scheduled_event
else None
)
self.scheduled_event_id: Optional[int] = self.scheduled_event.id if self.scheduled_event else None
@classmethod @classmethod
def from_incomplete(cls: Type[I], *, state: ConnectionState, data: InvitePayload) -> I: def from_incomplete(cls: Type[I], *, state: ConnectionState, data: InvitePayload) -> I:
guild: Optional[Union[Guild, PartialInviteGuild]] guild: Optional[Union[Guild, PartialInviteGuild]]
@ -451,7 +474,33 @@ class Invite(Hashable):
@property @property
def url(self) -> str: def url(self) -> str:
""":class:`str`: A property that retrieves the invite URL.""" """:class:`str`: A property that retrieves the invite URL."""
return self.BASE + '/' + self.code url = self.BASE + '/' + self.code
if self.scheduled_event_id is not None:
url += '?event=' + str(self.scheduled_event_id)
return url
def set_scheduled_event(self: I, scheduled_event: Snowflake, /) -> I:
"""Sets the scheduled event for this invite.
.. versionadded:: 2.0
Parameters
----------
scheduled_event: :class:`~discord.abc.Snowflake`
The ID of the scheduled event.
Returns
--------
:class:`Invite`
The invite with the new scheduled event.
"""
self.scheduled_event_id = scheduled_event.id
try:
self.scheduled_event = self.guild.get_scheduled_event(scheduled_event.id) # type: ignore - handled below
except AttributeError:
self.scheduled_event = None
return self
async def delete(self, *, reason: Optional[str] = None): async def delete(self, *, reason: Optional[str] = None):
"""|coro| """|coro|

549
discord/scheduled_event.py

@ -0,0 +1,549 @@
"""
The MIT License (MIT)
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING, AsyncIterator, Dict, Optional, Union
from .asset import Asset
from .enums import EventStatus, EntityType, PrivacyLevel, try_enum
from .mixins import Hashable
from .object import Object, OLDEST_OBJECT
from .utils import parse_time, _get_as_snowflake, _bytes_to_base64_data, MISSING
if TYPE_CHECKING:
from .types.scheduled_event import (
GuildScheduledEvent as GuildScheduledEventPayload,
GuildScheduledEventWithUserCount as GuildScheduledEventWithUserCountPayload,
EntityMetadata,
)
from .abc import Snowflake
from .channel import VoiceChannel, StageChannel
from .state import ConnectionState
from .user import User
GuildScheduledEventPayload = Union[GuildScheduledEventPayload, GuildScheduledEventWithUserCountPayload]
# fmt: off
__all__ = (
"ScheduledEvent",
)
# fmt: on
class ScheduledEvent(Hashable):
"""Represents a scheduled event in a guild.
.. versionadded:: 2.0
.. container:: operations
.. describe:: x == y
Checks if two scheduled events are equal.
.. describe:: x != y
Checks if two scheduled events are not equal.
.. describe:: hash(x)
Returns the scheduled event's hash.
Attributes
----------
id: :class:`int`
The scheduled event's ID.
name: :class:`str`
The name of the scheduled event.
description: :class:`str`
The description of the scheduled event.
entity_type: :class:`EntityType`
The type of entity this event is for.
entity_id: :class:`int`
The ID of the entity this event is for.
start_time: :class:`datetime.datetime`
The time that the scheduled event will start in UTC.
end_time: :class:`datetime.datetime`
The time that the scheduled event will end in UTC.
privacy_level: :class:`PrivacyLevel`
The privacy level of the scheduled event.
status: :class:`EventStatus`
The status of the scheduled event.
user_count: :class:`int`
The number of users subscribed to the scheduled event.
creator: Optional[:class:`User`]
The user that created the scheduled event.
location: Optional[:class:`str`]
The location of the scheduled event.
"""
__slots__ = (
'_state',
'_users',
'id',
'guild_id',
'name',
'description',
'entity_type',
'entity_id',
'start_time',
'end_time',
'privacy_level',
'status',
'_cover_image',
'user_count',
'creator',
'channel_id',
'location',
)
def __init__(self, *, state: ConnectionState, data: GuildScheduledEventPayload) -> None:
self._state = state
self._users: Dict[int, User] = {}
self._update(data)
def _update(self, data: GuildScheduledEventPayload) -> None:
self.id: int = int(data['id'])
self.guild_id: int = int(data['guild_id'])
self.name: str = data['name']
self.description: str = data.get('description', '')
self.entity_type = try_enum(EntityType, data['entity_type'])
self.entity_id: int = int(data['id'])
self.start_time: datetime = parse_time(data['scheduled_start_time'])
self.privacy_level: PrivacyLevel = try_enum(PrivacyLevel, data['status'])
self.status: EventStatus = try_enum(EventStatus, data['status'])
self._cover_image: Optional[str] = data['image']
self.user_count: int = data.get('user_count', 0)
creator = data.get('creator')
self.creator: Optional[User] = self._state.store_user(creator) if creator else None
self.end_time: Optional[datetime] = parse_time(data.get('scheduled_end_time'))
self.channel_id: Optional[int] = _get_as_snowflake(data, 'channel_id')
metadata = data.get('metadata')
if metadata:
self._unroll_metadata(metadata)
def _unroll_metadata(self, data: EntityMetadata):
self.location: Optional[str] = data.get('location')
@classmethod
def from_creation(cls, *, state: ConnectionState, data: GuildScheduledEventPayload):
creator_id = data.get('creator_id')
self = cls(state=state, data=data)
if creator_id:
self.creator = self._state.get_user(int(creator_id))
def __repr__(self) -> str:
return f'<GuildScheduledEvent id={self.id} name={self.name!r} guild_id={self.guild_id!r} creator={self.creator!r}>'
@property
def cover_image(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: The scheduled event's cover image."""
if self._cover_image is None:
return None
return Asset._from_scheduled_event_cover_image(self._state, self.id, self._cover_image)
@property
def channel(self) -> Optional[Union[VoiceChannel, StageChannel]]:
"""Optional[Union[:class:`VoiceChannel`, :class:`StageChannel`]]: The channel this scheduled event is in."""
return self.guild.get_channel(self.channel_id) # type: ignore
async def start(self, *, reason: Optional[str] = None) -> ScheduledEvent:
"""|coro|
Starts the scheduled event.
Shorthand for:
.. code-block:: python3
await event.edit(status=EventStatus.active)
Parameters
-----------
reason: Optional[:class:`str`]
The reason for starting the scheduled event.
Raises
------
ValueError
The scheduled event has already started or has ended.
Forbidden
You do not have the proper permissions to start the scheduled event.
HTTPException
The scheduled event could not be started.
Returns
-------
:class:`ScheduledEvent`
The scheduled event that was started.
"""
if self.status is not EventStatus.scheduled:
raise ValueError('This scheduled event is already running.')
return await self.edit(status=EventStatus.active, reason=reason)
async def end(self, *, reason: Optional[str] = None) -> ScheduledEvent:
"""|coro|
Ends the scheduled event.
Shorthand for:
.. code-block:: python3
await event.edit(status=EventStatus.completed)
Parameters
-----------
reason: Optional[:class:`str`]
The reason for ending the scheduled event.
Raises
------
ValueError
The scheduled event is not active or has already ended.
Forbidden
You do not have the proper permissions to end the scheduled event.
HTTPException
The scheduled event could not be ended.
Returns
-------
:class:`ScheduledEvent`
The scheduled event that was ended.
"""
if self.status is not EventStatus.active:
raise ValueError('This scheduled event is not active.')
return await self.edit(status=EventStatus.ended, reason=reason)
async def cancel(self, *, reason: Optional[str] = None) -> ScheduledEvent:
"""|coro|
Cancels the scheduled event.
Shorthand for:
.. code-block:: python3
await event.edit(status=EventStatus.cancelled)
Parameters
-----------
reason: Optional[:class:`str`]
The reason for cancelling the scheduled event.
Raises
------
ValueError
The scheduled event is already running.
Forbidden
You do not have the proper permissions to cancel the scheduled event.
HTTPException
The scheduled event could not be cancelled.
Returns
-------
:class:`ScheduledEvent`
The scheduled event that was cancelled.
"""
if self.status is not EventStatus.scheduled:
raise ValueError('This scheduled event is already running.')
return await self.edit(status=EventStatus.cancelled, reason=reason)
async def edit(
self,
*,
name: str = MISSING,
description: str = MISSING,
channel: Optional[Snowflake] = MISSING,
start_time: datetime = MISSING,
end_time: datetime = MISSING,
privacy_level: PrivacyLevel = MISSING,
entity_type: EntityType = MISSING,
status: EventStatus = MISSING,
image: bytes = MISSING,
location: str = MISSING,
reason: Optional[str] = None,
) -> ScheduledEvent:
r"""|coro|
Edits the scheduled event.
Requires :attr:`~Permissions.manage_events` permissions.
Parameters
-----------
name: :class:`str`
The name of the scheduled event.
description: :class:`str`
The description of the scheduled event.
channel: Optional[:class:`~discord.abc.Snowflake`]
The channel to put the scheduled event in.
Required if the entity type is either :attr:`EntityType.voice` or
:attr:`EntityType.stage_instance`.
start_time: :class:`datetime.datetime`
The time that the scheduled event will start. This must be a timezone-aware
datetime object. Consider using :func:`utils.utcnow`.
end_time: :class:`datetime.datetime`
The time that the scheduled event will end. This must be a timezone-aware
datetime object. Consider using :func:`utils.utcnow`.
Required if the entity type is :attr:`EntityType.external`.
privacy_level: :class:`PrivacyLevel`
The privacy level of the scheduled event.
entity_type: :class:`EntityType`
The new entity type.
status: :class:`EventStatus`
The new status of the scheduled event.
image: :class:`bytes`
The new image of the scheduled event.
location: :class:`str`
The new location of the scheduled event.
Required if the entity type is :attr:`EntityType.external`.
reason: Optional[:class:`str`]
The reason for editing the scheduled event. Shows up on the audit log.
Raises
-------
TypeError
`image` was not a :term:`py:bytes-like object`, or ``privacy_level``
was not a :class:`PrivacyLevel`, or ``entity_type`` was not an
:class:`EntityType`, ``status`` was not an :class:`EventStatus`, or
an argument was provided that was incompatible with the scheduled event's
entity type.
ValueError
``start_time`` or ``end_time`` was not a timezone-aware datetime object.
Forbidden
You do not have permissions to edit the scheduled event.
HTTPException
Editing the scheduled event failed.
Returns
--------
:class:`ScheduledEvent`
The edited scheduled event.
"""
payload = {}
metadata = {}
if name is not MISSING:
payload['name'] = name
if start_time is not MISSING:
if start_time.tzinfo is None:
raise ValueError(
'start_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.'
)
payload['scheduled_start_time'] = start_time.isoformat()
if description is not MISSING:
payload['description'] = description
if privacy_level is not MISSING:
if not isinstance(privacy_level, PrivacyLevel):
raise TypeError('privacy_level must be of type PrivacyLevel.')
payload['privacy_level'] = privacy_level.value
if status is not MISSING:
if not isinstance(status, EventStatus):
raise TypeError('status must be of type EventStatus')
payload['status'] = status.value
if image is not MISSING:
image_as_str: str = _bytes_to_base64_data(image)
payload['image'] = image_as_str
if entity_type is not MISSING:
if not isinstance(entity_type, EntityType):
raise TypeError('entity_type must be of type EntityType')
payload['entity_type'] = entity_type.value
_entity_type = entity_type or self.entity_type
if _entity_type in (EntityType.stage_instance, EntityType.voice):
if channel is MISSING or channel is None:
raise TypeError('channel must be set when entity_type is voice or stage_instance')
payload['channel_id'] = channel.id
if location is not MISSING:
raise TypeError('location cannot be set when entity_type is voice or stage_instance')
else:
if channel is not MISSING:
raise TypeError('channel cannot be set when entity_type is external')
if location is MISSING or location is None:
raise TypeError('location must be set when entity_type is external')
metadata['location'] = location
if end_time is MISSING:
raise TypeError('end_time must be set when entity_type is external')
if end_time.tzinfo is None:
raise ValueError(
'end_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.'
)
payload['scheduled_end_time'] = end_time.isoformat()
if metadata:
payload['entity_metadata'] = metadata
data = await self._state.http.edit_scheduled_event(self.guild_id, self.id, **payload, reason=reason)
s = ScheduledEvent(state=self._state, data=data)
s._users = self._users
return s
async def delete(self, *, reason: Optional[str] = None) -> None:
"""|coro|
Deletes the scheduled event.
Requires :attr:`~Permissions.manage_events` permissions.
Parameters
-----------
reason: Optional[:class:`str`]
The reason for deleting the scheduled event. Shows up on the audit log.
Raises
------
Forbidden
You do not have permissions to delete the scheduled event.
HTTPException
Deleting the scheduled event failed.
"""
await self._state.http.delete_scheduled_event(self.guild_id, self.id, reason=reason)
async def users(
self,
*,
limit: Optional[int] = None,
before: Optional[Snowflake] = None,
after: Optional[Snowflake] = None,
oldest_first: bool = MISSING,
) -> AsyncIterator[User]:
"""|coro|
Retrieves all :class:`User` that are in this thread.
This requires :attr:`Intents.members` to get information about members
other than yourself.
Raises
-------
HTTPException
Retrieving the members failed.
Returns
--------
List[:class:`User`]
All thread members in the thread.
"""
async def _before_strategy(retrieve, before, limit):
before_id = before.id if before else None
users = await self._state.http.get_scheduled_event_users(
self.guild_id, self.id, limit=retrieve, with_member=False, before=before_id
)
if users:
if limit is not None:
limit -= len(users)
before = Object(id=users[-1]['user']['id'])
return users, before, limit
async def _after_strategy(retrieve, after, limit):
after_id = after.id if after else None
users = await self._state.http.get_scheduled_event_users(
self.guild_id, self.id, limit=retrieve, with_member=False, after=after_id
)
if users:
if limit is not None:
limit -= len(users)
after = Object(id=users[0]['user']['id'])
return users, after, limit
if limit is None:
limit = self.user_count or None
if oldest_first is MISSING:
reverse = after is not None
else:
reverse = oldest_first
predicate = None
if reverse:
strategy, state = _after_strategy, after
if before:
predicate = lambda u: u['user']['id'] < before.id
else:
strategy, state = _before_strategy, before
if after and after != OLDEST_OBJECT:
predicate = lambda u: u['user']['id'] > after.id
while True:
retrieve = min(100 if limit is None else limit, 100)
if retrieve < 1:
return
data, state, limit = await strategy(retrieve, state, limit)
if len(data) < 100:
limit = 0
if reverse:
data = reversed(data)
if predicate:
data = filter(predicate, data)
users = (self._state.store_user(raw_user['user']) for raw_user in data)
for user in users:
yield user
def _add_user(self, user: User) -> None:
self._users[user.id] = user
def _pop_user(self, user_id: int) -> None:
self._users.pop(user_id)

15
discord/stage_instance.py

@ -28,7 +28,7 @@ from typing import Optional, TYPE_CHECKING
from .utils import MISSING, cached_slot_property from .utils import MISSING, cached_slot_property
from .mixins import Hashable from .mixins import Hashable
from .enums import StagePrivacyLevel, try_enum from .enums import PrivacyLevel, try_enum
# fmt: off # fmt: off
__all__ = ( __all__ = (
@ -72,7 +72,7 @@ class StageInstance(Hashable):
The ID of the channel that the stage instance is running in. The ID of the channel that the stage instance is running in.
topic: :class:`str` topic: :class:`str`
The topic of the stage instance. The topic of the stage instance.
privacy_level: :class:`StagePrivacyLevel` privacy_level: :class:`PrivacyLevel`
The privacy level of the stage instance. The privacy level of the stage instance.
discoverable_disabled: :class:`bool` discoverable_disabled: :class:`bool`
Whether discoverability for the stage instance is disabled. Whether discoverability for the stage instance is disabled.
@ -98,7 +98,7 @@ class StageInstance(Hashable):
self.id: int = int(data['id']) self.id: int = int(data['id'])
self.channel_id: int = int(data['channel_id']) self.channel_id: int = int(data['channel_id'])
self.topic: str = data['topic'] self.topic: str = data['topic']
self.privacy_level: StagePrivacyLevel = try_enum(StagePrivacyLevel, data['privacy_level']) self.privacy_level: PrivacyLevel = try_enum(PrivacyLevel, data['privacy_level'])
self.discoverable_disabled: bool = data.get('discoverable_disabled', False) self.discoverable_disabled: bool = data.get('discoverable_disabled', False)
def __repr__(self) -> str: def __repr__(self) -> str:
@ -110,14 +110,11 @@ class StageInstance(Hashable):
# the returned channel will always be a StageChannel or None # the returned channel will always be a StageChannel or None
return self._state.get_channel(self.channel_id) # type: ignore return self._state.get_channel(self.channel_id) # type: ignore
def is_public(self) -> bool:
return self.privacy_level is StagePrivacyLevel.public
async def edit( async def edit(
self, self,
*, *,
topic: str = MISSING, topic: str = MISSING,
privacy_level: StagePrivacyLevel = MISSING, privacy_level: PrivacyLevel = MISSING,
reason: Optional[str] = None, reason: Optional[str] = None,
) -> None: ) -> None:
"""|coro| """|coro|
@ -131,7 +128,7 @@ class StageInstance(Hashable):
----------- -----------
topic: :class:`str` topic: :class:`str`
The stage instance's new topic. The stage instance's new topic.
privacy_level: :class:`StagePrivacyLevel` privacy_level: :class:`PrivacyLevel`
The stage instance's new privacy level. The stage instance's new privacy level.
reason: :class:`str` reason: :class:`str`
The reason the stage instance was edited. Shows up on the audit log. The reason the stage instance was edited. Shows up on the audit log.
@ -152,7 +149,7 @@ class StageInstance(Hashable):
payload['topic'] = topic payload['topic'] = topic
if privacy_level is not MISSING: if privacy_level is not MISSING:
if not isinstance(privacy_level, StagePrivacyLevel): if not isinstance(privacy_level, PrivacyLevel):
raise TypeError('privacy_level field must be of type PrivacyLevel') raise TypeError('privacy_level field must be of type PrivacyLevel')
payload['privacy_level'] = privacy_level.value payload['privacy_level'] = privacy_level.value

81
discord/state.py

@ -56,6 +56,7 @@ from .invite import Invite
from .integrations import _integration_factory from .integrations import _integration_factory
from .interactions import Interaction from .interactions import Interaction
from .ui.view import ViewStore, View from .ui.view import ViewStore, View
from .scheduled_event import ScheduledEvent
from .stage_instance import StageInstance from .stage_instance import StageInstance
from .threads import Thread, ThreadMember from .threads import Thread, ThreadMember
from .sticker import GuildSticker from .sticker import GuildSticker
@ -744,6 +745,12 @@ class ConnectionState:
guild._remove_channel(channel) guild._remove_channel(channel)
self.dispatch('guild_channel_delete', channel) self.dispatch('guild_channel_delete', channel)
if channel.type in (ChannelType.voice, ChannelType.stage_voice):
for s in guild.scheduled_events:
if s.channel_id == channel.id:
guild._scheduled_events.pop(s.id)
self.dispatch('scheduled_event_delete', guild, s)
def parse_channel_update(self, data: gw.ChannelUpdateEvent) -> None: def parse_channel_update(self, data: gw.ChannelUpdateEvent) -> None:
channel_type = try_enum(ChannelType, data.get('type')) channel_type = try_enum(ChannelType, data.get('type'))
channel_id = int(data['id']) channel_id = int(data['id'])
@ -1286,6 +1293,80 @@ class ConnectionState:
else: else:
_log.debug('STAGE_INSTANCE_DELETE referencing unknown guild ID: %s. Discarding.', data['guild_id']) _log.debug('STAGE_INSTANCE_DELETE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
def parse_guild_scheduled_event_create(self, data: gw.GuildScheduledEventCreateEvent) -> None:
guild = self._get_guild(int(data['guild_id']))
if guild is not None:
scheduled_event = ScheduledEvent(state=self, data=data)
guild._scheduled_events[scheduled_event.id] = scheduled_event
self.dispatch('scheduled_event_create', guild, scheduled_event)
else:
_log.debug('SCHEDULED_EVENT_CREATE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
def parse_guild_scheduled_event_update(self, data: gw.GuildScheduledEventUpdateEvent) -> None:
guild = self._get_guild(int(data['guild_id']))
if guild is not None:
scheduled_event = guild._scheduled_events.get(int(data['id']))
if scheduled_event is not None:
old_scheduled_event = copy.copy(scheduled_event)
scheduled_event._update(data)
self.dispatch('scheduled_event_update', guild, old_scheduled_event, scheduled_event)
else:
_log.debug('SCHEDULED_EVENT_UPDATE referencing unknown scheduled event ID: %s. Discarding.', data['id'])
else:
_log.debug('SCHEDULED_EVENT_UPDATE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
def parse_guild_scheduled_event_delete(self, data: gw.GuildScheduledEventDeleteEvent) -> None:
guild = self._get_guild(int(data['guild_id']))
if guild is not None:
try:
scheduled_event = guild._scheduled_events.pop(int(data['id']))
except KeyError:
pass
else:
self.dispatch('scheduled_event_delete', guild, scheduled_event)
else:
_log.debug('SCHEDULED_EVENT_DELETE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
def parse_guild_scheduled_event_user_add(self, data: gw.GuildScheduledEventUserAdd) -> None:
guild = self._get_guild(int(data['guild_id']))
if guild is not None:
scheduled_event = guild._scheduled_events.get(int(data['guild_scheduled_event_id']))
if scheduled_event is not None:
user = self.get_user(int(data['user_id']))
if user is not None:
scheduled_event._add_user(user)
self.dispatch('scheduled_event_user_add', guild, scheduled_event, user)
else:
_log.debug('SCHEDULED_EVENT_USER_ADD referencing unknown user ID: %s. Discarding.', data['user_id'])
self.dispatch('scheduled_event_user_add', guild, scheduled_event, user)
else:
_log.debug(
'SCHEDULED_EVENT_USER_ADD referencing unknown scheduled event ID: %s. Discarding.',
data['guild_scheduled_event_id'],
)
else:
_log.debug('SCHEDULED_EVENT_USER_ADD referencing unknown guild ID: %s. Discarding.', data['guild_id'])
def parse_guild_scheduled_event_user_remove(self, data: gw.GuildScheduledEventUserRemove) -> None:
guild = self._get_guild(int(data['guild_id']))
if guild is not None:
scheduled_event = guild._scheduled_events.get(int(data['guild_scheduled_event_id']))
if scheduled_event is not None:
user = self.get_user(int(data['user_id']))
if user is not None:
scheduled_event._pop_user(user.id)
self.dispatch('scheduled_event_user_remove', scheduled_event, user)
else:
_log.debug('SCHEDULED_EVENT_USER_REMOVE referencing unknown user ID: %s. Discarding.', data['user_id'])
self.dispatch('scheduled_event_user_remove', scheduled_event, user)
else:
_log.debug(
'SCHEDULED_EVENT_USER_REMOVE referencing unknown scheduled event ID: %s. Discarding.',
data['guild_scheduled_event_id'],
)
else:
_log.debug('SCHEDULED_EVENT_USER_REMOVE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
def parse_voice_state_update(self, data: gw.VoiceStateUpdateEvent) -> None: def parse_voice_state_update(self, data: gw.VoiceStateUpdateEvent) -> None:
guild = self._get_guild(utils._get_as_snowflake(data, 'guild_id')) guild = self._get_guild(utils._get_as_snowflake(data, 'guild_id'))
channel_id = utils._get_as_snowflake(data, 'channel_id') channel_id = utils._get_as_snowflake(data, 'channel_id')

30
discord/types/audit_log.py

@ -25,13 +25,15 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import List, Literal, Optional, TypedDict, Union from typing import List, Literal, Optional, TypedDict, Union
from .webhook import Webhook from .webhook import Webhook
from .guild import MFALevel, VerificationLevel, ExplicitContentFilterLevel, DefaultMessageNotificationLevel from .guild import MFALevel, VerificationLevel, ExplicitContentFilterLevel, DefaultMessageNotificationLevel
from .integration import IntegrationExpireBehavior, PartialIntegration from .integration import IntegrationExpireBehavior, PartialIntegration
from .user import User from .user import User
from .scheduled_event import EntityType, EventStatus, GuildScheduledEvent
from .snowflake import Snowflake from .snowflake import Snowflake
from .role import Role from .role import Role
from .channel import ChannelType, VideoQualityMode, PermissionOverwrite from .channel import ChannelType, PrivacyLevel, VideoQualityMode, PermissionOverwrite
from .threads import Thread from .threads import Thread
AuditLogEvent = Literal[ AuditLogEvent = Literal[
@ -76,6 +78,9 @@ AuditLogEvent = Literal[
90, 90,
91, 91,
92, 92,
100,
101,
102,
110, 110,
111, 111,
112, 112,
@ -158,6 +163,7 @@ class _AuditLogChange_Int(TypedDict):
'user_limit', 'user_limit',
'auto_archive_duration', 'auto_archive_duration',
'default_auto_archive_duration', 'default_auto_archive_duration',
'communication_disabled_until',
] ]
new_value: int new_value: int
old_value: int old_value: int
@ -217,6 +223,24 @@ class _AuditLogChange_Overwrites(TypedDict):
old_value: List[PermissionOverwrite] old_value: List[PermissionOverwrite]
class _AuditLogChange_PrivacyLevel(TypedDict):
key: Literal['privacy_level']
new_value: PrivacyLevel
old_value: PrivacyLevel
class _AuditLogChange_Status(TypedDict):
key: Literal['status']
new_value: EventStatus
old_value: EventStatus
class _AuditLogChange_EntityType(TypedDict):
key: Literal['entity_type']
new_value: EntityType
old_value: EntityType
AuditLogChange = Union[ AuditLogChange = Union[
_AuditLogChange_Str, _AuditLogChange_Str,
_AuditLogChange_AssetHash, _AuditLogChange_AssetHash,
@ -232,6 +256,9 @@ AuditLogChange = Union[
_AuditLogChange_IntegrationExpireBehaviour, _AuditLogChange_IntegrationExpireBehaviour,
_AuditLogChange_VideoQualityMode, _AuditLogChange_VideoQualityMode,
_AuditLogChange_Overwrites, _AuditLogChange_Overwrites,
_AuditLogChange_PrivacyLevel,
_AuditLogChange_Status,
_AuditLogChange_EntityType,
] ]
@ -265,3 +292,4 @@ class AuditLog(TypedDict):
audit_log_entries: List[AuditLogEntry] audit_log_entries: List[AuditLogEntry]
integrations: List[PartialIntegration] integrations: List[PartialIntegration]
threads: List[Thread] threads: List[Thread]
guild_scheduled_events: List[GuildScheduledEvent]

2
discord/types/channel.py

@ -146,7 +146,7 @@ class GroupDMChannel(_BaseChannel):
Channel = Union[GuildChannel, DMChannel, GroupDMChannel] Channel = Union[GuildChannel, DMChannel, GroupDMChannel]
PrivacyLevel = Literal[1, 2] PrivacyLevel = Literal[2]
class StageInstance(TypedDict): class StageInstance(TypedDict):

14
discord/types/gateway.py

@ -29,7 +29,7 @@ from .activity import PartialPresenceUpdate
from .voice import GuildVoiceState from .voice import GuildVoiceState
from .integration import BaseIntegration, IntegrationApplication from .integration import BaseIntegration, IntegrationApplication
from .role import Role from .role import Role
from .channel import Channel, ChannelType, StageInstance from .channel import ChannelType, StageInstance
from .interactions import Interaction from .interactions import Interaction
from .invite import InviteTargetType from .invite import InviteTargetType
from .emoji import Emoji, PartialEmoji from .emoji import Emoji, PartialEmoji
@ -41,6 +41,7 @@ from .appinfo import GatewayAppInfo, PartialAppInfo
from .guild import Guild, UnavailableGuild from .guild import Guild, UnavailableGuild
from .user import User from .user import User
from .threads import Thread, ThreadMember from .threads import Thread, ThreadMember
from .scheduled_event import GuildScheduledEvent
class SessionStartLimit(TypedDict): class SessionStartLimit(TypedDict):
@ -348,6 +349,17 @@ class WebhooksUpdateEvent(TypedDict):
StageInstanceCreateEvent = StageInstanceUpdateEvent = StageInstanceDeleteEvent = StageInstance StageInstanceCreateEvent = StageInstanceUpdateEvent = StageInstanceDeleteEvent = StageInstance
GuildScheduledEventCreateEvent = GuildScheduledEventUpdateEvent = GuildScheduledEventDeleteEvent = GuildScheduledEvent
class _GuildScheduledEventUsersEvent(TypedDict):
guild_scheduled_event_id: Snowflake
user_id: Snowflake
guild_id: Snowflake
GuildScheduledEventUserAdd = GuildScheduledEventUserRemove = _GuildScheduledEventUsersEvent
VoiceStateUpdateEvent = GuildVoiceState VoiceStateUpdateEvent = GuildVoiceState

8
discord/types/guild.py

@ -23,8 +23,11 @@ DEALINGS IN THE SOFTWARE.
""" """
from typing import List, Literal, Optional, TypedDict from typing import List, Literal, Optional, TypedDict
from .scheduled_event import GuildScheduledEvent
from .sticker import GuildSticker
from .snowflake import Snowflake from .snowflake import Snowflake
from .channel import GuildChannel from .channel import GuildChannel, StageInstance
from .voice import GuildVoiceState from .voice import GuildVoiceState
from .welcome_screen import WelcomeScreen from .welcome_screen import WelcomeScreen
from .activity import PartialPresenceUpdate from .activity import PartialPresenceUpdate
@ -142,6 +145,9 @@ class Guild(_BaseGuildPreview, _GuildOptional):
premium_tier: PremiumTier premium_tier: PremiumTier
preferred_locale: str preferred_locale: str
public_updates_channel_id: Optional[Snowflake] public_updates_channel_id: Optional[Snowflake]
stickers: List[GuildSticker]
stage_instances: List[StageInstance]
guild_scheduled_events: List[GuildScheduledEvent]
class InviteGuild(Guild, total=False): class InviteGuild(Guild, total=False):

3
discord/types/invite.py

@ -26,6 +26,8 @@ from __future__ import annotations
from typing import Literal, Optional, TypedDict, Union from typing import Literal, Optional, TypedDict, Union
from .scheduled_event import GuildScheduledEvent
from .snowflake import Snowflake from .snowflake import Snowflake
from .guild import InviteGuild, _GuildPreviewUnique from .guild import InviteGuild, _GuildPreviewUnique
from .channel import PartialChannel from .channel import PartialChannel
@ -41,6 +43,7 @@ class _InviteOptional(TypedDict, total=False):
target_user: PartialUser target_user: PartialUser
target_type: InviteTargetType target_type: InviteTargetType
target_application: PartialAppInfo target_application: PartialAppInfo
guild_scheduled_event: GuildScheduledEvent
class _InviteMetadata(TypedDict, total=False): class _InviteMetadata(TypedDict, total=False):

15
discord/types/scheduled_event.py

@ -22,28 +22,31 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from typing import Literal, Optional, TypedDict, Union from typing import List, Literal, Optional, TypedDict, Union
from .snowflake import Snowflake from .snowflake import Snowflake
from .user import User from .user import User
from .member import Member from .member import Member
from .channel import PrivacyLevel as PrivacyLevel
GuildScheduledEventPrivacyLevel = Literal[1]
EventStatus = Literal[1, 2, 3, 4] EventStatus = Literal[1, 2, 3, 4]
EntityType = Literal[1, 2, 3]
class _BaseGuildScheduledEventOptional(TypedDict, total=False): class _BaseGuildScheduledEventOptional(TypedDict, total=False):
creator_id: Optional[Snowflake] creator_id: Optional[Snowflake]
description: str description: str
creator: User creator: User
user_count: int
class _BaseGuildScheduledEvent(_BaseGuildScheduledEventOptional): class _BaseGuildScheduledEvent(_BaseGuildScheduledEventOptional):
id: Snowflake id: Snowflake
guild_id: Snowflake guild_id: Snowflake
entity_id: Optional[Snowflake]
name: str name: str
scheduled_start_time: str scheduled_start_time: str
privacy_level: GuildScheduledEventPrivacyLevel privacy_level: PrivacyLevel
status: EventStatus status: EventStatus
image: Optional[str] image: Optional[str]
@ -80,7 +83,7 @@ GuildScheduledEvent = Union[StageInstanceScheduledEvent, VoiceScheduledEvent, Ex
class _WithUserCount(TypedDict): class _WithUserCount(TypedDict):
user_count: str user_count: int
class _StageInstanceScheduledEventWithUserCount(StageInstanceScheduledEvent, _WithUserCount): class _StageInstanceScheduledEventWithUserCount(StageInstanceScheduledEvent, _WithUserCount):
@ -107,3 +110,7 @@ class ScheduledEventUser(TypedDict):
class ScheduledEventUserWithMember(ScheduledEventUser): class ScheduledEventUserWithMember(ScheduledEventUser):
member: Member member: Member
ScheduledEventUsers = List[ScheduledEventUser]
ScheduledEventUsersWithMember = List[ScheduledEventUserWithMember]

31
discord/utils.py

@ -40,6 +40,7 @@ from typing import (
List, List,
Literal, Literal,
Mapping, Mapping,
NamedTuple,
Optional, Optional,
Protocol, Protocol,
Sequence, Sequence,
@ -63,6 +64,8 @@ import sys
import types import types
import warnings import warnings
import yarl
try: try:
import orjson import orjson
except ModuleNotFoundError: except ModuleNotFoundError:
@ -729,10 +732,19 @@ def _string_width(string: str, *, _IS_ASCII=_IS_ASCII) -> int:
return sum(2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1 for char in string) return sum(2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1 for char in string)
def resolve_invite(invite: Union[Invite, str]) -> str: class ResolvedInvite(NamedTuple):
code: str
event: Optional[int]
def resolve_invite(invite: Union[Invite, str]) -> ResolvedInvite:
""" """
Resolves an invite from a :class:`~discord.Invite`, URL or code. Resolves an invite from a :class:`~discord.Invite`, URL or code.
.. versionchanged:: 2.0
Now returns a :class:`.ResolvedInvite` instead of a
:class:`str`.
Parameters Parameters
----------- -----------
invite: Union[:class:`~discord.Invite`, :class:`str`] invite: Union[:class:`~discord.Invite`, :class:`str`]
@ -740,19 +752,24 @@ def resolve_invite(invite: Union[Invite, str]) -> str:
Returns Returns
-------- --------
:class:`str` :class:`.ResolvedInvite`
The invite code. A data class containing the invite code and the event ID.
""" """
from .invite import Invite # circular import from .invite import Invite # circular import
if isinstance(invite, Invite): if isinstance(invite, Invite):
return invite.code return ResolvedInvite(invite.code, invite.scheduled_event_id)
else: else:
rx = r'(?:https?\:\/\/)?discord(?:\.gg|(?:app)?\.com\/invite)\/(.+)' rx = r'(?:https?\:\/\/)?discord(?:\.gg|(?:app)?\.com\/invite)\/[^/]+'
m = re.match(rx, invite) m = re.match(rx, invite)
if m: if m:
return m.group(1) url = yarl.URL(invite)
return invite code = url.parts[-1]
event_id = url.query.get('event')
return ResolvedInvite(code, int(event_id) if event_id else None)
return ResolvedInvite(invite, None)
def resolve_template(code: Union[Template, str]) -> str: def resolve_template(code: Union[Template, str]) -> str:

4
discord/widget.py

@ -314,6 +314,6 @@ class Widget:
:class:`Invite` :class:`Invite`
The invite from the widget's invite URL. The invite from the widget's invite URL.
""" """
invite_id = resolve_invite(self._invite) resolved = resolve_invite(self._invite)
data = await self._state.http.get_invite(invite_id, with_counts=with_counts) data = await self._state.http.get_invite(resolved.code, with_counts=with_counts)
return Invite.from_incomplete(state=self._state, data=data) return Invite.from_incomplete(state=self._state, data=data)

137
docs/api.rst

@ -1020,6 +1020,53 @@ to handle it, which defaults to print a traceback and ignoring the exception.
:param after: The stage instance after the update. :param after: The stage instance after the update.
:type after: :class:`StageInstance` :type after: :class:`StageInstance`
.. function:: on_scheduled_event_create(event)
on_scheduled_event_delete(event)
Called when a :class:`ScheduledEvent` is created or deleted.
This requires :attr:`Intents.guild_scheduled_events` to be enabled.
.. versionadded:: 2.0
:param event: The scheduled event that was created or deleted.
:type event: :class:`ScheduledEvent`
.. function:: on_scheduled_event_update(before, after)
Called when a :class:`ScheduledEvent` is updated.
This requires :attr:`Intents.guild_scheduled_events` to be enabled.
The following, but not limited to, examples illustrate when this event is called:
- The scheduled start/end times are changed.
- The channel is changed.
- The description is changed.
- The status is changed.
- The image is changed.
.. versionadded:: 2.0
:param before: The scheduled event before the update.
:type before: :class:`ScheduledEvent`
:param after: The scheduled event after the update.
:type after: :class:`ScheduledEvent`
.. function:: on_scheduled_event_user_add(event, user)
on_scheduled_event_user_remove(event, user)
Called when a user is added or removed from a :class:`ScheduledEvent`.
This requires :attr:`Intents.guild_scheduled_events` to be enabled.
.. versionadded:: 2.0
:param event: The scheduled event that the user was added or removed from.
:type event: :class:`ScheduledEvent`
:param user: The user that was added or removed.
:type user: :class:`User`
.. function:: on_member_ban(guild, user) .. function:: on_member_ban(guild, user)
Called when user gets banned from a :class:`Guild`. Called when user gets banned from a :class:`Guild`.
@ -1110,6 +1157,22 @@ Utility Functions
.. autofunction:: discord.utils.escape_mentions .. autofunction:: discord.utils.escape_mentions
.. class:: ResolvedInvite
A data class which represents a resolved invite returned from :func:`discord.utils.resolve_invite`.
.. attribute:: code
The invite code.
:type: :class:`str`
.. attribute:: event
The id of the scheduled event that the invite refers to.
:type: Optional[:class:`int`]
.. autofunction:: discord.utils.resolve_invite .. autofunction:: discord.utils.resolve_invite
.. autofunction:: discord.utils.resolve_template .. autofunction:: discord.utils.resolve_template
@ -2503,20 +2566,12 @@ of :class:`enum.Enum`.
Represents full camera video quality. Represents full camera video quality.
.. class:: StagePrivacyLevel .. class:: PrivacyLevel
Represents a stage instance's privacy level. Represents the privacy level of a stage instance or scheduled event.
.. versionadded:: 2.0 .. versionadded:: 2.0
.. attribute:: public
The stage instance can be joined by external users.
.. attribute:: closed
The stage instance can only be joined by members of the guild.
.. attribute:: guild_only .. attribute:: guild_only
Alias for :attr:`.closed` Alias for :attr:`.closed`
@ -2726,6 +2781,50 @@ of :class:`enum.Enum`.
The guild requires 2 factor authentication. The guild requires 2 factor authentication.
.. class:: EntityType
Represents the type of entity that a scheduled event is for.
.. versionadded:: 2.0
.. attribute:: stage_instance
The scheduled event will occur in a stage instance.
.. attribute:: voice
The scheduled event will occur in a voice channel.
.. attribute:: external
The scheduled event will occur externally.
.. class:: EventStatus
Represents the status of an event.
.. versionadded:: 2.0
.. attribute:: scheduled
The event is scheduled.
.. attribute:: active
The event is active.
.. attribute:: completed
The event has ended.
.. attribute:: cancelled
The event has been cancelled.
.. attribute:: canceled
An alias for :attr:`cancelled`.
.. _discord-api-audit-logs: .. _discord-api-audit-logs:
Audit Log Data Audit Log Data
@ -3007,9 +3106,9 @@ AuditLogDiff
.. attribute:: privacy_level .. attribute:: privacy_level
The privacy level of the stage instance. The privacy level of the stage instance or scheduled event
:type: :class:`StagePrivacyLevel` :type: :class:`PrivacyLevel`
.. attribute:: roles .. attribute:: roles
@ -3207,9 +3306,10 @@ AuditLogDiff
.. attribute:: description .. attribute:: description
The description of a guild, or a sticker. The description of a guild, a sticker, or a scheduled event.
See also :attr:`Guild.description`, or :attr:`GuildSticker.description`. See also :attr:`Guild.description`, :attr:`GuildSticker.description`, or
:attr:`ScheduledEvent.description`.
:type: :class:`str` :type: :class:`str`
@ -3563,6 +3663,15 @@ Guild
:type: :class:`User` :type: :class:`User`
ScheduledEvent
~~~~~~~~~~~~~~
.. attributetable:: ScheduledEvent
.. autoclass:: ScheduledEvent()
:members:
Integration Integration
~~~~~~~~~~~~ ~~~~~~~~~~~~

Loading…
Cancel
Save