aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNadir Chowdhury <[email protected]>2021-05-30 18:51:52 +0100
committerGitHub <[email protected]>2021-05-30 13:51:52 -0400
commit9f98a9a87fa1f2cc2702b5dc536178a018a631eb (patch)
tree2eafaf9d8b0c9b791396e1bf953c4adc4b6d8282
parentFix potential KeyError when removing views (diff)
downloaddiscord.py-9f98a9a87fa1f2cc2702b5dc536178a018a631eb.tar.xz
discord.py-9f98a9a87fa1f2cc2702b5dc536178a018a631eb.zip
Implement StageInstance
-rw-r--r--discord/__init__.py1
-rw-r--r--discord/channel.py123
-rw-r--r--discord/client.py55
-rw-r--r--discord/enums.py6
-rw-r--r--discord/guild.py36
-rw-r--r--discord/http.py29
-rw-r--r--discord/stage_instance.py168
-rw-r--r--discord/state.py35
-rw-r--r--discord/types/channel.py12
-rw-r--r--docs/api.rst52
10 files changed, 502 insertions, 15 deletions
diff --git a/discord/__init__.py b/discord/__init__.py
index 3b057e36..dcf421ff 100644
--- a/discord/__init__.py
+++ b/discord/__init__.py
@@ -55,6 +55,7 @@ from .audit_logs import *
from .raw_models import *
from .team import *
from .sticker import *
+from .stage_instance import *
from .interactions import *
from .components import *
diff --git a/discord/channel.py b/discord/channel.py
index c9eb9c34..ded4da41 100644
--- a/discord/channel.py
+++ b/discord/channel.py
@@ -30,11 +30,12 @@ from typing import Callable, Dict, List, Optional, TYPE_CHECKING, Union, overloa
import discord.abc
from .permissions import PermissionOverwrite, Permissions
-from .enums import ChannelType, try_enum, VoiceRegion, VideoQualityMode
+from .enums import ChannelType, StagePrivacyLevel, try_enum, VoiceRegion, VideoQualityMode
from .mixins import Hashable
from . import utils
from .asset import Asset
from .errors import ClientException, NoMoreItems, InvalidArgument
+from .stage_instance import StageInstance
__all__ = (
'TextChannel',
@@ -49,7 +50,7 @@ __all__ = (
if TYPE_CHECKING:
from .role import Role
- from .member import Member
+ from .member import Member, VoiceState
from .abc import Snowflake
from .message import Message
from .webhook import Webhook
@@ -611,7 +612,7 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha
return ChannelType.voice.value
@property
- def members(self):
+ def members(self) -> List[Member]:
"""List[:class:`Member`]: Returns all members that are currently inside this voice channel."""
ret = []
for user_id, state in self.guild._voice_states.items():
@@ -622,7 +623,7 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha
return ret
@property
- def voice_states(self):
+ def voice_states(self) -> Dict[int, VoiceState]:
"""Returns a mapping of member IDs who have voice states in this channel.
.. versionadded:: 1.3
@@ -640,7 +641,7 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha
return {key: value for key, value in self.guild._voice_states.items() if value.channel.id == self.id}
@utils.copy_doc(discord.abc.GuildChannel.permissions_for)
- def permissions_for(self, member):
+ def permissions_for(self, member: Union[Role, Member], /) -> Permissions:
base = super().permissions_for(member)
# voice channels cannot be edited by people who can't connect to them
@@ -875,20 +876,119 @@ class StageChannel(VocalGuildChannel):
self.topic = data.get('topic')
@property
- def requesting_to_speak(self):
+ def requesting_to_speak(self) -> List[Member]:
"""List[:class:`Member`]: A list of members who are requesting to speak in the stage channel."""
return [member for member in self.members if member.voice.requested_to_speak_at is not None]
@property
+ def speakers(self) -> List[Member]:
+ """List[:class:`Member`]: A list of members who have been permitted to speak in the stage channel.
+
+ .. versionadded:: 2.0
+ """
+ return [member for member in self.members if not member.voice.suppress and member.voice.requested_to_speak_at is None]
+
+ @property
+ def listeners(self) -> List[Member]:
+ """List[:class:`Member`]: A list of members who are listening in the stage channel.
+
+ .. versionadded:: 2.0
+ """
+ return [member for member in self.members if member.voice.suppress]
+
+ @property
+ def moderators(self) -> List[Member]:
+ """List[:class:`Member`]: A list of members who are moderating the stage channel.
+
+ .. versionadded:: 2.0
+ """
+ required_permissions = Permissions.stage_moderator()
+ return [member for member in self.members if self.permissions_for(member) >= required_permissions]
+
+ @property
def type(self):
""":class:`ChannelType`: The channel's Discord type."""
return ChannelType.stage_voice
@utils.copy_doc(discord.abc.GuildChannel.clone)
async def clone(self, *, name: str = None, reason: Optional[str] = None) -> StageChannel:
- return await self._clone_impl({
- 'topic': self.topic,
- }, name=name, reason=reason)
+ return await self._clone_impl({}, name=name, reason=reason)
+
+ @property
+ def instance(self) -> Optional[StageInstance]:
+ """Optional[:class:`StageInstance`]: The running stage instance of the stage channel.
+
+ .. versionadded:: 2.0
+ """
+ return utils.get(self.guild.stage_instances, channel_id=self.id)
+
+ async def create_instance(self, *, topic: str, privacy_level: StagePrivacyLevel = utils.MISSING) -> StageInstance:
+ """|coro|
+
+ Create a stage instance.
+
+ You must have the :attr:`~Permissions.manage_channels` permission to
+ use this.
+
+ .. versionadded:: 2.0
+
+ Parameters
+ -----------
+ topic: :class:`str`
+ The stage instance's topic.
+ privacy_level: :class:`StagePrivacyLevel`
+ The stage instance's privacy level. Defaults to :attr:`PrivacyLevel.guild_only`.
+
+ Raises
+ ------
+ InvalidArgument
+ If the ``privacy_level`` parameter is not the proper type.
+ Forbidden
+ You do not have permissions to create a stage instance.
+ HTTPException
+ Creating a stage instance failed.
+
+ Returns
+ --------
+ :class:`StageInstance`
+ The newly created stage instance.
+ """
+
+ payload = {
+ 'channel_id': self.id,
+ 'topic': topic
+ }
+
+ if privacy_level is not utils.MISSING:
+ if not isinstance(privacy_level, StagePrivacyLevel):
+ raise InvalidArgument('privacy_level field must be of type PrivacyLevel')
+
+ payload['privacy_level'] = privacy_level.value
+
+ data = await self._state.http.create_stage_instance(**payload)
+ return StageInstance(guild=self.guild, state=self._state, data=data)
+
+ async def fetch_instance(self) -> StageInstance:
+ """|coro|
+
+ Gets the running :class:`StageInstance`.
+
+ .. versionadded:: 2.0
+
+ Raises
+ -------
+ :exc:`.NotFound`
+ The stage instance or channel could not be found.
+ :exc:`.HTTPException`
+ Getting the stage instance failed.
+
+ Returns
+ --------
+ :class:`StageInstance`
+ The stage instance.
+ """
+ data = await self._state.http.get_stage_instance(self.id)
+ return StageInstance(guild=self.guild, state=self._state, data=data)
@overload
async def edit(
@@ -918,12 +1018,13 @@ class StageChannel(VocalGuildChannel):
You must have the :attr:`~Permissions.manage_channels` permission to
use this.
+ .. versionchanged:: 2.0
+ The ``topic`` parameter must now be set via :attr:`create_instance`.
+
Parameters
----------
name: :class:`str`
The new channel's name.
- topic: Optional[:class:`str`]
- The new channel's topic.
position: :class:`int`
The new channel's position.
sync_permissions: :class:`bool`
diff --git a/discord/client.py b/discord/client.py
index 60226012..48bae688 100644
--- a/discord/client.py
+++ b/discord/client.py
@@ -29,7 +29,7 @@ import logging
import signal
import sys
import traceback
-from typing import Any, List, Optional, TYPE_CHECKING, Union
+from typing import Any, Generator, List, Optional, TYPE_CHECKING, TypeVar, Union
import aiohttp
@@ -56,6 +56,7 @@ from .webhook import Webhook
from .iterators import GuildIterator
from .appinfo import AppInfo
from .ui.view import View
+from .stage_instance import StageInstance
__all__ = (
'Client',
@@ -693,6 +694,28 @@ class Client:
"""
return self._connection.get_channel(id)
+ def get_stage_instance(self, id) -> Optional[StageInstance]:
+ """Returns a stage instance with the given stage channel ID.
+
+ .. versionadded:: 2.0
+
+ Parameters
+ -----------
+ id: :class:`int`
+ The ID to search for.
+
+ Returns
+ --------
+ Optional[:class:`StageInstance`]
+ The returns stage instance of ``None`` if not found.
+ """
+ from .channel import StageChannel
+
+ channel = self._connection.get_channel(id)
+
+ if isinstance(channel, StageChannel):
+ return channel.instance
+
def get_guild(self, id):
"""Returns a guild with the given ID.
@@ -1136,6 +1159,34 @@ class Client:
data = await self.http.create_guild(name, region_value, icon)
return Guild(data=data, state=self._connection)
+ async def fetch_stage_instance(self, channel_id: int) -> StageInstance:
+ """|coro|
+
+ Gets a :class:`StageInstance` for a stage channel id.
+
+ .. versionadded:: 2.0
+
+ Parameters
+ -----------
+ channel_id: :class:`int`
+ The stage channel ID.
+
+ Raises
+ -------
+ :exc:`.NotFound`
+ The stage instance or channel could not be found.
+ :exc:`.HTTPException`
+ Getting the stage instance failed.
+
+ Returns
+ --------
+ :class:`StageInstance`
+ The stage instance from the stage channel ID.
+ """
+ data = await self.http.get_stage_instance(channel_id)
+ guild = self.get_guild(int(data['guild_id']))
+ return StageInstance(guild=guild, state=self._connection, data=data) # type: ignore
+
# Invite management
async def fetch_invite(self, url: Union[Invite, str], *, with_counts: bool = True, with_expiration: bool = True) -> Invite:
@@ -1261,7 +1312,7 @@ class Client:
async def fetch_user(self, user_id):
"""|coro|
- Retrieves a :class:`~discord.User` based on their ID.
+ Retrieves a :class:`~discord.User` based on their ID.
You do not have to share any guilds with the user to get this information,
however many operations do require that you do.
diff --git a/discord/enums.py b/discord/enums.py
index f34d135c..897f9602 100644
--- a/discord/enums.py
+++ b/discord/enums.py
@@ -50,6 +50,7 @@ __all__ = (
'VideoQualityMode',
'ComponentType',
'ButtonStyle',
+ 'StagePrivacyLevel',
)
def _create_value_cls(name):
@@ -480,6 +481,11 @@ class ButtonStyle(Enum):
def __int__(self):
return self.value
+class StagePrivacyLevel(Enum):
+ public = 1
+ closed = 2
+ guild_only = 2
+
T = TypeVar('T')
def create_unknown_value(cls: Type[T], val: Any) -> T:
diff --git a/discord/guild.py b/discord/guild.py
index e7c74de9..be223559 100644
--- a/discord/guild.py
+++ b/discord/guild.py
@@ -46,6 +46,7 @@ from .widget import Widget
from .asset import Asset
from .flags import SystemChannelFlags
from .integrations import Integration, _integration_factory
+from .stage_instance import StageInstance
__all__ = (
'Guild',
@@ -182,7 +183,7 @@ class Guild(Hashable):
'description', 'max_presences', 'max_members', 'max_video_channel_users',
'premium_tier', 'premium_subscription_count', '_system_channel_flags',
'preferred_locale', '_discovery_splash', '_rules_channel_id',
- '_public_updates_channel_id', 'nsfw')
+ '_public_updates_channel_id', '_stage_instances', 'nsfw')
_PREMIUM_GUILD_LIMITS = {
None: _GuildLimit(emoji=50, bitrate=96e3, filesize=8388608),
@@ -319,6 +320,11 @@ class Guild(Hashable):
self._public_updates_channel_id = utils._get_as_snowflake(guild, 'public_updates_channel_id')
self.nsfw = guild.get('nsfw', False)
+ self._stage_instances = {}
+ for s in guild.get('stage_instances', []):
+ stage_instance = StageInstance(guild=self, data=s, state=state)
+ self._stage_instances[stage_instance.id] = stage_instance
+
cache_joined = self._state.member_cache_flags.joined
self_id = self._state.self_id
for mdata in guild.get('members', []):
@@ -614,6 +620,32 @@ class Guild(Hashable):
return None
@property
+ def stage_instances(self) -> List[StageInstance]:
+ """List[:class:`StageInstance`]: Returns a :class:`list` of the guild's stage instances that
+ are currently running.
+
+ .. versionadded:: 2.0
+ """
+ return list(self._stage_instances.values())
+
+ def get_stage_instance(self, stage_instance_id: int) -> Optional[StageInstance]:
+ """Returns a stage instance with the given ID.
+
+ .. versionadded:: 2.0
+
+ Parameters
+ -----------
+ stage_instance_id: :class:`int`
+ The ID to search for.
+
+ Returns
+ --------
+ Optional[:class:`StageInstance`]
+ The stage instance or ``None`` if not found.
+ """
+ return self._stage_instances.get(stage_instance_id)
+
+ @property
def owner(self):
"""Optional[:class:`Member`]: The member that owns the guild."""
return self.get_member(self.owner_id)
@@ -1801,7 +1833,7 @@ class Guild(Hashable):
The list of integrations that are attached to the guild.
"""
data = await self._state.http.get_all_integrations(self.id)
-
+
def convert(d):
factory, _ = _integration_factory(d['type'])
if factory is None:
diff --git a/discord/http.py b/discord/http.py
index c14cc8d4..4dfea5da 100644
--- a/discord/http.py
+++ b/discord/http.py
@@ -44,7 +44,9 @@ if TYPE_CHECKING:
from .types import (
interactions,
invite,
+ stage_instance,
)
+ from .types.snowflake import Snowflake
T = TypeVar('T')
Response = Coroutine[Any, Any, T]
@@ -1080,6 +1082,33 @@ class HTTPClient:
def move_member(self, user_id, guild_id, channel_id, *, reason=None):
return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason)
+ # Stage instance management
+
+ def get_stage_instance(self, channel_id: Snowflake) -> Response[stage_instance.StageInstance]:
+ return self.request(Route('GET', '/stage-instances/{channel_id}', channel_id=channel_id))
+
+ def create_stage_instance(self, **payload) -> Response[stage_instance.StageInstance]:
+ valid_keys = (
+ 'channel_id',
+ 'topic',
+ 'privacy_level',
+ )
+ payload = {k: v for k, v in payload.items() if k in valid_keys}
+
+ return self.request(Route('POST', '/stage-instances'), json=payload)
+
+ def edit_stage_instance(self, channel_id: Snowflake, **payload) -> Response[None]:
+ valid_keys = (
+ 'topic',
+ 'privacy_level',
+ )
+ payload = {k: v for k, v in payload.items() if k in valid_keys}
+
+ return self.request(Route('PATCH', '/stage-instances/{channel_id}', channel_id=channel_id), json=payload)
+
+ def delete_stage_instance(self, channel_id: Snowflake) -> Response[None]:
+ return self.request(Route('DELETE', '/stage-instances/{channel_id}', channel_id=channel_id))
+
# Application commands (global)
def get_global_commands(self, application_id) -> Response[List[interactions.ApplicationCommand]]:
diff --git a/discord/stage_instance.py b/discord/stage_instance.py
new file mode 100644
index 00000000..a6479d75
--- /dev/null
+++ b/discord/stage_instance.py
@@ -0,0 +1,168 @@
+"""
+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 typing import Optional, TYPE_CHECKING
+
+from .utils import MISSING, cached_slot_property
+from .mixins import Hashable
+from .errors import InvalidArgument
+from .enums import StagePrivacyLevel, try_enum
+
+__all__ = (
+ 'StageInstance',
+)
+
+if TYPE_CHECKING:
+ from .types.channel import StageInstance as StageInstancePayload
+ from .state import ConnectionState
+ from .channel import StageChannel
+ from .guild import Guild
+
+
+class StageInstance(Hashable):
+ """Represents a stage instance of a stage channel in a guild.
+
+ .. versionadded:: 2.0
+
+ .. container:: operations
+
+ .. describe:: x == y
+
+ Checks if two stagea instances are equal.
+
+ .. describe:: x != y
+
+ Checks if two stage instances are not equal.
+
+ .. describe:: hash(x)
+
+ Returns the stage instance's hash.
+
+ Attributes
+ -----------
+ id: :class:`int`
+ The stage instance's ID.
+ guild: :class:`Guild`
+ The guild that the stage instance is running in.
+ channel_id: :class:`int`
+ The ID of the channel that the stage instance is running in.
+ topic: :class:`str`
+ The topic of the stage instance.
+ privacy_level: :class:`StagePrivacyLevel`
+ The privacy level of the stage instance.
+ discoverable_disabled: :class:`bool`
+ Whether the stage instance is discoverable.
+ """
+
+ __slots__ = (
+ '_state',
+ 'id',
+ 'guild',
+ 'channel_id',
+ 'topic',
+ 'privacy_level',
+ 'discoverable_disabled',
+ '_cs_channel',
+ )
+
+ def __init__(self, *, state: ConnectionState, guild: Guild, data: StageInstancePayload) -> None:
+ self._state = state
+ self.guild = guild
+ self._update(data)
+
+ def _update(self, data: StageInstancePayload):
+ self.id: int = int(data['id'])
+ self.channel_id: int = int(data['channel_id'])
+ self.topic: str = data['topic']
+ self.privacy_level = try_enum(StagePrivacyLevel, data['privacy_level'])
+ self.discoverable_disabled = data['discoverable_disabled']
+
+ def __repr__(self) -> str:
+ return f'<StageInstance id={self.id} guild={self.guild!r} channel_id={self.channel_id} topic={self.topic!r}>'
+
+ @cached_slot_property('_cs_channel')
+ def channel(self) -> Optional[StageChannel]:
+ """Optional[:class:`StageChannel`: The guild that stage instance is running in."""
+ return self._state.get_channel(self.channel_id)
+
+ def is_public(self) -> bool:
+ return self.privacy_level is StagePrivacyLevel.public
+
+ async def edit(self, *, topic: str = MISSING, privacy_level: StagePrivacyLevel = MISSING) -> None:
+ """|coro|
+
+ Edits the stage instance.
+
+ You must have the :attr:`~Permissions.manage_channels` permission to
+ use this.
+
+ Parameters
+ -----------
+ topic: :class:`str`
+ The stage instance's new topic.
+ privacy_level: :class:`StagePrivacyLevel`
+ The stage instance's new privacy level.
+
+ Raises
+ ------
+ InvalidArgument
+ If the ``privacy_level`` parameter is not the proper type.
+ Forbidden
+ You do not have permissions to edit the stage instance.
+ HTTPException
+ Editing a stage instance failed.
+ """
+
+ payload = {}
+
+ if topic is not MISSING:
+ payload['topic'] = topic
+
+ if privacy_level is not MISSING:
+ if not isinstance(privacy_level, StagePrivacyLevel):
+ raise InvalidArgument('privacy_level field must be of type PrivacyLevel')
+
+ payload['privacy_level'] = privacy_level.value
+
+ if payload:
+ await self._state.http.edit_stage_instance(self.channel_id, **payload)
+
+ async def delete(self) -> None:
+ """|coro|
+
+ Deletes the stage instance.
+
+ You must have the :attr:`~Permissions.manage_channels` permission to
+ use this.
+
+ Raises
+ ------
+ Forbidden
+ You do not have permissions to delete the stage instance.
+ HTTPException
+ Deleting the stage instance failed.
+ """
+ await self._state.http.delete_stage_instance(self.channel_id)
diff --git a/discord/state.py b/discord/state.py
index fec75887..177c195c 100644
--- a/discord/state.py
+++ b/discord/state.py
@@ -53,6 +53,7 @@ from .object import Object
from .invite import Invite
from .interactions import Interaction
from .ui.view import ViewStore
+from .stage_instance import StageInstance
class ChunkRequest:
def __init__(self, guild_id, loop, resolver, *, cache=True):
@@ -956,6 +957,40 @@ class ConnectionState:
else:
log.debug('WEBHOOKS_UPDATE referencing an unknown channel ID: %s. Discarding.', data['channel_id'])
+ def parse_stage_instance_create(self, data):
+ guild = self._get_guild(int(data['guild_id']))
+ if guild is not None:
+ stage_instance = StageInstance(guild=guild, state=self, data=data)
+ guild._stage_instances[stage_instance.id] = stage_instance
+ self.dispatch('stage_instance_create', stage_instance)
+ else:
+ log.debug('STAGE_INSTANCE_CREATE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
+
+ def parse_stage_instance_update(self, data):
+ guild = self._get_guild(int(data['guild_id']))
+ if guild is not None:
+ stage_instance = guild._stage_instances.get(int(data['id']))
+ if stage_instance is not None:
+ old_stage_instance = copy.copy(stage_instance)
+ stage_instance._update(data)
+ self.dispatch('stage_instance_update', old_stage_instance, stage_instance)
+ else:
+ log.debug('STAGE_INSTANCE_UPDATE referencing unknown stage instance ID: %s. Discarding.', data['id'])
+ else:
+ log.debug('STAGE_INSTANCE_UPDATE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
+
+ def parse_stage_instance_delete(self, data):
+ guild = self._get_guild(int(data['guild_id']))
+ if guild is not None:
+ try:
+ stage_instance = guild._stage_instances.pop(int(data['id']))
+ except KeyError:
+ pass
+ else:
+ self.dispatch('stage_instance_delete', stage_instance)
+ else:
+ log.debug('STAGE_INSTANCE_DELETE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
+
def parse_voice_state_update(self, data):
guild = self._get_guild(utils._get_as_snowflake(data, 'guild_id'))
channel_id = utils._get_as_snowflake(data, 'channel_id')
diff --git a/discord/types/channel.py b/discord/types/channel.py
index d9c9f74d..ea3747ab 100644
--- a/discord/types/channel.py
+++ b/discord/types/channel.py
@@ -89,3 +89,15 @@ class DMChannel(PartialChannel):
class GroupDMChannel(DMChannel):
icon: Optional[str]
owner_id: Snowflake
+
+
+PrivacyLevel = Literal[1, 2]
+
+
+class StageInstance(TypedDict):
+ id: Snowflake
+ guild_id: Snowflake
+ channel_id: Snowflake
+ topic: str
+ privacy_level: PrivacyLevel
+ discoverable_disabled: bool
diff --git a/docs/api.rst b/docs/api.rst
index 27604ae4..ff6b1375 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -835,6 +835,32 @@ to handle it, which defaults to print a traceback and ignoring the exception.
:param after: The voice state after the changes.
:type after: :class:`VoiceState`
+.. function:: on_stage_instance_create(stage_instance)
+ on_stage_instance_delete(stage_instance)
+
+ Called when a :class:`StageInstance` is created or deleted for a :class:`StageChannel`.
+
+ .. versionadded:: 2.0
+
+ :param stage_instance: The stage instance that was created or deleted.
+ :type stage_instance: :class:`StageInstance`
+
+.. function:: on_stage_instance_update(before, after)
+
+ Called when a :class:`StageInstance` is updated.
+
+ The following, but not limited to, examples illustrate when this event is called:
+
+ - The topic is changed.
+ - The privacy level is changed.
+
+ .. versionadded:: 2.0
+
+ :param before: The stage instance before the update.
+ :type before: :class:`StageInstance`
+ :param after: The stage instance after the update.
+ :type after: :class:`StageInstance`
+
.. function:: on_member_ban(guild, user)
Called when user gets banned from a :class:`Guild`.
@@ -2120,6 +2146,23 @@ of :class:`enum.Enum`.
Represents full camera video quality.
+.. class:: PrivacyLevel
+
+ Represents a stage instance's privacy level.
+
+ .. 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
+
+ Alias for :attr:`.closed`
Async Iterator
----------------
@@ -3126,6 +3169,15 @@ StageChannel
:members:
:inherited-members:
+
+StageInstance
+~~~~~~~~~~~~~~
+
+.. attributetable:: StageInstance
+
+.. autoclass:: StageInstance()
+ :members:
+
CategoryChannel
~~~~~~~~~~~~~~~~~