diff --git a/discord/__init__.py b/discord/__init__.py index 48fe10925..c13675e34 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -73,6 +73,7 @@ from .poll import * from .soundboard import * from .subscription import * from .presences import * +from .onboarding import * class VersionInfo(NamedTuple): diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 89577769f..fdc22c26b 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -44,6 +44,7 @@ from .sticker import GuildSticker from .threads import Thread from .integrations import PartialIntegration from .channel import ForumChannel, StageChannel, ForumTag +from .onboarding import OnboardingPrompt, OnboardingPromptOption __all__ = ( 'AuditLogDiff', @@ -73,6 +74,7 @@ if TYPE_CHECKING: from .types.snowflake import Snowflake from .types.command import ApplicationCommandPermissions from .types.automod import AutoModerationAction + from .types.onboarding import Prompt as PromptPayload, PromptOption as PromptOptionPayload from .user import User from .app_commands import AppCommand from .webhook import Webhook @@ -246,6 +248,16 @@ def _transform_default_emoji(entry: AuditLogEntry, data: str) -> PartialEmoji: return PartialEmoji(name=data) +def _transform_onboarding_prompts(entry: AuditLogEntry, data: List[PromptPayload]) -> List[OnboardingPrompt]: + return [OnboardingPrompt(data=prompt, state=entry._state, guild=entry.guild) for prompt in data] + + +def _transform_onboarding_prompt_options( + entry: AuditLogEntry, data: List[PromptOptionPayload] +) -> List[OnboardingPromptOption]: + return [OnboardingPromptOption(data=option, state=entry._state, guild=entry.guild) for option in data] + + E = TypeVar('E', bound=enums.Enum) @@ -268,13 +280,15 @@ def _flag_transformer(cls: Type[F]) -> Callable[[AuditLogEntry, Union[int, str]] def _transform_type( entry: AuditLogEntry, data: Union[int, str] -) -> Union[enums.ChannelType, enums.StickerType, enums.WebhookType, str]: +) -> Union[enums.ChannelType, enums.StickerType, enums.WebhookType, str, enums.OnboardingPromptType]: if entry.action.name.startswith('sticker_'): return enums.try_enum(enums.StickerType, data) elif entry.action.name.startswith('integration_'): return data # type: ignore # integration type is str elif entry.action.name.startswith('webhook_'): return enums.try_enum(enums.WebhookType, data) + elif entry.action.name.startswith('onboarding_question_'): + return enums.try_enum(enums.OnboardingPromptType, data) else: return enums.try_enum(enums.ChannelType, data) @@ -353,7 +367,10 @@ class AuditLogChanges: 'flags': (None, _transform_overloaded_flags), 'default_reaction_emoji': (None, _transform_default_reaction), 'emoji_name': ('emoji', _transform_default_emoji), - 'user_id': ('user', _transform_member_id) + 'user_id': ('user', _transform_member_id), + 'options': (None, _transform_onboarding_prompt_options), + 'prompts': (None, _transform_onboarding_prompts), + 'default_channel_ids': ('default_channels', _transform_channels_or_threads), } # fmt: on diff --git a/discord/enums.py b/discord/enums.py index 71f755c12..677eaa919 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -77,6 +77,8 @@ __all__ = ( 'VoiceChannelEffectAnimationType', 'SubscriptionStatus', 'MessageReferenceType', + 'OnboardingPromptType', + 'OnboardingMode', ) @@ -399,6 +401,11 @@ class AuditLogAction(Enum): automod_timeout_member = 145 creator_monetization_request_created = 150 creator_monetization_terms_accepted = 151 + onboarding_question_create = 163 + onboarding_question_update = 164 + onboarding_update = 167 + server_guide_create = 190 + server_guide_update = 191 # fmt: on @property @@ -464,6 +471,10 @@ class AuditLogAction(Enum): AuditLogAction.soundboard_sound_create: AuditLogActionCategory.create, AuditLogAction.soundboard_sound_update: AuditLogActionCategory.update, AuditLogAction.soundboard_sound_delete: AuditLogActionCategory.delete, + AuditLogAction.onboarding_question_create: AuditLogActionCategory.create, + AuditLogAction.onboarding_question_update: AuditLogActionCategory.update, + AuditLogAction.onboarding_update: AuditLogActionCategory.update, + } # fmt: on return lookup[self] @@ -509,6 +520,10 @@ class AuditLogAction(Enum): return 'user' elif v < 152: return 'creator_monetization' + elif v < 165: + return 'onboarding_question' + elif v < 168: + return 'onboarding' class UserFlags(Enum): @@ -864,6 +879,16 @@ class SubscriptionStatus(Enum): inactive = 2 +class OnboardingPromptType(Enum): + multiple_choice = 0 + dropdown = 1 + + +class OnboardingMode(Enum): + default = 0 + advanced = 1 + + def create_unknown_value(cls: Type[E], val: Any) -> E: value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below name = f'unknown_{val}' diff --git a/discord/guild.py b/discord/guild.py index 6b8e8814e..5834209bd 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -76,6 +76,7 @@ from .enums import ( AutoModRuleEventType, ForumOrderType, ForumLayoutType, + OnboardingMode, ) from .mixins import Hashable from .user import User @@ -91,6 +92,7 @@ from .sticker import GuildSticker from .file import File from .audit_logs import AuditLogEntry from .object import OLDEST_OBJECT, Object +from .onboarding import Onboarding, PartialOnboardingPrompt from .welcome_screen import WelcomeScreen, WelcomeChannel from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction from .partial_emoji import _EmojiTag, PartialEmoji @@ -4823,3 +4825,68 @@ class Guild(Hashable): data = await self._state.http.create_soundboard_sound(self.id, reason=reason, **payload) return SoundboardSound(guild=self, state=self._state, data=data) + + async def onboarding(self) -> Onboarding: + """|coro| + + Fetches the onboarding configuration for this guild. + + .. versionadded:: 2.4 + + Returns + -------- + :class:`Onboarding` + The onboarding configuration that was fetched. + """ + data = await self._state.http.get_guild_onboarding(self.id) + return Onboarding(data=data, guild=self, state=self._state) + + async def edit_onboarding( + self, + *, + prompts: List[PartialOnboardingPrompt] = MISSING, + default_channels: List[Snowflake] = MISSING, + enabled: bool = MISSING, + mode: OnboardingMode = MISSING, + reason: str = MISSING, + ) -> Onboarding: + """|coro| + + Edits the onboarding configuration for this guild. + + .. versionadded:: 2.4 + + Parameters + ----------- + prompts: List[:class:`PartialOnboardingPrompt`] + The prompts that will be shown to new members. + default_channels: List[:class:`abc.Snowflake`] + The channels that will be used as the default channels for new members. + enabled: :class:`bool` + Whether the onboarding configuration is enabled. + mode: :class:`OnboardingMode` + The mode that will be used for the onboarding configuration. + reason: :class:`str` + The reason for editing the onboarding configuration. Shows up on the audit log. + + Raises + ------- + Forbidden + You do not have permissions to edit the onboarding configuration. + HTTPException + Editing the onboarding configuration failed. + + Returns + -------- + :class:`Onboarding` + The new onboarding configuration. + """ + data = await self._state.http.edit_guild_onboarding( + self.id, + prompts=[p.to_dict(id=i) for i, p in enumerate(prompts)] if prompts is not MISSING else None, + default_channel_ids=[c.id for c in default_channels] if default_channels is not MISSING else None, + enabled=enabled if enabled is not MISSING else None, + mode=mode.value if mode is not MISSING else None, + reason=reason if reason is not MISSING else None, + ) + return Onboarding(data=data, guild=self, state=self._state) diff --git a/discord/http.py b/discord/http.py index 71912f71b..87eadb19e 100644 --- a/discord/http.py +++ b/discord/http.py @@ -81,6 +81,7 @@ if TYPE_CHECKING: invite, member, message, + onboarding, template, role, user, @@ -2541,6 +2542,42 @@ class HTTPClient: ), ) + # Guild Onboarding + + def get_guild_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]: + return self.request(Route('GET', '/guilds/{guild_id}/onboarding', guild_id=guild_id)) + + def edit_guild_onboarding( + self, + guild_id: Snowflake, + *, + prompts: Optional[List[onboarding.Prompt]] = None, + default_channel_ids: Optional[List[Snowflake]] = None, + enabled: Optional[bool] = None, + mode: Optional[onboarding.OnboardingMode] = None, + reason: Optional[str], + ) -> Response[onboarding.Onboarding]: + + payload = {} + + if prompts is not None: + payload['prompts'] = prompts + + if default_channel_ids is not None: + payload['default_channel_ids'] = default_channel_ids + + if enabled is not None: + payload['enabled'] = enabled + + if mode is not None: + payload['mode'] = mode + + return self.request( + Route('PUT', f'/guilds/{guild_id}/onboarding', guild_id=guild_id), + json=payload, + reason=reason, + ) + # Soundboard def get_soundboard_default_sounds(self) -> Response[List[soundboard.SoundboardDefaultSound]]: diff --git a/discord/onboarding.py b/discord/onboarding.py index 0b6dc841a..5b5c59e0b 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -22,238 +22,326 @@ 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 TYPE_CHECKING, Dict, List, Union +import os +from typing import TYPE_CHECKING, Iterable, Optional, Set, List, Union + +from discord.utils import MISSING + +from . import utils from .mixins import Hashable -from .object import Object -from .role import Role -from .enums import OnboardingPromptType, try_enum -from .partial_emoji import PartialEmoji -from .utils import SequenceProxy +from .enums import OnboardingMode, OnboardingPromptType, try_enum +from .utils import cached_slot_property, MISSING + +__all__ = ( + 'Onboarding', + 'PartialOnboardingPrompt', + 'OnboardingPrompt', + 'OnboardingPromptOption', + 'PartialOnboardingPromptOption', +) + if TYPE_CHECKING: - from .abc import GuildChannel, PartialMessageable + from .abc import GuildChannel + from .emoji import Emoji from .guild import Guild + from .partial_emoji import PartialEmoji + from .role import Role from .threads import Thread from .types.onboarding import ( + Prompt as PromptPayload, + PromptOption as PromptOptionPayload, Onboarding as OnboardingPayload, - OnboardingPrompt as OnboardingPromptPayload, - OnboardingPromptOption as OnboardingPromptOptionPayload, ) + from .state import ConnectionState -class OnboardingPromptOption(Hashable): - """Represents a guild's onboarding prompt's option. - - .. container:: operations - - .. describe:: x == y - - Checks if two guilds are equal. +class PartialOnboardingPromptOption: + """Represents a partial onboarding prompt option, these are used in the creation + of an :class:`OnboardingPrompt` via :meth:`Guild.edit_onboarding`. - .. describe:: x != y + .. versionadded:: 2.4 - Checks if two guilds are not equal. - - .. describe:: hash(x) - - Returns the guild's hash. - - .. describe:: str(x) + Attributes + ----------- + title: :class:`str` + The title of this prompt option. + description: Optional[:class:`str`] + The description of this prompt option. + emoji: Optional[Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]] + The emoji tied to this option. May be a custom emoji, or a unicode emoji. + channel_ids: Set[:class:`int`] + The IDs of the channels that will be made visible if this option is selected. + role_ids: Set[:class:`int`] + The IDs of the roles given to the user if this option is selected. + """ - Returns the guild's name. + __slots__ = ( + 'title', + 'emoji', + 'description', + 'channel_ids', + 'role_ids', + ) - .. versionadded:: 2.2 + def __init__( + self, + title: str, + emoji: Union[Emoji, PartialEmoji, str], + description: Optional[str] = None, + channel_ids: Iterable[int] = MISSING, + role_ids: Iterable[int] = MISSING, + ) -> None: + self.title: str = title + self.description: Optional[str] = description + self.emoji: Union[PartialEmoji, Emoji, str] = emoji + self.channel_ids: Set[int] = set(channel_ids or []) + self.role_ids: Set[int] = set(role_ids or []) + + def to_dict(self, *, id: int = MISSING) -> PromptOptionPayload: + if isinstance(self.emoji, str): + emoji_payload = {"emoji_name": self.emoji} + else: + emoji_payload = { + "emoji_id": self.emoji.id, + "emoji_name": self.emoji.name, + "emoji_animated": self.emoji.animated, + } + + return { + 'id': id or os.urandom(16).hex(), + 'title': self.title, + 'description': self.description, + 'channel_ids': list(self.channel_ids), + 'role_ids': list(self.role_ids), + **emoji_payload, + } # type: ignore + + +class OnboardingPromptOption(PartialOnboardingPromptOption, Hashable): + """Represents an onboarding prompt option. + + .. versionadded:: 2.4 Attributes ----------- id: :class:`int` - The ID of the option. + The ID of this prompt option. + guild: :class:`Guild` + The guild the onboarding prompt option is related to. title: :class:`str` - The title of the option. - description: :class:`str` - The description of the option. - emoji: :class:`PartialEmoji` - The emoji of the option. + The title of this prompt option. + description: Optional[:class:`str`] + The description of this prompt option. + emoji: Optional[Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]] + The emoji tied to this option. May be a custom emoji, or a unicode emoji. + channel_ids: Set[:class:`int`] + The IDs of the channels that will be made visible if this option is selected. + role_ids: Set[:class:`int`] + The IDs of the roles given to the user if this option is selected. """ - __slots__ = ('id', 'title', 'description', 'emoji', '_channels', '_roles', '_onboarding') - - def __init__(self, *, onboarding: Onboarding, data: OnboardingPromptOptionPayload) -> None: - self._onboarding: Onboarding = onboarding - - self._channels: Dict[int, Union[GuildChannel, Thread, PartialMessageable, Object]] = {} - self._roles: Dict[int, Union[Role, Object]] = {} - self._from_data(data) - - def _from_data(self, data: OnboardingPromptOptionPayload) -> None: - guild = self._onboarding._guild - state = guild._state + __slots__ = ( + '_state', + '_cs_channels', + '_cs_roles', + 'guild', + 'id', + ) + def __init__(self, *, data: PromptOptionPayload, state: ConnectionState, guild: Guild) -> None: + self._state: ConnectionState = state + self.guild: Guild = guild self.id: int = int(data['id']) - self.title: str = data['title'] - self.description: str = data['description'] - - emoji = PartialEmoji.from_dict(data['emoji']) - emoji._state = state - self.emoji: PartialEmoji = emoji - - channel_ids = data.get('channel_ids', []) - for channel_id in channel_ids: - channel = guild.get_channel_or_thread(int(channel_id)) or state.get_channel(int(channel_id)) - self._channels[int(channel_id)] = channel or Object(id=channel_id) # type: ignore # can't be PrivateChannel - - role_ids = data.get('role_ids', []) - for role_id in role_ids: - role = guild.get_role(int(role_id)) - - self._roles[int(role_id)] = role or Object(id=role_id, type=Role) + super().__init__( + title=data['title'], + description=data['description'], + emoji=self._state.get_emoji_from_partial_payload(data['emoji']), + channel_ids=[int(id) for id in data['channel_ids']], + role_ids=[int(id) for id in data['role_ids']], + ) def __repr__(self) -> str: - return f'' - - @property - def channels(self) -> SequenceProxy[Union[GuildChannel, Thread, PartialMessageable, Object]]: - """List[:class:`Union[GuildChannel, Thread, PartialMessageable, Object]`]: A list of channels that are opted into when this option is selected.""" - return SequenceProxy(self._channels.values()) - - @property - def roles(self) -> SequenceProxy[Union[Role, Object]]: - """List[:class:`Union[Role, Object]`]: A list of roles that are assigned when this option is selected.""" - return SequenceProxy(self._roles.values()) - - -class OnboardingPrompt(Hashable): - """Represents a guild's onboarding prompt. - - .. container:: operations - - .. describe:: x == y + return f'' - Checks if two guilds are equal. + @cached_slot_property('_cs_channels') + def channels(self) -> List[Union[GuildChannel, Thread]]: + """List[Union[:class:`abc.GuildChannel`, :class:`Thread`]]: The list of channels which will be made visible if this option is selected.""" + it = filter(None, map(self.guild._resolve_channel, self.channel_ids)) + return utils._unique(it) - .. describe:: x != y + @cached_slot_property('_cs_roles') + def roles(self) -> List[Role]: + """List[:class:`Role`]: The list of roles given to the user if this option is selected.""" + it = filter(None, map(self.guild.get_role, self.role_ids)) + return utils._unique(it) - Checks if two guilds are not equal. + def to_dict(self) -> PromptOptionPayload: + return super().to_dict(id=self.id) - .. describe:: hash(x) - Returns the guild's hash. +class PartialOnboardingPrompt: + """Represents a partial onboarding prompt, these are used in the creation + of an :class:`Onboarding` via :meth:`Guild.edit_onboarding`. - .. describe:: str(x) + .. versionadded:: 2.4 - Returns the guild's name. + Attributes + ----------- + type: :class:`OnboardingPromptType` + The type of this prompt. + title: :class:`str` + The title of this prompt. + options: List[:class:`PartialOnboardingPromptOption`] + The options of this prompt. + single_select: :class:`bool` + Whether this prompt is single select. + required: :class:`bool` + Whether this prompt is required. + in_onboarding: :class:`bool` + Whether this prompt is in the onboarding flow. + """ - .. versionadded:: 2.2 + __slots__ = ( + 'type', + 'title', + 'options', + 'single_select', + 'required', + 'in_onboarding', + ) + def __init__( + self, + *, + type: OnboardingPromptType, + title: str, + options: List[PartialOnboardingPromptOption], + single_select: bool = True, + required: bool = True, + in_onboarding: bool = True, + ) -> None: + self.type: OnboardingPromptType = try_enum(OnboardingPromptType, type) + self.title: str = title + self.options: List[PartialOnboardingPromptOption] = options + self.single_select: bool = single_select + self.required: bool = required + self.in_onboarding: bool = in_onboarding + + def to_dict(self, *, id: int) -> PromptPayload: + return { + 'id': id, + 'type': self.type.value, + 'title': self.title, + 'options': [option.to_dict() for option in self.options], + 'single_select': self.single_select, + 'required': self.required, + 'in_onboarding': self.in_onboarding, + } + + +class OnboardingPrompt(PartialOnboardingPrompt, Hashable): + """Represents an onboarding prompt. + + .. versionadded:: 2.4 Attributes ----------- id: :class:`int` - The ID of the prompt. + The ID of this prompt. + guild: :class:`Guild` + The guild the onboarding prompt is related to. + type: :class:`OnboardingPromptType` + The type of onboarding prompt. title: :class:`str` - The title of the prompt. + The title of this prompt. + options: List[:class:`OnboardingPromptOption`] + The list of options the user can select from. single_select: :class:`bool` - Whether only one option can be selected at a time. + Whether only one option can be selected. required: :class:`bool` - Whether the prompt is required in the onboarding flow. + Whether this prompt is required to complete the onboarding flow. in_onboarding: :class:`bool` - Whether the prompt is in the onboarding flow. + Whether this prompt is part of the onboarding flow. """ - __slots__ = ('id', 'title', 'single_select', 'required', 'in_onboarding', '_oboarding', '_options', '_type') - - def __init__(self, *, onboarding: Onboarding, data: OnboardingPromptPayload) -> None: - self._oboarding: Onboarding = onboarding - self._from_data(data) + options: List[OnboardingPromptOption] + + __slots__ = ( + '_state', + 'guild', + 'id', + 'title', + 'options', + 'single_select', + 'required', + 'in_onboarding', + 'type', + ) - def _from_data(self, data: OnboardingPromptPayload) -> None: + def __init__(self, *, data: PromptPayload, state: ConnectionState, guild: Guild): + self._state: ConnectionState = state + self.guild: Guild = guild self.id: int = int(data['id']) - self.title: str = data['title'] - self.single_select: bool = data['single_select'] - self.required: bool = data['required'] - self._type: OnboardingPromptType = try_enum(OnboardingPromptType, data['type']) - self.in_onboarding: bool = data['in_onboarding'] - self._options: List[OnboardingPromptOption] = [ - OnboardingPromptOption(onboarding=self._oboarding, data=option) for option in data['options'] - ] + super().__init__( + type=try_enum(OnboardingPromptType, data['type']), + title=data['title'], + options=[OnboardingPromptOption(data=option_data, state=state, guild=guild) for option_data in data['options']], + single_select=data['single_select'], + required=data['required'], + in_onboarding=data['in_onboarding'], + ) def __repr__(self) -> str: - return f'' - - @property - def type(self) -> OnboardingPromptType: - """Optional[:class:`OnboardingPromptType`]: The type of the prompt.""" - return self._type - - @property - def options(self) -> SequenceProxy[OnboardingPromptOption]: - """List[:class:`OnboardingPromptOption`]: The options available to the prompt.""" - return SequenceProxy(self._options) + return f'' class Onboarding: - """Represents a guild's onboarding. - - .. container:: operations - - .. describe:: x == y + """Represents a guild's onboarding configuration. - Checks if two guilds are equal. - - .. describe:: x != y - - Checks if two guilds are not equal. - - .. describe:: hash(x) - - Returns the guild's hash. - - .. describe:: str(x) - - Returns the guild's name. - - .. versionadded:: 2.2 + .. versionadded:: 2.4 Attributes ----------- - enabled: :class:`bool` - Whether guild onboarding is enabled. + guild: :class:`Guild` + The guild the onboarding configuration is for. + prompts: List[:class:`OnboardingPrompt`] + The list of prompts shown during the onboarding and customize community flows. + default_channel_ids: Set[:class:`int`] + The IDs of the channels exposed to a new user by default. + enabled: :class:`bool`: + Whether onboarding is enabled in this guild. + mode: :class:`OnboardingMode` + The mode of onboarding for this guild. """ - __slots__ = ('enabled', '_guild', '_default_channel_ids', '_default_channels', '_prompts', '_guild_id') - - def __init__(self, *, guild: Guild, data: OnboardingPayload) -> None: - self._guild = guild - - self._default_channels: Dict[int, Union[GuildChannel, Thread, PartialMessageable, Object]] = {} - self._from_data(data) - - def _from_data(self, data: OnboardingPayload) -> None: - guild = self._guild - state = guild._state + __slots__ = ( + '_state', + '_cs_default_channels', + 'guild', + 'prompts', + 'default_channel_ids', + 'enabled', + 'mode', + ) + def __init__(self, *, data: OnboardingPayload, guild: Guild, state: ConnectionState) -> None: + self._state: ConnectionState = state + self.guild: Guild = guild + self.default_channel_ids: Set[int] = {int(channel_id) for channel_id in data['default_channel_ids']} + self.prompts: List[OnboardingPrompt] = [ + OnboardingPrompt(data=prompt_data, state=state, guild=guild) for prompt_data in data['prompts'] + ] self.enabled: bool = data['enabled'] - self._guild_id: int = int(data['guild_id']) - - prompts = data.get('prompts', []) - self._prompts: List[OnboardingPrompt] = [OnboardingPrompt(onboarding=self, data=prompt) for prompt in prompts] - default_channel_ids = data.get('default_channel_ids', []) - for channel_id in default_channel_ids: - channel = guild.get_channel_or_thread(int(channel_id)) or state.get_channel(int(channel_id)) - self._default_channels[int(channel_id)] = channel or Object(id=channel_id) # type: ignore # can't be a private channel + self.mode: OnboardingMode = try_enum(OnboardingMode, data.get('mode', 0)) def __repr__(self) -> str: - return f'' - - @property - def default_channels(self) -> SequenceProxy[Union[GuildChannel, Thread, PartialMessageable, Object]]: - """List[Union[:class:`GuildChannel`, :class:`Thread`, :class:`Object`]: The channels that new members get opted into automatically.""" - return SequenceProxy(self._default_channels.values()) + return f'' - @property - def prompts(self) -> SequenceProxy[OnboardingPrompt]: - """List[:class:`GuildOnboardingPrompt`]: The prompts shown during onboarding and in costomize community.""" - return SequenceProxy(self._prompts) + @cached_slot_property('_cs_default_channels') + def default_channels(self) -> List[Union[GuildChannel, Thread]]: + """List[Union[:class:`abc.GuildChannel`, :class:`Thread`]]: The list of channels exposed to a new user by default.""" + it = filter(None, map(self.guild._resolve_channel, self.default_channel_ids)) + return utils._unique(it) diff --git a/discord/reaction.py b/discord/reaction.py index 9fd933b0a..060447e13 100644 --- a/discord/reaction.py +++ b/discord/reaction.py @@ -102,7 +102,7 @@ class Reaction: def __init__(self, *, message: Message, data: ReactionPayload, emoji: Optional[Union[PartialEmoji, Emoji, str]] = None): self.message: Message = message - self.emoji: Union[PartialEmoji, Emoji, str] = emoji or message._state.get_reaction_emoji(data['emoji']) + self.emoji: Union[PartialEmoji, Emoji, str] = emoji or message._state.get_emoji_from_partial_payload(data['emoji']) self.count: int = data.get('count', 1) self.me: bool = data['me'] details = data.get('count_details', {}) diff --git a/discord/state.py b/discord/state.py index 0fbeadea2..097f5b353 100644 --- a/discord/state.py +++ b/discord/state.py @@ -1786,7 +1786,7 @@ class ConnectionState(Generic[ClientT]): return channel.guild.get_member(user_id) return self.get_user(user_id) - def get_reaction_emoji(self, data: PartialEmojiPayload) -> Union[Emoji, PartialEmoji, str]: + def get_emoji_from_partial_payload(self, data: PartialEmojiPayload) -> Union[Emoji, PartialEmoji, str]: emoji_id = utils._get_as_snowflake(data, 'id') if not emoji_id: diff --git a/discord/types/audit_log.py b/discord/types/audit_log.py index 2c37542fd..135a24518 100644 --- a/discord/types/audit_log.py +++ b/discord/types/audit_log.py @@ -38,6 +38,7 @@ from .channel import ChannelType, DefaultReaction, PrivacyLevel, VideoQualityMod from .threads import Thread from .command import ApplicationCommand, ApplicationCommandPermissions from .automod import AutoModerationTriggerMetadata +from .onboarding import PromptOption, Prompt AuditLogEvent = Literal[ 1, @@ -99,6 +100,9 @@ AuditLogEvent = Literal[ 145, 150, 151, + 163, + 164, + 167, ] @@ -116,6 +120,7 @@ class _AuditLogChange_Str(TypedDict): 'tags', 'unicode_emoji', 'emoji_name', + 'title', ] new_value: str old_value: str @@ -163,6 +168,10 @@ class _AuditLogChange_Bool(TypedDict): 'available', 'archived', 'locked', + 'enabled', + 'single_select', + 'required', + 'in_onboarding', ] new_value: bool old_value: bool @@ -273,8 +282,8 @@ class _AuditLogChange_AppCommandPermissions(TypedDict): old_value: ApplicationCommandPermissions -class _AuditLogChange_AppliedTags(TypedDict): - key: Literal['applied_tags'] +class _AuditLogChange_SnowflakeList(TypedDict): + key: Literal['applied_tags', 'default_channel_ids'] new_value: List[Snowflake] old_value: List[Snowflake] @@ -297,6 +306,18 @@ class _AuditLogChange_TriggerMetadata(TypedDict): old_value: Optional[AutoModerationTriggerMetadata] +class _AuditLogChange_Prompts(TypedDict): + key: Literal['prompts'] + new_value: List[Prompt] + old_value: List[Prompt] + + +class _AuditLogChange_Options(TypedDict): + key: Literal['options'] + new_value: List[PromptOption] + old_value: List[PromptOption] + + AuditLogChange = Union[ _AuditLogChange_Str, _AuditLogChange_AssetHash, @@ -317,10 +338,12 @@ AuditLogChange = Union[ _AuditLogChange_Status, _AuditLogChange_EntityType, _AuditLogChange_AppCommandPermissions, - _AuditLogChange_AppliedTags, + _AuditLogChange_SnowflakeList, _AuditLogChange_AvailableTags, _AuditLogChange_DefaultReactionEmoji, _AuditLogChange_TriggerMetadata, + _AuditLogChange_Prompts, + _AuditLogChange_Options, ] diff --git a/discord/types/onboarding.py b/discord/types/onboarding.py index 686422191..64fa199c4 100644 --- a/discord/types/onboarding.py +++ b/discord/types/onboarding.py @@ -24,37 +24,38 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import List, Literal, TypedDict, TYPE_CHECKING -from .snowflake import Snowflake +from typing import Literal, Optional, TypedDict -if TYPE_CHECKING: - from .emoji import PartialEmoji +from .emoji import PartialEmoji +from .snowflake import Snowflake -OnboardingPromptType = Literal[0, 1] +PromptType = Literal[0, 1] +OnboardingMode = Literal[0, 1] -class OnboardingPromptOption(TypedDict): +class PromptOption(TypedDict): id: Snowflake - channel_ids: List[Snowflake] - role_ids: List[Snowflake] + channel_ids: list[Snowflake] + role_ids: list[Snowflake] emoji: PartialEmoji title: str - description: str + description: Optional[str] -class OnboardingPrompt(TypedDict): +class Prompt(TypedDict): id: Snowflake - options: List[OnboardingPromptOption] + options: list[PromptOption] title: str single_select: bool required: bool in_onboarding: bool - type: OnboardingPromptType + type: PromptType class Onboarding(TypedDict): guild_id: Snowflake - prompts: List[OnboardingPrompt] - default_channel_ids: List[Snowflake] + prompts: list[Prompt] + default_channel_ids: list[Snowflake] enabled: bool + mode: OnboardingMode diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 3b62b10fa..665d704ab 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -755,7 +755,7 @@ class _WebhookState: def get_reaction_emoji(self, data: PartialEmojiPayload) -> Union[PartialEmoji, Emoji, str]: if self._parent is not None: - return self._parent.get_reaction_emoji(data) + return self._parent.get_emoji_from_partial_payload(data) emoji_id = utils._get_as_snowflake(data, 'id') diff --git a/docs/api.rst b/docs/api.rst index dc6775ec6..4aca17c34 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3097,6 +3097,48 @@ of :class:`enum.Enum`. .. versionadded:: 2.5 + .. attribute:: onboarding_question_create + + A guild onboarding prompt was created. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.type` + - :attr:`~AuditLogDiff.title` + - :attr:`~AuditLogDiff.options` + - :attr:`~AuditLogDiff.single_select` + - :attr:`~AuditLogDiff.required` + - :attr:`~AuditLogDiff.in_onboarding` + + .. versionadded:: 2.4 + + .. attribute:: onboarding_question_update + + A guild onboarding prompt was updated. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.type` + - :attr:`~AuditLogDiff.title` + - :attr:`~AuditLogDiff.options` + - :attr:`~AuditLogDiff.single_select` + - :attr:`~AuditLogDiff.required` + - :attr:`~AuditLogDiff.in_onboarding` + + .. versionadded:: 2.4 + + .. attribute:: onboarding_update + + The guild's onboarding configuration was updated. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.enabled` + - :attr:`~AuditLogDiff.default_channels` + - :attr:`~AuditLogDiff.prompts` + + .. versionadded:: 2.4 + .. class:: AuditLogActionCategory Represents the category that the :class:`AuditLogAction` belongs to. @@ -3770,110 +3812,34 @@ of :class:`enum.Enum`. The entitlement owner is a user. -.. class:: PollLayoutType +.. class:: OnboardingPromptType - Represents how a poll answers are shown. + Represents the type of onboarding prompt. .. versionadded:: 2.4 - .. attribute:: default - - The default layout. - + .. attribute:: multiple_choice -.. class:: InviteType + Prompt options are multiple choice. - Represents the type of an invite. + .. attribute:: dropdown - .. versionadded:: 2.4 - - .. attribute:: guild + Prompt options are displayed as a drop-down. - The invite is a guild invite. +.. class:: OnboardingMode - .. attribute:: group_dm - - The invite is a group DM invite. - - .. attribute:: friend - - The invite is a friend invite. - - -.. class:: ReactionType - - Represents the type of a reaction. + Represents the onboarding constriant mode. .. versionadded:: 2.4 - .. attribute:: normal - - A normal reaction. - - .. attribute:: burst - - A burst reaction, also known as a "super reaction". - - -.. class:: VoiceChannelEffectAnimationType - - Represents the animation type of a voice channel effect. - - .. versionadded:: 2.5 - - .. attribute:: premium - - A fun animation, sent by a Nitro subscriber. - - .. attribute:: basic - - The standard animation. - - -.. class:: SubscriptionStatus - - Represents the status of an subscription. - - .. versionadded:: 2.5 - - .. attribute:: active - - The subscription is active. - - .. attribute:: ending - - The subscription is active but will not renew. - - .. attribute:: inactive - - The subscription is inactive and not being charged. - - -.. class:: MessageReferenceType - - Represents the type of a message reference. - - .. versionadded:: 2.5 - .. attribute:: default - A standard reference used by message replies (:attr:`MessageType.reply`), - crossposted messaged created by a followed channel integration, and messages of type: - - - :attr:`MessageType.pins_add` - - :attr:`MessageType.channel_follow_add` - - :attr:`MessageType.thread_created` - - :attr:`MessageType.thread_starter_message` - - :attr:`MessageType.poll_result` - - :attr:`MessageType.context_menu_command` + Only default channels count towards onboarding constraints. - .. attribute:: forward + .. attribute:: advanced - A forwarded message. - - .. attribute:: reply + Default channels and questions count towards onboarding constraints. - An alias for :attr:`.default`. .. _discord-api-audit-logs: @@ -4121,9 +4087,9 @@ AuditLogDiff .. attribute:: type - The type of channel, sticker, webhook or integration. + The type of channel, sticker, webhook, integration or onboarding prompt. - :type: Union[:class:`ChannelType`, :class:`StickerType`, :class:`WebhookType`, :class:`str`] + :type: Union[:class:`ChannelType`, :class:`StickerType`, :class:`WebhookType`, :class:`str`, :class:`OnboardingPromptType`] .. attribute:: topic @@ -4478,7 +4444,7 @@ AuditLogDiff .. attribute:: enabled - Whether the automod rule is active or not. + Whether guild onboarding or the automod rule is active or not. :type: :class:`bool` @@ -4608,6 +4574,118 @@ AuditLogDiff :type: :class:`float` + .. attribute:: options + + The onboarding prompt options associated with this onboarding prompt. + + See also :attr:`OnboardingPrompt.options` + + :type: List[:class:`OnboardingPromptOption`] + + .. attribute:: default_channels + + The default channels associated with the onboarding in this guild. + + See also :attr:`Onboarding.default_channels` + + :type: List[:class:`abc.GuildChannel`, :class:`Object`] + + .. attribute:: prompts + + The onboarding prompts associated with the onboarding in this guild. + + See also :attr:`Onboarding.prompts` + + :type: List[:class:`OnboardingPrompt`] + + .. attribute:: title + + The title of the onboarding prompt. + + See also :attr:`OnboardingPrompt.title` + + :type: :class:`str` + + .. attribute:: single_select + + Whether only one prompt option can be selected. + + See also :attr:`OnboardingPrompt.single_select` + + :type: :class:`bool` + + .. attribute:: required + + Whether the onboarding prompt is required to complete the onboarding. + + See also :attr:`OnboardingPrompt.required` + + :type: :class:`bool` + + .. attribute:: in_onboarding + + Whether this prompt is currently part of the onboarding flow. + + See also :attr:`OnboardingPrompt.in_onboarding` + + :type: :class:`bool` + + .. attribute:: options + + The onboarding prompt options associated with this onboarding prompt. + + See also :attr:`OnboardingPrompt.options` + + :type: List[:class:`OnboardingPromptOption`] + + .. attribute:: default_channels + + The default channels associated with the onboarding in this guild. + + See also :attr:`Onboarding.default_channels` + + :type: List[:class:`abc.GuildChannel`, :class:`Object`] + + .. attribute:: prompts + + The onboarding prompts associated with the onboarding in this guild. + + See also :attr:`Onboarding.prompts` + + :type: List[:class:`OnboardingPrompt`] + + .. attribute:: title + + The title of the onboarding prompt. + + See also :attr:`OnboardingPrompt.title` + + :type: :class:`str` + + .. attribute:: single_select + + Whether only one prompt option can be selected. + + See also :attr:`OnboardingPrompt.single_select` + + :type: :class:`bool` + + .. attribute:: required + + Whether the onboarding prompt is required to complete the onboarding. + + See also :attr:`OnboardingPrompt.required` + + :type: :class:`bool` + + .. attribute:: in_onboarding + + Whether this prompt is currently part of the onboarding flow. + + See also :attr:`OnboardingPrompt.in_onboarding` + + :type: :class:`bool` + .. this is currently missing the following keys: reason and application_id I'm not sure how to port these @@ -5231,6 +5309,46 @@ GuildSticker .. autoclass:: GuildSticker() :members: +Onboarding +~~~~~~~~~~~ + +.. attributetable:: Onboarding + +.. autoclass:: Onboarding() + :members: + +PartialOnboardingPrompt +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: PartialOnboardingPrompt + +.. autoclass:: PartialOnboardingPrompt + :members: + +OnboardingPrompt +~~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPrompt + +.. autoclass:: OnboardingPrompt() + :members: + +PartialOnboardingPromptOption +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: PartialOnboardingPromptOption + +.. autoclass:: PartialOnboardingPromptOption + :members: + +OnboardingPromptOption +~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPromptOption + +.. autoclass:: OnboardingPromptOption() + :members: + BaseSoundboardSound ~~~~~~~~~~~~~~~~~~~~~~~