From 156e7740c71824b04e6d7af0c3c551d02a81f4c6 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Fri, 24 Feb 2023 22:38:38 +0100 Subject: [PATCH 01/39] Push implementation --- discord/enums.py | 3 + discord/guild.py | 17 +++ discord/http.py | 4 + discord/onboarding.py | 260 ++++++++++++++++++++++++++++++++++++ discord/types/onboarding.py | 60 +++++++++ docs/api.rst | 39 ++++++ 6 files changed, 383 insertions(+) create mode 100644 discord/onboarding.py create mode 100644 discord/types/onboarding.py diff --git a/discord/enums.py b/discord/enums.py index 43ae5b4f7..a08115c2a 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -750,6 +750,9 @@ class ForumLayoutType(Enum): list_view = 1 gallery_view = 2 +class OnboardingPromptType(Enum): + multiple_choice = 0 + dropdown = 1 def create_unknown_value(cls: Type[E], val: Any) -> E: value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below diff --git a/discord/guild.py b/discord/guild.py index 078a7b8be..b6355ceae 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -90,6 +90,7 @@ from .audit_logs import AuditLogEntry from .object import OLDEST_OBJECT, Object from .welcome_screen import WelcomeScreen, WelcomeChannel from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction +from .onboarding import Onboarding __all__ = ( @@ -4026,3 +4027,19 @@ class Guild(Hashable): ) return AutoModRule(data=data, guild=self, state=self._state) + + + async def fetch_onboarding(self) -> Optional[Onboarding]: + """|coro| + + Fetches the onboarding information for this guild. + + .. versionadded:: 2.2 + + Returns + -------- + Optional[:class:`Onboarding`] + The onboarding information for this guild. + """ + data = await self._state.http.get_guild_onboarding(self.id) + return Onboarding(guild=self, data=data) \ No newline at end of file diff --git a/discord/http.py b/discord/http.py index 5913f4911..d89fed131 100644 --- a/discord/http.py +++ b/discord/http.py @@ -90,6 +90,7 @@ if TYPE_CHECKING: scheduled_event, sticker, welcome_screen, + onboarding, ) from .types.snowflake import Snowflake, SnowflakeList @@ -1735,6 +1736,9 @@ class HTTPClient: ) -> Response[widget.WidgetSettings]: return self.request(Route('PATCH', '/guilds/{guild_id}/widget', guild_id=guild_id), json=payload, reason=reason) + def get_guild_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]: + return self.request(Route('GET', '/guilds/{guild_id}/onboarding', guild_id=guild_id)) + # Invite management def create_invite( diff --git a/discord/onboarding.py b/discord/onboarding.py new file mode 100644 index 000000000..8aaf120ff --- /dev/null +++ b/discord/onboarding.py @@ -0,0 +1,260 @@ +""" +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 TYPE_CHECKING, Dict, List, Union + +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 + +if TYPE_CHECKING: + from .abc import GuildChannel, PartialMessageable + from .guild import Guild + from .threads import Thread + from .types.onboarding import ( + Onboarding as OnboardingPayload, + OnboardingPrompt as OnboardingPromptPayload, + OnboardingPromptOption as OnboardingPromptOptionPayload, + ) + + + +class OnboardingPromptOption(Hashable): + """Represents a guild's onboarding prompt's option. + + .. container:: operations + + .. describe:: x == y + + 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 + + Attributes + ----------- + id: :class:`int` + The ID of the option. + title: :class:`str` + The title of the option. + description: :class:`str` + The description of the option. + emoji: :class:`PartialEmoji` + The emoji of the option. + """ + __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 + + 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) + + 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 + + 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 + + + Attributes + ----------- + id: :class:`int` + The ID of the prompt. + title: :class:`str` + The title of the prompt. + single_select: :class:`bool` + Whether only one option can be selected at a time. + required: :class:`bool` + Whether the prompt is required in the onboarding flow. + in_onboarding: :class:`bool` + Whether the prompt is in 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) + + def _from_data(self, data: OnboardingPromptPayload) -> None: + 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'] + ] + + 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) + + +class Onboarding: + """Represents a guild's onboarding. + + .. container:: operations + + .. describe:: x == y + + 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 + + Attributes + ----------- + enabled: :class:`bool` + Whether guild onboarding is enabled. + """ + + __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 + + 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 + + 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()) + + @property + def prompts(self) -> SequenceProxy[OnboardingPrompt]: + """List[:class:`GuildOnboardingPrompt`]: The prompts shown during onboarding and in costomize community.""" + return SequenceProxy(self._prompts) diff --git a/discord/types/onboarding.py b/discord/types/onboarding.py new file mode 100644 index 000000000..78f4ff4f3 --- /dev/null +++ b/discord/types/onboarding.py @@ -0,0 +1,60 @@ +""" +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 List, Literal, TypedDict, TYPE_CHECKING +from .snowflake import Snowflake + +if TYPE_CHECKING: + from .emoji import PartialEmoji + + +OnboardingPromptType = Literal[0, 1] + + +class OnboardingPromptOption(TypedDict): + id: Snowflake + channel_ids: List[Snowflake] + role_ids: List[Snowflake] + emoji: PartialEmoji + title: str + description: str + + +class OnboardingPrompt(TypedDict): + id: Snowflake + options: List[OnboardingPromptOption] + title: str + single_select: bool + required: bool + in_onboarding: bool + type: OnboardingPromptType + + +class Onboarding(TypedDict): + guild_id: Snowflake + prompts: List[OnboardingPrompt] + default_channel_ids: List[Snowflake] + enabled: bool \ No newline at end of file diff --git a/docs/api.rst b/docs/api.rst index 32e393ef2..253620e06 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3276,6 +3276,20 @@ of :class:`enum.Enum`. Displays posts as a collection of tiles. +.. class:: OnboardingPromptType + + Represents the type of a guild onboarding prompt. + + .. versionadded:: 2.2 + + .. attribute:: multiple_choice + + The prompt will be a multiple choice. + + .. attribute:: dropdown + + The prompt will be a dropdown. + .. _discord-api-audit-logs: @@ -4608,6 +4622,31 @@ PartialWebhookChannel .. autoclass:: PartialWebhookChannel() :members: +Onboarding +~~~~~~~~~~~~ + +.. attributetable:: Onboarding + +.. autoclass:: Onboarding() + :members: + +OnboardingPrompt +~~~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPrompt + +.. autoclass:: OnboardingPrompt() + :members: + +OnboardingPromptOption +~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPromptOption + +.. autoclass:: OnboardingPromptOption() + :members: + + .. _discord_api_data: Data Classes From c3a6fce4f65dcd2ed2740ba30993fb0a397de648 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Fri, 24 Feb 2023 22:41:06 +0100 Subject: [PATCH 02/39] Run black discord\enums.py discord\guild.py discord\onboarding.py discord\types\onboarding.py discord\http.py --- discord/enums.py | 2 ++ discord/guild.py | 3 +-- discord/onboarding.py | 21 ++++++++++----------- discord/types/onboarding.py | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index a08115c2a..96518f42e 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -750,10 +750,12 @@ class ForumLayoutType(Enum): list_view = 1 gallery_view = 2 + class OnboardingPromptType(Enum): multiple_choice = 0 dropdown = 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 b6355ceae..af7f43134 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4028,7 +4028,6 @@ class Guild(Hashable): return AutoModRule(data=data, guild=self, state=self._state) - async def fetch_onboarding(self) -> Optional[Onboarding]: """|coro| @@ -4042,4 +4041,4 @@ class Guild(Hashable): The onboarding information for this guild. """ data = await self._state.http.get_guild_onboarding(self.id) - return Onboarding(guild=self, data=data) \ No newline at end of file + return Onboarding(guild=self, data=data) diff --git a/discord/onboarding.py b/discord/onboarding.py index 8aaf120ff..0b6dc841a 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -44,7 +44,6 @@ if TYPE_CHECKING: ) - class OnboardingPromptOption(Hashable): """Represents a guild's onboarding prompt's option. @@ -65,7 +64,7 @@ class OnboardingPromptOption(Hashable): .. describe:: str(x) Returns the guild's name. - + .. versionadded:: 2.2 Attributes @@ -79,8 +78,9 @@ class OnboardingPromptOption(Hashable): emoji: :class:`PartialEmoji` The emoji of the option. """ + __slots__ = ('id', 'title', 'description', 'emoji', '_channels', '_roles', '_onboarding') - + def __init__(self, *, onboarding: Onboarding, data: OnboardingPromptOptionPayload) -> None: self._onboarding: Onboarding = onboarding @@ -98,7 +98,7 @@ class OnboardingPromptOption(Hashable): emoji = PartialEmoji.from_dict(data['emoji']) emoji._state = state - self.emoji: PartialEmoji = emoji + self.emoji: PartialEmoji = emoji channel_ids = data.get('channel_ids', []) for channel_id in channel_ids: @@ -114,7 +114,6 @@ class OnboardingPromptOption(Hashable): 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.""" @@ -148,8 +147,8 @@ class OnboardingPrompt(Hashable): Returns the guild's name. .. versionadded:: 2.2 - - + + Attributes ----------- id: :class:`int` @@ -163,7 +162,9 @@ class OnboardingPrompt(Hashable): in_onboarding: :class:`bool` Whether the prompt is in 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) @@ -176,7 +177,7 @@ class OnboardingPrompt(Hashable): 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'] + OnboardingPromptOption(onboarding=self._oboarding, data=option) for option in data['options'] ] def __repr__(self) -> str: @@ -238,9 +239,7 @@ class Onboarding: 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 - ] + 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)) diff --git a/discord/types/onboarding.py b/discord/types/onboarding.py index 78f4ff4f3..686422191 100644 --- a/discord/types/onboarding.py +++ b/discord/types/onboarding.py @@ -57,4 +57,4 @@ class Onboarding(TypedDict): guild_id: Snowflake prompts: List[OnboardingPrompt] default_channel_ids: List[Snowflake] - enabled: bool \ No newline at end of file + enabled: bool From 20f14b817433f1ac87401ad7db9c32ed76f336e6 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sat, 25 Feb 2023 16:02:04 +1000 Subject: [PATCH 03/39] Begin implementaton of guild onboarding configuration support --- discord/__init__.py | 1 + discord/enums.py | 6 ++ discord/guild.py | 17 +++ discord/http.py | 4 + discord/onboarding.py | 209 ++++++++++++++++++++++++++++++++++++ discord/types/onboarding.py | 59 ++++++++++ docs/api.rst | 38 +++++++ 7 files changed, 334 insertions(+) create mode 100644 discord/onboarding.py create mode 100644 discord/types/onboarding.py diff --git a/discord/__init__.py b/discord/__init__.py index 014615354..51de8d09c 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -68,6 +68,7 @@ from .interactions import * from .components import * from .threads import * from .automod import * +from .onboarding import * class VersionInfo(NamedTuple): diff --git a/discord/enums.py b/discord/enums.py index 43ae5b4f7..8fcadf1eb 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -67,6 +67,7 @@ __all__ = ( 'AutoModRuleEventType', 'AutoModRuleActionType', 'ForumLayoutType', + 'OnboardingPromptType', ) if TYPE_CHECKING: @@ -751,6 +752,11 @@ class ForumLayoutType(Enum): gallery_view = 2 +class OnboardingPromptType(Enum): + multiple_choice = 0 + dropdown = 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 078a7b8be..ce8324b99 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -88,6 +88,7 @@ from .sticker import GuildSticker from .file import File from .audit_logs import AuditLogEntry from .object import OLDEST_OBJECT, Object +from .onboarding import Onboarding from .welcome_screen import WelcomeScreen, WelcomeChannel from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction @@ -4026,3 +4027,19 @@ class Guild(Hashable): ) return AutoModRule(data=data, guild=self, state=self._state) + + async def fetch_onboarding(self) -> Onboarding: + """|coro| + + Fetches the onboarding configuration for this guild. + + .. versionadded:: 2.2 + + 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) \ No newline at end of file diff --git a/discord/http.py b/discord/http.py index 5913f4911..70ad63191 100644 --- a/discord/http.py +++ b/discord/http.py @@ -81,6 +81,7 @@ if TYPE_CHECKING: invite, member, message, + onboarding, template, role, user, @@ -2359,6 +2360,9 @@ class HTTPClient: reason=reason, ) + def get_guild_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]: + return self.request(Route('GET', '/guilds/{guild_id}/onboarding', guild_id=guild_id)) + # Misc def application_info(self) -> Response[appinfo.AppInfo]: diff --git a/discord/onboarding.py b/discord/onboarding.py new file mode 100644 index 000000000..c806541c6 --- /dev/null +++ b/discord/onboarding.py @@ -0,0 +1,209 @@ +""" +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 TYPE_CHECKING, Optional, Set, List, Union + +from . import utils +from .enums import OnboardingPromptType, try_enum +from .utils import cached_slot_property + +__all__ = ('Onboarding', 'OnboardingPrompt', 'OnboardingPromptOption') + + +if TYPE_CHECKING: + from .abc import GuildChannel + from .emoji import Emoji + from .guild import Guild + from .partial_emoji import PartialEmoji + from .role import Role + from .state import ConnectionState + from .threads import Thread + from .types.onboarding import ( + Prompt as PromptPayload, + PromptOption as PromptOptionPayload, + Onboarding as OnboardingPayload, + ) + + +class OnboardingPromptOption: + """Represents an onboarding prompt option. + + .. versionadded:: 2.2 + + Attributes + ----------- + id: :class:`int` + The ID of this prompt option. + guild: :class:`Guild` + The guild the onboarding prompt option is related to. + title: :class:`str` + The title of this prompt option. + description: Optional[:class:`str`] + The description of this prompt option. + emoji: 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 set 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__ = ( + '_state', + '_cs_channels', + '_cs_roles', + 'guild', + 'id', + 'title', + 'description', + 'emoji', + 'channel_ids', + 'role_ids', + ) + + 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: Optional[str] = data['description'] + self.emoji: Union[PartialEmoji, Emoji, str] = self._state.get_reaction_emoji(data['emoji']) + self.channel_ids: Set[int] = {int(channel_id) for channel_id in data['channel_ids']} + self.role_ids: Set[int] = {int(role_id) for role_id in data['role_ids']} + + def __repr__(self) -> str: + return f'' + + @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 set visible if this option is selected.""" + it = filter(None, map(self.guild._resolve_channel, self.channel_ids)) + return utils._unique(it) + + @cached_slot_property('_cs_roles') + def default_channels(self) -> List[Role]: + """List[Union[:class:`abc.GuildChannel`, :class:`Thread`]]: 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) + + +class OnboardingPrompt: + """Represents an onboarding prompt. + + .. versionadded:: 2.2 + + Attributes + ----------- + id: :class:`int` + 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 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. + required: :class:`bool` + Whether this prompt is required to complete the onboarding flow. + in_onboarding: :class:`bool` + Whether this prompt is part of the onboarding flow. + """ + + __slots__ = ( + '_state', + 'guild', + 'id', + 'title', + 'options', + 'single_select', + 'required', + 'in_onboarding', + 'type', + ) + + 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.options: List[OnboardingPromptOption] = [ + OnboardingPromptOption(data=option_data, state=state, guild=guild) for option_data in data['options'] + ] + self.single_select: bool = data['single_select'] + self.required: bool = data['required'] + self.in_onboarding: bool = data['in_onboarding'] + self.type: OnboardingPromptType = try_enum(OnboardingPromptType, data['type']) + + def __repr__(self) -> str: + return f'' + + +class Onboarding: + """Represents a guild's onboarding configuration. + + .. versionadded:: 2.2 + + Attributes + ----------- + 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. + """ + + __slots__ = ( + '_state', + '_cs_default_channels', + 'guild', + 'prompts', + 'default_channel_ids', + 'enabled', + ) + + 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'] + + def __repr__(self) -> str: + return f'' + + @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/types/onboarding.py b/discord/types/onboarding.py new file mode 100644 index 000000000..ab90f8533 --- /dev/null +++ b/discord/types/onboarding.py @@ -0,0 +1,59 @@ +""" +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 Literal, Optional, TypedDict + +from .emoji import Emoji +from .snowflake import Snowflake + + +PromptType = Literal[0, 1] + + +class PromptOption(TypedDict): + id: Snowflake + channel_ids: list[Snowflake] + role_ids: list[Snowflake] + emoji: Emoji + title: str + description: Optional[str] + + +class Prompt(TypedDict): + id: Snowflake + options: list[PromptOption] + title: str + single_select: bool + required: bool + in_onboarding: bool + type: PromptType + + +class Onboarding(TypedDict): + guild_id: Snowflake + prompts: list[Prompt] + default_channel_ids: list[Snowflake] + enabled: bool diff --git a/docs/api.rst b/docs/api.rst index 32e393ef2..4f64a0c9d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3276,6 +3276,20 @@ of :class:`enum.Enum`. Displays posts as a collection of tiles. +.. class:: OnboardingPromptType + + Represents the type of onboarding prompt. + + .. versionadded:: 2.2 + + .. attribute:: multiple_choice + + Prompt options are multiple choice. + + .. attribute:: dropdown + + Prompt options are displayed as a drop-down. + .. _discord-api-audit-logs: @@ -4480,6 +4494,30 @@ GuildSticker .. autoclass:: GuildSticker() :members: +Onboarding +~~~~~~~~~~~ + +.. attributetable:: Onboarding + +.. autoclass:: Onboarding() + :members: + +OnboardingPrompt +~~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPrompt + +.. autoclass:: OnboardingPrompt() + :members: + +OnboardingPromptOption +~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPromptOption + +.. autoclass:: OnboardingPromptOption() + :members: + ShardInfo ~~~~~~~~~~~ From 3fd0c25c88d9f54faa63edb1f5611f689c8fa26a Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sat, 25 Feb 2023 18:23:44 +1000 Subject: [PATCH 04/39] Add missing newline at eof in guild.py --- discord/guild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/guild.py b/discord/guild.py index ce8324b99..32a7416ec 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4042,4 +4042,4 @@ class Guild(Hashable): """ data = await self._state.http.get_guild_onboarding(self.id) - return Onboarding(data=data, guild=self, state=self._state) \ No newline at end of file + return Onboarding(data=data, guild=self, state=self._state) From d051807865de3e00b9d0a292eb59c43b00b2a035 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sat, 25 Feb 2023 21:13:59 +1000 Subject: [PATCH 05/39] Note that emoji can be Null --- discord/onboarding.py | 4 ++-- discord/types/emoji.py | 2 ++ discord/types/onboarding.py | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index c806541c6..fb28610f4 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -63,7 +63,7 @@ class OnboardingPromptOption: The title of this prompt option. description: Optional[:class:`str`] The description of this prompt option. - emoji: Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`] + 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 set visible if this option is selected. @@ -90,7 +90,7 @@ class OnboardingPromptOption: self.id: int = int(data['id']) self.title: str = data['title'] self.description: Optional[str] = data['description'] - self.emoji: Union[PartialEmoji, Emoji, str] = self._state.get_reaction_emoji(data['emoji']) + self.emoji: Optional[Union[PartialEmoji, Emoji, str]] = self._state.get_reaction_emoji(data['emoji']) self.channel_ids: Set[int] = {int(channel_id) for channel_id in data['channel_ids']} self.role_ids: Set[int] = {int(role_id) for role_id in data['role_ids']} diff --git a/discord/types/emoji.py b/discord/types/emoji.py index d54690c14..85e709757 100644 --- a/discord/types/emoji.py +++ b/discord/types/emoji.py @@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE. """ from typing import Optional, TypedDict +from typing_extensions import NotRequired from .snowflake import Snowflake, SnowflakeList from .user import User @@ -30,6 +31,7 @@ from .user import User class PartialEmoji(TypedDict): id: Optional[Snowflake] name: Optional[str] + animated: NotRequired[bool] class Emoji(PartialEmoji, total=False): diff --git a/discord/types/onboarding.py b/discord/types/onboarding.py index ab90f8533..11a85c8de 100644 --- a/discord/types/onboarding.py +++ b/discord/types/onboarding.py @@ -26,7 +26,7 @@ from __future__ import annotations from typing import Literal, Optional, TypedDict -from .emoji import Emoji +from .emoji import PartialEmoji from .snowflake import Snowflake @@ -37,7 +37,7 @@ class PromptOption(TypedDict): id: Snowflake channel_ids: list[Snowflake] role_ids: list[Snowflake] - emoji: Emoji + emoji: PartialEmoji title: str description: Optional[str] From 87c7a2abab09ae05daaaaa56f0eaa2b15f7ebc7f Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 27 Feb 2023 20:52:49 +1000 Subject: [PATCH 06/39] Update discord/onboarding.py Co-authored-by: numbermaniac <5206120+numbermaniac@users.noreply.github.com> --- discord/onboarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index fb28610f4..ff0cfc699 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -66,9 +66,9 @@ class OnboardingPromptOption: 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 set visible if this option is selected. + 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 + The IDs of the roles given to the user if this option is selected. """ __slots__ = ( From 074b05f0b0ecc63fd002d998fe03888844d376ab Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 27 Feb 2023 22:25:42 +1000 Subject: [PATCH 07/39] Update discord/onboarding.py Co-authored-by: numbermaniac <5206120+numbermaniac@users.noreply.github.com> --- discord/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index ff0cfc699..96be087cd 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -99,7 +99,7 @@ class OnboardingPromptOption: @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 set visible if this option is selected.""" + """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) From d3fe6d633ceb953e8f7d9b1716a9816526aaba8f Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sat, 1 Apr 2023 22:09:58 +1000 Subject: [PATCH 08/39] Rename State.get_reaction_emoji --- discord/onboarding.py | 2 +- discord/reaction.py | 2 +- discord/state.py | 2 +- discord/webhook/async_.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 96be087cd..f4999b0f4 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -90,7 +90,7 @@ class OnboardingPromptOption: self.id: int = int(data['id']) self.title: str = data['title'] self.description: Optional[str] = data['description'] - self.emoji: Optional[Union[PartialEmoji, Emoji, str]] = self._state.get_reaction_emoji(data['emoji']) + self.emoji: Optional[Union[PartialEmoji, Emoji, str]] = self._state.get_emoji_from_partial_payload(data['emoji']) self.channel_ids: Set[int] = {int(channel_id) for channel_id in data['channel_ids']} self.role_ids: Set[int] = {int(role_id) for role_id in data['role_ids']} diff --git a/discord/reaction.py b/discord/reaction.py index 5f50ec8f4..e1f88061f 100644 --- a/discord/reaction.py +++ b/discord/reaction.py @@ -85,7 +85,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'] diff --git a/discord/state.py b/discord/state.py index 8b556f28c..68ca61f36 100644 --- a/discord/state.py +++ b/discord/state.py @@ -1572,7 +1572,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/webhook/async_.py b/discord/webhook/async_.py index f9b03193a..acf69c310 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -731,7 +731,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') From f21c903ac783f7e600cd63168b1b104f0acce6e0 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sat, 1 Apr 2023 22:16:52 +1000 Subject: [PATCH 09/39] Bump version --- discord/guild.py | 2 +- discord/onboarding.py | 6 +++--- docs/api.rst | 27 ++++++++++++++------------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 5593a88e5..8317b7a00 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4227,7 +4227,7 @@ class Guild(Hashable): Fetches the onboarding configuration for this guild. - .. versionadded:: 2.2 + .. versionadded:: 2.3 Returns -------- diff --git a/discord/onboarding.py b/discord/onboarding.py index f4999b0f4..d06d778d7 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -51,7 +51,7 @@ if TYPE_CHECKING: class OnboardingPromptOption: """Represents an onboarding prompt option. - .. versionadded:: 2.2 + .. versionadded:: 2.3 Attributes ----------- @@ -113,7 +113,7 @@ class OnboardingPromptOption: class OnboardingPrompt: """Represents an onboarding prompt. - .. versionadded:: 2.2 + .. versionadded:: 2.3 Attributes ----------- @@ -167,7 +167,7 @@ class OnboardingPrompt: class Onboarding: """Represents a guild's onboarding configuration. - .. versionadded:: 2.2 + .. versionadded:: 2.3 Attributes ----------- diff --git a/docs/api.rst b/docs/api.rst index adc523680..992cf3941 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3309,34 +3309,35 @@ of :class:`enum.Enum`. Displays posts as a collection of tiles. -.. class:: OnboardingPromptType - Represents the type of onboarding prompt. +.. class:: ForumOrderType - .. versionadded:: 2.2 + Represents how a forum's posts are sorted in the client. - .. attribute:: multiple_choice + .. versionadded:: 2.3 - Prompt options are multiple choice. + .. attribute:: latest_activity - .. attribute:: dropdown + Sort forum posts by activity. - Prompt options are displayed as a drop-down. + .. attribute:: creation_date + Sort forum posts by creation time (from most recent to oldest). -.. class:: ForumOrderType - Represents how a forum's posts are sorted in the client. +.. class:: OnboardingPromptType + + Represents the type of onboarding prompt. .. versionadded:: 2.3 - .. attribute:: latest_activity + .. attribute:: multiple_choice - Sort forum posts by activity. + Prompt options are multiple choice. - .. attribute:: creation_date + .. attribute:: dropdown - Sort forum posts by creation time (from most recent to oldest). + Prompt options are displayed as a drop-down. .. _discord-api-audit-logs: From e8462dffa06fe9cec4521dc4cd04a4fbbd26c278 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Tue, 18 Apr 2023 21:47:44 +1000 Subject: [PATCH 10/39] Address review comments --- discord/guild.py | 3 +-- discord/onboarding.py | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 8317b7a00..ea488c0ab 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4222,7 +4222,7 @@ class Guild(Hashable): return AutoModRule(data=data, guild=self, state=self._state) - async def fetch_onboarding(self) -> Onboarding: + async def onboarding(self) -> Onboarding: """|coro| Fetches the onboarding configuration for this guild. @@ -4235,5 +4235,4 @@ class Guild(Hashable): 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) diff --git a/discord/onboarding.py b/discord/onboarding.py index d06d778d7..0e47ddedf 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -30,7 +30,11 @@ from . import utils from .enums import OnboardingPromptType, try_enum from .utils import cached_slot_property -__all__ = ('Onboarding', 'OnboardingPrompt', 'OnboardingPromptOption') +__all__ = ( + 'Onboarding', + 'OnboardingPrompt', + 'OnboardingPromptOption', +) if TYPE_CHECKING: From e8019f9d4cbf16b1b52f39c2bd8251c600b8239d Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Tue, 18 Apr 2023 22:11:15 +1000 Subject: [PATCH 11/39] Add Onboarding audit log data --- discord/audit_logs.py | 19 ++++++- discord/enums.py | 12 +++++ discord/types/audit_log.py | 29 +++++++++-- docs/api.rst | 102 +++++++++++++++++++++++++++++++++++-- 4 files changed, 155 insertions(+), 7 deletions(-) diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 47f397a8a..fa5b2ff21 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', @@ -72,6 +73,7 @@ if TYPE_CHECKING: from .types.snowflake import Snowflake from .types.command import ApplicationCommandPermissions from .types.automod import AutoModerationTriggerMetadata, AutoModerationAction + from .types.onboarding import Prompt as PromptPayload, PromptOption as PromptOptionPayload from .user import User from .app_commands import AppCommand @@ -263,6 +265,16 @@ def _transform_automod_actions(entry: AuditLogEntry, data: List[AutoModerationAc return [AutoModRuleAction.from_data(action) for action in 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) @@ -285,13 +297,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) @@ -370,6 +384,9 @@ class AuditLogChanges: 'available_tags': (None, _transform_forum_tags), 'flags': (None, _transform_overloaded_flags), 'default_reaction_emoji': (None, _transform_default_reaction), + '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 e0db03669..b8e0bc36d 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -368,6 +368,11 @@ class AuditLogAction(Enum): automod_block_message = 143 automod_flag_message = 144 automod_timeout_member = 145 + onboarding_question_create = 163 + onboarding_question_update = 164 + onboarding_update = 167 + server_guide_create = 190 + server_guide_update = 191 # fmt: on @property @@ -428,6 +433,9 @@ class AuditLogAction(Enum): AuditLogAction.automod_block_message: None, AuditLogAction.automod_flag_message: None, AuditLogAction.automod_timeout_member: None, + AuditLogAction.onboarding_question_create: AuditLogActionCategory.create, + AuditLogAction.onboarding_question_update: AuditLogActionCategory.update, + AuditLogAction.onboarding_update: AuditLogActionCategory.update, } # fmt: on return lookup[self] @@ -471,6 +479,10 @@ class AuditLogAction(Enum): return 'auto_moderation' elif v < 146: return 'user' + elif v < 165: + return 'onboarding_question' + elif v < 168: + return 'onboarding' class UserFlags(Enum): diff --git a/discord/types/audit_log.py b/discord/types/audit_log.py index 4401bc784..b46a37274 100644 --- a/discord/types/audit_log.py +++ b/discord/types/audit_log.py @@ -37,6 +37,7 @@ from .role import Role from .channel import ChannelType, DefaultReaction, PrivacyLevel, VideoQualityMode, PermissionOverwrite, ForumTag from .threads import Thread from .command import ApplicationCommand, ApplicationCommandPermissions +from .onboarding import PromptOption, Prompt AuditLogEvent = Literal[ 1, @@ -93,6 +94,9 @@ AuditLogEvent = Literal[ 143, 144, 145, + 163, + 164, + 167, ] @@ -109,6 +113,7 @@ class _AuditLogChange_Str(TypedDict): 'permissions', 'tags', 'unicode_emoji', + 'title', ] new_value: str old_value: str @@ -154,6 +159,10 @@ class _AuditLogChange_Bool(TypedDict): 'available', 'archived', 'locked', + 'enabled', + 'single_select', + 'required', + 'in_onboarding', ] new_value: bool old_value: bool @@ -258,8 +267,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] @@ -276,6 +285,18 @@ class _AuditLogChange_DefaultReactionEmoji(TypedDict): old_value: Optional[DefaultReaction] +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, @@ -295,9 +316,11 @@ AuditLogChange = Union[ _AuditLogChange_Status, _AuditLogChange_EntityType, _AuditLogChange_AppCommandPermissions, - _AuditLogChange_AppliedTags, + _AuditLogChange_SnowflakeList, _AuditLogChange_AvailableTags, _AuditLogChange_DefaultReactionEmoji, + _AuditLogChange_Prompts, + _AuditLogChange_Options, ] diff --git a/docs/api.rst b/docs/api.rst index 992cf3941..f63eb38a8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2798,6 +2798,46 @@ of :class:`enum.Enum`. .. versionadded:: 2.1 + .. 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.3 + + .. 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.3 + + .. 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` + .. class:: AuditLogActionCategory Represents the category that the :class:`AuditLogAction` belongs to. @@ -3580,9 +3620,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 @@ -3935,7 +3975,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` @@ -4011,6 +4051,62 @@ AuditLogDiff :type: :class:`ChannelFlags` + .. 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:`OnboardingUser.in_onboarding` + + :type: :class:`bool` + .. this is currently missing the following keys: reason and application_id I'm not sure how to port these From 689cc855331717708eaaa5fc56becab5c0123176 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sun, 2 Jul 2023 01:33:00 +1000 Subject: [PATCH 12/39] Update versions, fix docs --- discord/guild.py | 2 +- discord/onboarding.py | 6 +++--- docs/api.rst | 10 ++++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 9ced6114b..87c97b488 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4284,7 +4284,7 @@ class Guild(Hashable): Fetches the onboarding configuration for this guild. - .. versionadded:: 2.3 + .. versionadded:: 2.4 Returns -------- diff --git a/discord/onboarding.py b/discord/onboarding.py index 0e47ddedf..4a96444ec 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -55,7 +55,7 @@ if TYPE_CHECKING: class OnboardingPromptOption: """Represents an onboarding prompt option. - .. versionadded:: 2.3 + .. versionadded:: 2.4 Attributes ----------- @@ -117,7 +117,7 @@ class OnboardingPromptOption: class OnboardingPrompt: """Represents an onboarding prompt. - .. versionadded:: 2.3 + .. versionadded:: 2.4 Attributes ----------- @@ -171,7 +171,7 @@ class OnboardingPrompt: class Onboarding: """Represents a guild's onboarding configuration. - .. versionadded:: 2.3 + .. versionadded:: 2.4 Attributes ----------- diff --git a/docs/api.rst b/docs/api.rst index 06c81120a..a5ec43a57 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2812,7 +2812,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.required` - :attr:`~AuditLogDiff.in_onboarding` - .. versionadded:: 2.3 + .. versionadded:: 2.4 .. attribute:: onboarding_question_update @@ -2827,7 +2827,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.required` - :attr:`~AuditLogDiff.in_onboarding` - .. versionadded:: 2.3 + .. versionadded:: 2.4 .. attribute:: onboarding_update @@ -2839,6 +2839,8 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.default_channels` - :attr:`~AuditLogDiff.prompts` + .. versionadded:: 2.4 + .. class:: AuditLogActionCategory Represents the category that the :class:`AuditLogAction` belongs to. @@ -3376,7 +3378,7 @@ of :class:`enum.Enum`. Represents the type of onboarding prompt. - .. versionadded:: 2.3 + .. versionadded:: 2.4 .. attribute:: multiple_choice @@ -4148,7 +4150,7 @@ AuditLogDiff Whether this prompt is currently part of the onboarding flow. - See also :attr:`OnboardingUser.in_onboarding` + See also :attr:`OnboardingPrompt.in_onboarding` :type: :class:`bool` From a9ed9efedb9cc40fce65f43b5221c72966384744 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sun, 2 Jul 2023 01:39:09 +1000 Subject: [PATCH 13/39] Fix PromptOption roles property --- discord/onboarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 4a96444ec..e400cd6c6 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -108,8 +108,8 @@ class OnboardingPromptOption: return utils._unique(it) @cached_slot_property('_cs_roles') - def default_channels(self) -> List[Role]: - """List[Union[:class:`abc.GuildChannel`, :class:`Thread`]]: The list of roles given to the user if this option is selected.""" + 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) From 1b29dd36cbd440f0ffe48d2d128ea53eb2259f97 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sun, 2 Jul 2023 03:05:16 +1000 Subject: [PATCH 14/39] Add support for modifying onboarding, mode --- discord/enums.py | 6 ++ discord/guild.py | 53 ++++++++++- discord/http.py | 31 +++++++ discord/onboarding.py | 177 +++++++++++++++++++++++++++++++----- discord/state.py | 8 ++ discord/types/onboarding.py | 2 + docs/api.rst | 30 ++++++ 7 files changed, 282 insertions(+), 25 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index de5ea6a11..7f32cc007 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -69,6 +69,7 @@ __all__ = ( 'ForumLayoutType', 'ForumOrderType', 'OnboardingPromptType', + 'OnboardingMode', ) if TYPE_CHECKING: @@ -776,6 +777,11 @@ class OnboardingPromptType(Enum): 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 87c97b488..60e8c9cd0 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -75,6 +75,7 @@ from .enums import ( AutoModRuleEventType, ForumOrderType, ForumLayoutType, + OnboardingMode, ) from .mixins import Hashable from .user import User @@ -90,7 +91,7 @@ from .sticker import GuildSticker from .file import File from .audit_logs import AuditLogEntry from .object import OLDEST_OBJECT, Object -from .onboarding import Onboarding +from .onboarding import Onboarding, PartialOnboardingPrompt from .welcome_screen import WelcomeScreen, WelcomeChannel from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction from .partial_emoji import _EmojiTag, PartialEmoji @@ -4293,3 +4294,53 @@ class Guild(Hashable): """ 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.modify_guild_onboarding( + self.id, + prompts=[p.to_dict() for p in 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 d30d7a14b..2c136acaa 100644 --- a/discord/http.py +++ b/discord/http.py @@ -2379,6 +2379,37 @@ class HTTPClient: 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 modify_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, + ) + # Misc def application_info(self) -> Response[appinfo.AppInfo]: diff --git a/discord/onboarding.py b/discord/onboarding.py index e400cd6c6..b88b21d9b 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -24,16 +24,24 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Set, List, Union +import os +from typing import TYPE_CHECKING, Iterable, Optional, Set, List, Union + +from discord.types.onboarding import Prompt as PromptPayload +from discord.utils import MISSING from . import utils -from .enums import OnboardingPromptType, try_enum -from .utils import cached_slot_property +from .mixins import Hashable +from .enums import OnboardingMode, OnboardingPromptType, try_enum +from .state import ConnectionState +from .utils import cached_slot_property, MISSING __all__ = ( 'Onboarding', + 'PartialOnboardingPrompt', 'OnboardingPrompt', 'OnboardingPromptOption', + 'PartialOnboardingPromptOption', ) @@ -43,7 +51,6 @@ if TYPE_CHECKING: from .guild import Guild from .partial_emoji import PartialEmoji from .role import Role - from .state import ConnectionState from .threads import Thread from .types.onboarding import ( Prompt as PromptPayload, @@ -52,7 +59,60 @@ if TYPE_CHECKING: ) -class OnboardingPromptOption: +class PartialOnboardingPromptOption: + """Represents a partial onboarding prompt option, these are used in the creation + of an :class:`OnboardingPrompt` via :meth:`Guild.edit_onboarding`. + + .. versionadded:: 2.4 + + 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. + """ + + __slots__ = ( + 'title', + 'emoji', + 'description', + 'channel_ids', + 'role_ids', + ) + + 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: + return { + 'id': id or os.urandom(16).hex(), + 'title': self.title, + 'description': self.description, + 'emoji': ConnectionState.emoji_to_partial_payload(self.emoji), + 'channel_ids': list(self.channel_ids), + 'role_ids': list(self.role_ids), + } + + +class OnboardingPromptOption(PartialOnboardingPromptOption, Hashable): """Represents an onboarding prompt option. .. versionadded:: 2.4 @@ -81,22 +141,19 @@ class OnboardingPromptOption: '_cs_roles', 'guild', 'id', - 'title', - 'description', - 'emoji', - 'channel_ids', - 'role_ids', ) 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: Optional[str] = data['description'] - self.emoji: Optional[Union[PartialEmoji, Emoji, str]] = self._state.get_emoji_from_partial_payload(data['emoji']) - self.channel_ids: Set[int] = {int(channel_id) for channel_id in data['channel_ids']} - self.role_ids: Set[int] = {int(role_id) for role_id in data['role_ids']} + 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'' @@ -113,8 +170,71 @@ class OnboardingPromptOption: it = filter(None, map(self.guild.get_role, self.role_ids)) return utils._unique(it) + def to_dict(self) -> PromptOptionPayload: + return super().to_dict(id=self.id) + + +class PartialOnboardingPrompt: + """Represents a partial onboarding prompt, these are used in the creation + of an :class:`Onboarding` via :meth:`Guild.edit_onboarding`. + + .. versionadded:: 2.4 + + 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. + """ + + __slots__ = ( + 'type', + 'title', + 'options', + 'single_select', + 'required', + 'in_onboarding', + ) -class OnboardingPrompt: + 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 = MISSING) -> PromptPayload: + return { + 'id': id or os.urandom(16).hex(), + '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 @@ -139,6 +259,8 @@ class OnboardingPrompt: Whether this prompt is part of the onboarding flow. """ + options: List[OnboardingPromptOption] + __slots__ = ( '_state', 'guild', @@ -155,18 +277,21 @@ class OnboardingPrompt: self._state: ConnectionState = state self.guild: Guild = guild self.id: int = int(data['id']) - self.title: str = data['title'] - self.options: List[OnboardingPromptOption] = [ - OnboardingPromptOption(data=option_data, state=state, guild=guild) for option_data in data['options'] - ] - self.single_select: bool = data['single_select'] - self.required: bool = data['required'] - self.in_onboarding: bool = data['in_onboarding'] - self.type: OnboardingPromptType = try_enum(OnboardingPromptType, data['type']) + 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'' + def to_dict(self) -> PromptPayload: + return super().to_dict(id=self.id) + class Onboarding: """Represents a guild's onboarding configuration. @@ -183,6 +308,8 @@ class Onboarding: 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__ = ( @@ -192,6 +319,7 @@ class Onboarding: 'prompts', 'default_channel_ids', 'enabled', + 'mode', ) def __init__(self, *, data: OnboardingPayload, guild: Guild, state: ConnectionState) -> None: @@ -202,6 +330,7 @@ class Onboarding: OnboardingPrompt(data=prompt_data, state=state, guild=guild) for prompt_data in data['prompts'] ] self.enabled: bool = data['enabled'] + self.mode: OnboardingMode = try_enum(OnboardingMode, data.get('mode', 0)) def __repr__(self) -> str: return f'' diff --git a/discord/state.py b/discord/state.py index 0d88c6f49..a9afd07fb 100644 --- a/discord/state.py +++ b/discord/state.py @@ -1587,6 +1587,14 @@ class ConnectionState(Generic[ClientT]): self, animated=data.get('animated', False), id=emoji_id, name=data['name'] # type: ignore ) + @staticmethod + def emoji_to_partial_payload(emoji: Union[Emoji, PartialEmoji, str]) -> PartialEmojiPayload: + if isinstance(emoji, str): + return {'name': emoji} # type: ignore + elif isinstance(emoji, Emoji): + emoji = emoji._to_partial() + return emoji.to_dict() + def _upgrade_partial_emoji(self, emoji: PartialEmoji) -> Union[Emoji, PartialEmoji, str]: emoji_id = emoji.id if not emoji_id: diff --git a/discord/types/onboarding.py b/discord/types/onboarding.py index 11a85c8de..64fa199c4 100644 --- a/discord/types/onboarding.py +++ b/discord/types/onboarding.py @@ -31,6 +31,7 @@ from .snowflake import Snowflake PromptType = Literal[0, 1] +OnboardingMode = Literal[0, 1] class PromptOption(TypedDict): @@ -57,3 +58,4 @@ class Onboarding(TypedDict): prompts: list[Prompt] default_channel_ids: list[Snowflake] enabled: bool + mode: OnboardingMode diff --git a/docs/api.rst b/docs/api.rst index a5ec43a57..3f0e00a93 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3388,6 +3388,20 @@ of :class:`enum.Enum`. Prompt options are displayed as a drop-down. +.. class:: OnboardingMode + + Represents the onboarding constriant mode. + + .. versionadded:: 2.4 + + .. attribute:: default + + Only default channels count towards onboarding constraints. + + .. attribute:: advanced + + Default channels and questions count towards onboarding constraints. + .. _discord-api-audit-logs: @@ -4730,6 +4744,14 @@ Onboarding .. autoclass:: Onboarding() :members: +PartialOnboardingPrompt +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: PartialOnboardingPrompt + +.. autoclass:: PartialOnboardingPrompt + :members: + OnboardingPrompt ~~~~~~~~~~~~~~~~~ @@ -4738,6 +4760,14 @@ OnboardingPrompt .. autoclass:: OnboardingPrompt() :members: +PartialOnboardingPromptOption +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: PartialOnboardingPromptOption + +.. autoclass:: PartialOnboardingPromptOption + :members: + OnboardingPromptOption ~~~~~~~~~~~~~~~~~~~~~~~ From cf27b27c0b2dbba2b33f064ab5cbe04e8ab74e78 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sun, 2 Jul 2023 03:07:50 +1000 Subject: [PATCH 15/39] Remove types import --- discord/onboarding.py | 1 - 1 file changed, 1 deletion(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index b88b21d9b..3b99cd90d 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -27,7 +27,6 @@ from __future__ import annotations import os from typing import TYPE_CHECKING, Iterable, Optional, Set, List, Union -from discord.types.onboarding import Prompt as PromptPayload from discord.utils import MISSING from . import utils From 786b153242c7e5d70862d9a3ef775be0025599fc Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sun, 2 Jul 2023 03:10:11 +1000 Subject: [PATCH 16/39] Resolve circular import --- discord/onboarding.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 3b99cd90d..5fcf234b2 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -32,7 +32,6 @@ from discord.utils import MISSING from . import utils from .mixins import Hashable from .enums import OnboardingMode, OnboardingPromptType, try_enum -from .state import ConnectionState from .utils import cached_slot_property, MISSING __all__ = ( @@ -56,6 +55,7 @@ if TYPE_CHECKING: PromptOption as PromptOptionPayload, Onboarding as OnboardingPayload, ) + from .state import ConnectionState class PartialOnboardingPromptOption: @@ -101,6 +101,7 @@ class PartialOnboardingPromptOption: self.role_ids: Set[int] = set(role_ids or []) def to_dict(self, *, id: int = MISSING) -> PromptOptionPayload: + from .state import ConnectionState # circular import return { 'id': id or os.urandom(16).hex(), 'title': self.title, From 96778d320cae6de25e6f5634d993884140dd2d99 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sun, 2 Jul 2023 03:10:27 +1000 Subject: [PATCH 17/39] Fix spacing --- discord/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 5fcf234b2..1832984a2 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -101,7 +101,7 @@ class PartialOnboardingPromptOption: self.role_ids: Set[int] = set(role_ids or []) def to_dict(self, *, id: int = MISSING) -> PromptOptionPayload: - from .state import ConnectionState # circular import + from .state import ConnectionState # circular import return { 'id': id or os.urandom(16).hex(), 'title': self.title, From b0a281aa5ccce35ed79b18f2952d8fcf49b38d02 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sun, 2 Jul 2023 03:12:06 +1000 Subject: [PATCH 18/39] run black --- discord/onboarding.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/onboarding.py b/discord/onboarding.py index 1832984a2..50bfeddeb 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -102,6 +102,7 @@ class PartialOnboardingPromptOption: def to_dict(self, *, id: int = MISSING) -> PromptOptionPayload: from .state import ConnectionState # circular import + return { 'id': id or os.urandom(16).hex(), 'title': self.title, From 28fc9f0607e4f22eca866b98971fe9216e5b873e Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Sun, 2 Jul 2023 03:13:02 +1000 Subject: [PATCH 19/39] Fix underline on prompt option --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 3f0e00a93..327edd878 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4761,7 +4761,7 @@ OnboardingPrompt :members: PartialOnboardingPromptOption -~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. attributetable:: PartialOnboardingPromptOption From 025d09937916e55acc41b3e448e4edaf7115f0a2 Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Wed, 26 Jul 2023 23:50:14 +1000 Subject: [PATCH 20/39] Fix prompt ID code --- discord/guild.py | 2 +- discord/onboarding.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 60e8c9cd0..d25af2f09 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4337,7 +4337,7 @@ class Guild(Hashable): """ data = await self._state.http.modify_guild_onboarding( self.id, - prompts=[p.to_dict() for p in prompts] if prompts is not MISSING else None, + 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, diff --git a/discord/onboarding.py b/discord/onboarding.py index 50bfeddeb..4ca71b724 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -223,9 +223,9 @@ class PartialOnboardingPrompt: self.required: bool = required self.in_onboarding: bool = in_onboarding - def to_dict(self, *, id: int = MISSING) -> PromptPayload: + def to_dict(self, *, id: int) -> PromptPayload: return { - 'id': id or os.urandom(16).hex(), + 'id': id, 'type': self.type.value, 'title': self.title, 'options': [option.to_dict() for option in self.options], @@ -290,9 +290,6 @@ class OnboardingPrompt(PartialOnboardingPrompt, Hashable): def __repr__(self) -> str: return f'' - def to_dict(self) -> PromptPayload: - return super().to_dict(id=self.id) - class Onboarding: """Represents a guild's onboarding configuration. From b805441fc7b00117d73bc410e0caf04268cf60ae Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Fri, 10 Nov 2023 00:08:13 +1000 Subject: [PATCH 21/39] Update guild.py Co-Authored-By: Andrin <65789180+Puncher1@users.noreply.github.com> --- discord/guild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/guild.py b/discord/guild.py index 395fa59fa..ae34cf9a2 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4350,7 +4350,7 @@ class Guild(Hashable): :class:`Onboarding` The new onboarding configuration. """ - data = await self._state.http.modify_guild_onboarding( + 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, From 001dc937017af60f0c1769186307bd87d4386f4a Mon Sep 17 00:00:00 2001 From: Josh <8677174+bijij@users.noreply.github.com> Date: Fri, 10 Nov 2023 00:19:16 +1000 Subject: [PATCH 22/39] Move to the dumbest emoji payload I've ever seen --- discord/onboarding.py | 13 ++++++++++--- discord/state.py | 8 -------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 4ca71b724..5b5c59e0b 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -101,16 +101,23 @@ class PartialOnboardingPromptOption: self.role_ids: Set[int] = set(role_ids or []) def to_dict(self, *, id: int = MISSING) -> PromptOptionPayload: - from .state import ConnectionState # circular import + 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, - 'emoji': ConnectionState.emoji_to_partial_payload(self.emoji), 'channel_ids': list(self.channel_ids), 'role_ids': list(self.role_ids), - } + **emoji_payload, + } # type: ignore class OnboardingPromptOption(PartialOnboardingPromptOption, Hashable): diff --git a/discord/state.py b/discord/state.py index aaa591ee7..ed956eb50 100644 --- a/discord/state.py +++ b/discord/state.py @@ -1616,14 +1616,6 @@ class ConnectionState(Generic[ClientT]): self, animated=data.get('animated', False), id=emoji_id, name=data['name'] # type: ignore ) - @staticmethod - def emoji_to_partial_payload(emoji: Union[Emoji, PartialEmoji, str]) -> PartialEmojiPayload: - if isinstance(emoji, str): - return {'name': emoji} # type: ignore - elif isinstance(emoji, Emoji): - emoji = emoji._to_partial() - return emoji.to_dict() - def _upgrade_partial_emoji(self, emoji: PartialEmoji) -> Union[Emoji, PartialEmoji, str]: emoji_id = emoji.id if not emoji_id: From cfce3ece36dc7b3a10ff1f1e8842fb75f2022bf0 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Wed, 9 Jul 2025 18:57:04 +0200 Subject: [PATCH 23/39] Correct auditlog actions https: //github.com/Rapptz/discord.py/pull/9260#issuecomment-2115616375 Co-Authored-By: Andrin Schaller <65789180+codeofandrin@users.noreply.github.com> --- discord/audit_logs.py | 2 +- discord/enums.py | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/discord/audit_logs.py b/discord/audit_logs.py index fdc22c26b..8ff7f798e 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -287,7 +287,7 @@ def _transform_type( 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_'): + elif entry.action.name.startswith('onboarding_prompt_'): return enums.try_enum(enums.OnboardingPromptType, data) else: return enums.try_enum(enums.ChannelType, data) diff --git a/discord/enums.py b/discord/enums.py index 677eaa919..ebb42988b 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -401,11 +401,13 @@ 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_prompt_create = 163 + onboarding_prompt_update = 164 + onboarding_prompt_delete = 165 + onboarding_create = 166 onboarding_update = 167 - server_guide_create = 190 - server_guide_update = 191 + home_settings_create = 190 + home_settings_update = 191 # fmt: on @property @@ -471,10 +473,11 @@ 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_prompt_create: AuditLogActionCategory.create, + AuditLogAction.onboarding_prompt_update: AuditLogActionCategory.update, + AuditLogAction.onboarding_prompt_delete: AuditLogActionCategory.delete, + AuditLogAction.onboarding_create: AuditLogActionCategory.create, AuditLogAction.onboarding_update: AuditLogActionCategory.update, - } # fmt: on return lookup[self] From cbb83f0c20defd42c730d22f8a86f54877f37a65 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Wed, 9 Jul 2025 18:57:28 +0200 Subject: [PATCH 24/39] Apply suggestions from reviews https: //github.com/Rapptz/discord.py/pull/9260#pullrequestreview-2085585270 Co-Authored-By: DA344 <108473820+DA-344@users.noreply.github.com> --- discord/onboarding.py | 19 +++++++++++-------- discord/types/onboarding.py | 12 ++++++------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 5b5c59e0b..245671c89 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -89,7 +89,7 @@ class PartialOnboardingPromptOption: def __init__( self, title: str, - emoji: Union[Emoji, PartialEmoji, str], + emoji: Union[Emoji, PartialEmoji, str] = MISSING, description: Optional[str] = None, channel_ids: Iterable[int] = MISSING, role_ids: Iterable[int] = MISSING, @@ -101,14 +101,17 @@ class PartialOnboardingPromptOption: 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} + if self.emoji is not MISSING: + 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, + } else: - emoji_payload = { - "emoji_id": self.emoji.id, - "emoji_name": self.emoji.name, - "emoji_animated": self.emoji.animated, - } + emoji_payload = {} return { 'id': id or os.urandom(16).hex(), diff --git a/discord/types/onboarding.py b/discord/types/onboarding.py index 64fa199c4..4945fb18e 100644 --- a/discord/types/onboarding.py +++ b/discord/types/onboarding.py @@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import Literal, Optional, TypedDict +from typing import Literal, Optional, TypedDict, List from .emoji import PartialEmoji from .snowflake import Snowflake @@ -36,8 +36,8 @@ OnboardingMode = Literal[0, 1] 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: Optional[str] @@ -45,7 +45,7 @@ class PromptOption(TypedDict): class Prompt(TypedDict): id: Snowflake - options: list[PromptOption] + options: List[PromptOption] title: str single_select: bool required: bool @@ -55,7 +55,7 @@ class Prompt(TypedDict): class Onboarding(TypedDict): guild_id: Snowflake - prompts: list[Prompt] - default_channel_ids: list[Snowflake] + prompts: List[Prompt] + default_channel_ids: List[Snowflake] enabled: bool mode: OnboardingMode From 68cb29c58b19fed38615fd3380501f3c54a8b881 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:05:55 +0200 Subject: [PATCH 25/39] Add back removed enum docs --- docs/api.rst | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 4aca17c34..b5960ea6d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3811,6 +3811,109 @@ of :class:`enum.Enum`. The entitlement owner is a user. +.. class:: PollLayoutType + + Represents how a poll answers are shown. + + .. versionadded:: 2.4 + + .. attribute:: default + + The default layout. + +.. class:: InviteType + + Represents the type of an invite. + + .. versionadded:: 2.4 + + .. attribute:: guild + + The invite is a guild invite. + + .. 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. + + .. 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` + + .. attribute:: forward + + A forwarded message. + + .. attribute:: reply + + An alias for :attr:`.default`. .. class:: OnboardingPromptType From 6b228a2bc9bc5b502cd602577c2b9acf271ba1c7 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:09:32 +0200 Subject: [PATCH 26/39] Revert removed lines --- docs/api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index b5960ea6d..fcc68ee31 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3811,6 +3811,7 @@ of :class:`enum.Enum`. The entitlement owner is a user. + .. class:: PollLayoutType Represents how a poll answers are shown. @@ -3821,6 +3822,7 @@ of :class:`enum.Enum`. The default layout. + .. class:: InviteType Represents the type of an invite. From 87e8634c0b0c7e68298d9583ea3fa118a5b7429f Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:09:54 +0200 Subject: [PATCH 27/39] Add new auditlog actions to types --- discord/types/audit_log.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/discord/types/audit_log.py b/discord/types/audit_log.py index 135a24518..29e2f09b7 100644 --- a/discord/types/audit_log.py +++ b/discord/types/audit_log.py @@ -102,7 +102,11 @@ AuditLogEvent = Literal[ 151, 163, 164, + 165, + 166, 167, + 190, + 191, ] From 1b2219f8d0643122ddb8236dba132b9d058bd42c Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:12:41 +0200 Subject: [PATCH 28/39] Add home_settings audit log actions to category property --- discord/enums.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discord/enums.py b/discord/enums.py index 5315fd9b6..e5b0215ef 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -479,6 +479,8 @@ class AuditLogAction(Enum): AuditLogAction.onboarding_prompt_delete: AuditLogActionCategory.delete, AuditLogAction.onboarding_create: AuditLogActionCategory.create, AuditLogAction.onboarding_update: AuditLogActionCategory.update, + AuditLogAction.home_settings_create: AuditLogActionCategory.create, + AuditLogAction.home_settings_update: AuditLogActionCategory.update, } # fmt: on return lookup[self] From 242388daf2d614ee503adabeb23f38c484839058 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:22:01 +0200 Subject: [PATCH 29/39] Fix AuditLogAction.target_type --- discord/enums.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index e5b0215ef..37d82e796 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -526,10 +526,12 @@ class AuditLogAction(Enum): return 'user' elif v < 152: return 'creator_monetization' - elif v < 165: - return 'onboarding_question' + elif v < 166: + return 'onboarding_prompt' elif v < 168: return 'onboarding' + elif v < 192: + return 'home_settings' class UserFlags(Enum): From 711e3ff497e03a3b77b6bc4fe7649f4651357de2 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:31:17 +0200 Subject: [PATCH 30/39] Correct AuditLogAction docs --- docs/api.rst | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index fcc68ee31..0a317d8a6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3097,7 +3097,7 @@ of :class:`enum.Enum`. .. versionadded:: 2.5 - .. attribute:: onboarding_question_create + .. attribute:: onboarding_prompt_create A guild onboarding prompt was created. @@ -3112,7 +3112,7 @@ of :class:`enum.Enum`. .. versionadded:: 2.4 - .. attribute:: onboarding_question_update + .. attribute:: onboarding_prompt_update A guild onboarding prompt was updated. @@ -3127,6 +3127,33 @@ of :class:`enum.Enum`. .. versionadded:: 2.4 + .. attribute:: onboarding_prompt_delete + + A guild onboarding prompt was deleted. + + 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_create + + The guild's onboarding configuration was created. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.enabled` + - :attr:`~AuditLogDiff.default_channels` + - :attr:`~AuditLogDiff.prompts` + + .. versionadded:: 2.4 + .. attribute:: onboarding_update The guild's onboarding configuration was updated. @@ -3139,6 +3166,18 @@ of :class:`enum.Enum`. .. versionadded:: 2.4 + .. attribute:: home_settings_create + + The guild's server guide was created. + + .. versionadded:: 2.4 + + .. attribute:: home_settings_update + + The guild's server guide was updated. + + .. versionadded:: 2.4 + .. class:: AuditLogActionCategory Represents the category that the :class:`AuditLogAction` belongs to. From 8481e7a08ec759bfb5bedc87a5c2b3cce47ff624 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:36:06 +0200 Subject: [PATCH 31/39] 2.4 -> 2.6 --- discord/guild.py | 7 +++++-- discord/onboarding.py | 10 +++++----- docs/api.rst | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 3e750b89e..183f28c8e 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4875,7 +4875,10 @@ class Guild(Hashable): Fetches the onboarding configuration for this guild. - .. versionadded:: 2.4 + You must have :attr:`Permissions.manage_guild` and + :attr:`Permissions.manage_roles` to do this. + + .. versionadded:: 2.6 Returns -------- @@ -4898,7 +4901,7 @@ class Guild(Hashable): Edits the onboarding configuration for this guild. - .. versionadded:: 2.4 + .. versionadded:: 2.6 Parameters ----------- diff --git a/discord/onboarding.py b/discord/onboarding.py index 245671c89..bc3a72c7b 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -62,7 +62,7 @@ class PartialOnboardingPromptOption: """Represents a partial onboarding prompt option, these are used in the creation of an :class:`OnboardingPrompt` via :meth:`Guild.edit_onboarding`. - .. versionadded:: 2.4 + .. versionadded:: 2.6 Attributes ----------- @@ -126,7 +126,7 @@ class PartialOnboardingPromptOption: class OnboardingPromptOption(PartialOnboardingPromptOption, Hashable): """Represents an onboarding prompt option. - .. versionadded:: 2.4 + .. versionadded:: 2.6 Attributes ----------- @@ -189,7 +189,7 @@ class PartialOnboardingPrompt: """Represents a partial onboarding prompt, these are used in the creation of an :class:`Onboarding` via :meth:`Guild.edit_onboarding`. - .. versionadded:: 2.4 + .. versionadded:: 2.6 Attributes ----------- @@ -248,7 +248,7 @@ class PartialOnboardingPrompt: class OnboardingPrompt(PartialOnboardingPrompt, Hashable): """Represents an onboarding prompt. - .. versionadded:: 2.4 + .. versionadded:: 2.6 Attributes ----------- @@ -304,7 +304,7 @@ class OnboardingPrompt(PartialOnboardingPrompt, Hashable): class Onboarding: """Represents a guild's onboarding configuration. - .. versionadded:: 2.4 + .. versionadded:: 2.6 Attributes ----------- diff --git a/docs/api.rst b/docs/api.rst index 0a317d8a6..270852825 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3110,7 +3110,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.required` - :attr:`~AuditLogDiff.in_onboarding` - .. versionadded:: 2.4 + .. versionadded:: 2.6 .. attribute:: onboarding_prompt_update @@ -3125,7 +3125,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.required` - :attr:`~AuditLogDiff.in_onboarding` - .. versionadded:: 2.4 + .. versionadded:: 2.6 .. attribute:: onboarding_prompt_delete @@ -3140,7 +3140,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.required` - :attr:`~AuditLogDiff.in_onboarding` - .. versionadded:: 2.4 + .. versionadded:: 2.6 .. attribute:: onboarding_create @@ -3152,7 +3152,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.default_channels` - :attr:`~AuditLogDiff.prompts` - .. versionadded:: 2.4 + .. versionadded:: 2.6 .. attribute:: onboarding_update @@ -3164,19 +3164,19 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.default_channels` - :attr:`~AuditLogDiff.prompts` - .. versionadded:: 2.4 + .. versionadded:: 2.6 .. attribute:: home_settings_create The guild's server guide was created. - .. versionadded:: 2.4 + .. versionadded:: 2.6 .. attribute:: home_settings_update The guild's server guide was updated. - .. versionadded:: 2.4 + .. versionadded:: 2.6 .. class:: AuditLogActionCategory @@ -3960,7 +3960,7 @@ of :class:`enum.Enum`. Represents the type of onboarding prompt. - .. versionadded:: 2.4 + .. versionadded:: 2.6 .. attribute:: multiple_choice @@ -3972,9 +3972,9 @@ of :class:`enum.Enum`. .. class:: OnboardingMode - Represents the onboarding constriant mode. + Represents the onboarding constraint mode. - .. versionadded:: 2.4 + .. versionadded:: 2.6 .. attribute:: default From f92a63ad16ba14a7212ba9159bbcfeebbdb94842 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:37:19 +0200 Subject: [PATCH 32/39] Final changes - Remove partials - Fix AuditLogAction docs - Add AuditLogDiff.mode - Add Onboarding.get_prompt - Add OnboardingPrompt.get_option - Add PartialEmoji._to_onboarding_prompt_option_payload --- discord/audit_logs.py | 5 +- discord/guild.py | 9 +- discord/onboarding.py | 275 +++++++++++++++++------------------- discord/partial_emoji.py | 6 + discord/types/onboarding.py | 23 ++- docs/api.rst | 61 +------- 6 files changed, 166 insertions(+), 213 deletions(-) diff --git a/discord/audit_logs.py b/discord/audit_logs.py index a5fbda7c9..aceaf367d 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -249,13 +249,13 @@ def _transform_default_emoji(entry: AuditLogEntry, data: str) -> PartialEmoji: 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] + return [OnboardingPrompt.from_dict(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] + return [OnboardingPromptOption.from_dict(data=option, state=entry._state, guild=entry.guild) for option in data] E = TypeVar('E', bound=enums.Enum) @@ -371,6 +371,7 @@ class AuditLogChanges: 'options': (None, _transform_onboarding_prompt_options), 'prompts': (None, _transform_onboarding_prompts), 'default_channel_ids': ('default_channels', _transform_channels_or_threads), + 'mode': (None, _enum_transformer(enums.OnboardingMode)), } # fmt: on diff --git a/discord/guild.py b/discord/guild.py index 183f28c8e..cbf28ffc3 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -92,7 +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 .onboarding import Onboarding from .welcome_screen import WelcomeScreen, WelcomeChannel from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction from .partial_emoji import _EmojiTag, PartialEmoji @@ -141,6 +141,7 @@ if TYPE_CHECKING: from .types.widget import EditWidgetSettings from .types.audit_log import AuditLogEvent from .message import EmojiInputType + from .onboarding import OnboardingPrompt VocalGuildChannel = Union[VoiceChannel, StageChannel] GuildChannel = Union[VocalGuildChannel, ForumChannel, TextChannel, CategoryChannel] @@ -4875,7 +4876,7 @@ class Guild(Hashable): Fetches the onboarding configuration for this guild. - You must have :attr:`Permissions.manage_guild` and + You must have :attr:`Permissions.manage_guild` and :attr:`Permissions.manage_roles` to do this. .. versionadded:: 2.6 @@ -4891,7 +4892,7 @@ class Guild(Hashable): async def edit_onboarding( self, *, - prompts: List[PartialOnboardingPrompt] = MISSING, + prompts: List[OnboardingPrompt] = MISSING, default_channels: List[Snowflake] = MISSING, enabled: bool = MISSING, mode: OnboardingMode = MISSING, @@ -4905,7 +4906,7 @@ class Guild(Hashable): Parameters ----------- - prompts: List[:class:`PartialOnboardingPrompt`] + prompts: List[:class:`OnboardingPrompt`] 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. diff --git a/discord/onboarding.py b/discord/onboarding.py index bc3a72c7b..6beec0c75 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -23,28 +23,25 @@ DEALINGS IN THE SOFTWARE. """ from __future__ import annotations - -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 .enums import OnboardingMode, OnboardingPromptType, try_enum +from .partial_emoji import PartialEmoji from .utils import cached_slot_property, MISSING +from . import utils __all__ = ( 'Onboarding', - 'PartialOnboardingPrompt', 'OnboardingPrompt', 'OnboardingPromptOption', - 'PartialOnboardingPromptOption', ) if TYPE_CHECKING: - from .abc import GuildChannel + from typing_extensions import Self + + from .abc import GuildChannel, Snowflake from .emoji import Emoji from .guild import Guild from .partial_emoji import PartialEmoji @@ -53,19 +50,23 @@ if TYPE_CHECKING: from .types.onboarding import ( Prompt as PromptPayload, PromptOption as PromptOptionPayload, + CreatePromptOption as CreatePromptOptionPayload, Onboarding as OnboardingPayload, ) from .state import ConnectionState -class PartialOnboardingPromptOption: - """Represents a partial onboarding prompt option, these are used in the creation - of an :class:`OnboardingPrompt` via :meth:`Guild.edit_onboarding`. +class OnboardingPromptOption(Hashable): + """Represents a onboarding prompt option. + + This can be manually created for :meth:`Guild.edit_onboarding`. .. versionadded:: 2.6 Attributes ----------- + id: :class:`int` + The ID of this prompt option. If this was manually created then the ID will be ``0``. title: :class:`str` The title of this prompt option. description: Optional[:class:`str`] @@ -73,126 +74,123 @@ class PartialOnboardingPromptOption: 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. + The IDs of the channels the user will be added to if this option is selected. role_ids: Set[:class:`int`] - The IDs of the roles given to the user if this option is selected. + The IDs of the roles the user will be given if this option is selected. """ __slots__ = ( 'title', 'emoji', 'description', + 'id', 'channel_ids', 'role_ids', + '_guild', + '_cs_channels', + '_cs_roles', ) def __init__( self, + *, title: str, emoji: Union[Emoji, PartialEmoji, str] = MISSING, description: Optional[str] = None, - channel_ids: Iterable[int] = MISSING, - role_ids: Iterable[int] = MISSING, + channels: Iterable[Union[Snowflake, int]] = MISSING, + roles: Iterable[Union[Snowflake, int]] = MISSING, ) -> None: + self.id: int = 0 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 self.emoji is not MISSING: - 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, - } - else: - emoji_payload = {} - - 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.6 + self.emoji: Optional[Union[Emoji, PartialEmoji]] = ( + PartialEmoji.from_str(emoji) if isinstance(emoji, str) else emoji if emoji is not MISSING else None + ) - Attributes - ----------- - id: :class:`int` - The ID of this prompt option. - guild: :class:`Guild` - The guild the onboarding prompt option is related to. - 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. - """ + self.channel_ids: Set[int] = ( + {c.id if not isinstance(c, int) else c for c in channels} if channels is not MISSING else set() + ) + self.role_ids: Set[int] = {c.id if not isinstance(c, int) else c for c in roles} if roles is not MISSING else set() + self._guild: Optional[Guild] = None - __slots__ = ( - '_state', - '_cs_channels', - '_cs_roles', - 'guild', - 'id', - ) + def __repr__(self) -> str: + return f'' - def __init__(self, *, data: PromptOptionPayload, state: ConnectionState, guild: Guild) -> None: - self._state: ConnectionState = state - self.guild: Guild = guild - self.id: int = int(data['id']) - super().__init__( + @classmethod + def from_dict(cls, *, data: PromptOptionPayload, state: ConnectionState, guild: Guild) -> Self: + instance = cls( 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']], + emoji=state.get_emoji_from_partial_payload(data['emoji']) if 'emoji' in data else MISSING, + channels=[int(id) for id in data['channel_ids']], + roles=[int(id) for id in data['role_ids']], ) + instance._guild = guild + instance.id = int(data['id']) + return instance - def __repr__(self) -> str: - return f'' + def to_dict( + self, + ) -> CreatePromptOptionPayload: + res: CreatePromptOptionPayload = { + 'title': self.title, + 'description': self.description, + 'channel_ids': list(self.channel_ids), + 'role_ids': list(self.role_ids), + } + if self.emoji: + res.update((self.emoji._to_partial())._to_onboarding_prompt_option_payload()) # type: ignore + return res + + @property + def guild(self) -> Guild: + """:class:`Guild`: The guild this prompt option is related to. + + Raises + ------- + ValueError + If this prompt option is manually created. + """ + if self._guild is None: + raise ValueError('This prompt option is manually created therefore has no guild.') + return self._guild @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.""" + """List[Union[:class:`abc.GuildChannel`, :class:`Thread`]]: The list of channels which will be made visible if this option is selected. + + Raises + ------- + ValueError + IF the prompt option is manually created, therefore has no guild. + """ it = filter(None, map(self.guild._resolve_channel, self.channel_ids)) return utils._unique(it) @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.""" + """List[:class:`Role`]: The list of roles given to the user if this option is selected. + + Raises + ------- + ValueError + If the prompt option is manually created, therefore has no guild. + """ it = filter(None, map(self.guild.get_role, self.role_ids)) return utils._unique(it) - def to_dict(self) -> PromptOptionPayload: - return super().to_dict(id=self.id) +class OnboardingPrompt: + """Represents a onboarding prompt. -class PartialOnboardingPrompt: - """Represents a partial onboarding prompt, these are used in the creation - of an :class:`Onboarding` via :meth:`Guild.edit_onboarding`. + This can be manually created for :meth:`Guild.edit_onboarding`. .. versionadded:: 2.6 Attributes ----------- + id: :class:`int` + The ID of this prompt. If this was manually created then the ID will be ``0 type: :class:`OnboardingPromptType` The type of this prompt. title: :class:`str` @@ -208,12 +206,14 @@ class PartialOnboardingPrompt: """ __slots__ = ( + 'id', 'type', 'title', 'options', 'single_select', 'required', 'in_onboarding', + '_guild', ) def __init__( @@ -221,18 +221,40 @@ class PartialOnboardingPrompt: *, type: OnboardingPromptType, title: str, - options: List[PartialOnboardingPromptOption], + options: List[OnboardingPromptOption], single_select: bool = True, required: bool = True, in_onboarding: bool = True, ) -> None: - self.type: OnboardingPromptType = try_enum(OnboardingPromptType, type) + self.id: int = 0 + self.type: OnboardingPromptType = type self.title: str = title - self.options: List[PartialOnboardingPromptOption] = options + self.options: List[OnboardingPromptOption] = options self.single_select: bool = single_select self.required: bool = required self.in_onboarding: bool = in_onboarding + self._guild: Optional[Guild] = None + + def __repr__(self) -> str: + return f'' + + @classmethod + def from_dict(cls, *, data: PromptPayload, state: ConnectionState, guild: Guild) -> Self: + instance = cls( + type=try_enum(OnboardingPromptType, data['type']), + title=data['title'], + options=[ + OnboardingPromptOption.from_dict(data=option_data, state=state, guild=guild) # type: ignore + for option_data in data['options'] + ], + single_select=data['single_select'], + required=data['required'], + in_onboarding=data['in_onboarding'], + ) + instance.id = int(data['id']) + return instance + def to_dict(self, *, id: int) -> PromptPayload: return { 'id': id, @@ -244,61 +266,22 @@ class PartialOnboardingPrompt: 'in_onboarding': self.in_onboarding, } + @property + def guild(self) -> Guild: + """:class:`Guild`: The guild this prompt is related to. -class OnboardingPrompt(PartialOnboardingPrompt, Hashable): - """Represents an onboarding prompt. - - .. versionadded:: 2.6 - - Attributes - ----------- - id: :class:`int` - 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 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. - required: :class:`bool` - Whether this prompt is required to complete the onboarding flow. - in_onboarding: :class:`bool` - Whether this prompt is part of the onboarding flow. - """ - - options: List[OnboardingPromptOption] - - __slots__ = ( - '_state', - 'guild', - 'id', - 'title', - 'options', - 'single_select', - 'required', - 'in_onboarding', - 'type', - ) + Raises + ------ + ValueError + If this prompt is manually created, therefore has no guild. + """ + if self._guild is None: + raise ValueError('This prompt is manually created therefore has no guild.') + return self._guild - def __init__(self, *, data: PromptPayload, state: ConnectionState, guild: Guild): - self._state: ConnectionState = state - self.guild: Guild = guild - self.id: int = int(data['id']) - 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'' + def get_option(self, option_id: int, /) -> Optional[OnboardingPromptOption]: + """Optional[:class:`OnboardingPromptOption`]: The option with the given ID, if found.""" + return next((option for option in self.options if option.id == option_id), None) class Onboarding: @@ -335,7 +318,7 @@ class Onboarding: 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'] + OnboardingPrompt.from_dict(data=prompt_data, state=state, guild=guild) for prompt_data in data['prompts'] ] self.enabled: bool = data['enabled'] self.mode: OnboardingMode = try_enum(OnboardingMode, data.get('mode', 0)) @@ -348,3 +331,7 @@ class Onboarding: """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) + + def get_prompt(self, prompt_id: int, /) -> Optional[OnboardingPrompt]: + """Optional[:class:`OnboardingPrompt`]: The prompt with the given ID, if found.""" + return next((prompt for prompt in self.prompts if prompt.id == prompt_id), None) diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index 7d366949c..502202330 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -167,6 +167,12 @@ class PartialEmoji(_EmojiTag, AssetMixin): return {'emoji_id': self.id, 'emoji_name': None} return {'emoji_id': None, 'emoji_name': self.name} + def _to_onboarding_prompt_option_payload(self) -> Dict[str, Any]: + if self.id is not None: + return {'emoji_id': self.id, 'emoji_name': self.name, 'emoji_animated': self.animated} + + return {'emoji_name': self.name} + @classmethod def with_state( cls, diff --git a/discord/types/onboarding.py b/discord/types/onboarding.py index 4945fb18e..64f9c45c8 100644 --- a/discord/types/onboarding.py +++ b/discord/types/onboarding.py @@ -23,29 +23,40 @@ DEALINGS IN THE SOFTWARE. """ from __future__ import annotations - -from typing import Literal, Optional, TypedDict, List +from typing import TYPE_CHECKING, Literal, Optional, TypedDict, List, Union from .emoji import PartialEmoji from .snowflake import Snowflake +if TYPE_CHECKING: + from typing_extensions import NotRequired + PromptType = Literal[0, 1] OnboardingMode = Literal[0, 1] -class PromptOption(TypedDict): - id: Snowflake +class _PromptOption(TypedDict): channel_ids: List[Snowflake] role_ids: List[Snowflake] - emoji: PartialEmoji title: str description: Optional[str] +class CreatePromptOption(_PromptOption): + emoji_id: NotRequired[Snowflake] + emoji_name: NotRequired[str] + emoji_animated: NotRequired[bool] + + +class PromptOption(_PromptOption): + id: Snowflake + emoji: NotRequired[PartialEmoji] + + class Prompt(TypedDict): id: Snowflake - options: List[PromptOption] + options: List[Union[PromptOption, CreatePromptOption]] title: str single_select: bool required: bool diff --git a/docs/api.rst b/docs/api.rst index 54ae9c65e..f5510dc18 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3157,6 +3157,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.enabled` - :attr:`~AuditLogDiff.default_channels` - :attr:`~AuditLogDiff.prompts` + - :attr:`~AuditLogDiff.mode` .. versionadded:: 2.6 @@ -3169,6 +3170,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.enabled` - :attr:`~AuditLogDiff.default_channels` - :attr:`~AuditLogDiff.prompts` + - :attr:`~AuditLogDiff.mode` .. versionadded:: 2.6 @@ -4632,7 +4634,7 @@ AuditLogDiff The trigger for the automod rule. - .. note :: + .. note:: The :attr:`~AutoModTrigger.type` of the trigger may be incorrect. Some attributes such as :attr:`~AutoModTrigger.keyword_filter`, :attr:`~AutoModTrigger.regex_patterns`, @@ -4644,7 +4646,7 @@ AuditLogDiff The actions to take when an automod rule is triggered. - :type: List[AutoModRuleAction] + :type: List[:class:`AutoModRuleAction`] .. attribute:: exempt_roles @@ -4798,61 +4800,6 @@ AuditLogDiff :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 From 342e587bcfd879a6075639831f72d4bd695af4f0 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:31:09 +0200 Subject: [PATCH 33/39] Doc AuditLogDiff.mode and remove outdated --- docs/api.rst | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index f5510dc18..75785b5bf 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4800,6 +4800,14 @@ AuditLogDiff :type: :class:`bool` + .. attribute:: mode + + The onboarding constraint mode. + + See also :attr:`Onboarding.mode` + + :type: :class:`OnboardingMode` + .. this is currently missing the following keys: reason and application_id I'm not sure how to port these @@ -5432,14 +5440,6 @@ Onboarding .. autoclass:: Onboarding() :members: -PartialOnboardingPrompt -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. attributetable:: PartialOnboardingPrompt - -.. autoclass:: PartialOnboardingPrompt - :members: - OnboardingPrompt ~~~~~~~~~~~~~~~~~ @@ -5448,13 +5448,6 @@ OnboardingPrompt .. autoclass:: OnboardingPrompt() :members: -PartialOnboardingPromptOption -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. attributetable:: PartialOnboardingPromptOption - -.. autoclass:: PartialOnboardingPromptOption - :members: OnboardingPromptOption ~~~~~~~~~~~~~~~~~~~~~~~ From 7761beb1410f320ae27203de92acfbbfea4e2287 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:33:16 +0200 Subject: [PATCH 34/39] Update onboarding.py --- discord/onboarding.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 6beec0c75..3509c4afb 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -149,10 +149,10 @@ class OnboardingPromptOption(Hashable): Raises ------- ValueError - If this prompt option is manually created. + If the prompt option was created manually. """ if self._guild is None: - raise ValueError('This prompt option is manually created therefore has no guild.') + raise ValueError('This prompt does not have an associated guild because it was created manually.') return self._guild @cached_slot_property('_cs_channels') @@ -273,10 +273,10 @@ class OnboardingPrompt: Raises ------ ValueError - If this prompt is manually created, therefore has no guild. + If the prompt was created manually. """ if self._guild is None: - raise ValueError('This prompt is manually created therefore has no guild.') + raise ValueError('This prompt does not have an associated guild because it was created manually.') return self._guild def get_option(self, option_id: int, /) -> Optional[OnboardingPromptOption]: From eb5209a6dacecce8cba0b48d7d814b875a181e21 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:35:44 +0200 Subject: [PATCH 35/39] Update onboarding.py --- discord/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 3509c4afb..0ea0fd9cd 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -195,7 +195,7 @@ class OnboardingPrompt: The type of this prompt. title: :class:`str` The title of this prompt. - options: List[:class:`PartialOnboardingPromptOption`] + options: List[:class:`OnboardingPromptOption`] The options of this prompt. single_select: :class:`bool` Whether this prompt is single select. From e90be265e7d546752a30c0c9e7673e50949fc76e Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:52:26 +0200 Subject: [PATCH 36/39] Document parameters for OnboardingPrompt(Option) --- discord/onboarding.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 0ea0fd9cd..4d3a33d58 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -63,6 +63,20 @@ class OnboardingPromptOption(Hashable): .. versionadded:: 2.6 + Parameters + ----------- + title: :class:`str` + The title of this prompt option. + emoji: Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`] + The emoji tied to this option. May be a custom emoji, or a unicode emoji. I + f this is a string, it will be converted to a :class:`PartialEmoji`. + description: Optional[:class:`str`] + The description of this prompt option. + channels: Iterable[Union[:class:`abc.Snowflake`, :class:`int`]] + The channels the user will be added to if this option is selected. + roles: Iterable[Union[:class:`abc.Snowflake`, :class:`int`]] + The roles the user will be given if this option is selected. + Attributes ----------- id: :class:`int` @@ -71,7 +85,7 @@ class OnboardingPromptOption(Hashable): 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`]] + emoji: Optional[Union[:class:`Emoji`, :class:`PartialEmoji`]] 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 the user will be added to if this option is selected. @@ -187,10 +201,28 @@ class OnboardingPrompt: .. versionadded:: 2.6 + Parameters + ----------- + type: :class:`OnboardingPromptType` + The type of this prompt. + title: :class:`str` + The title of this prompt. + options: List[:class:`OnboardingPromptOption`] + The options of this prompt. + single_select: :class:`bool` + Whether this prompt is single select. + Defaults to ``True``. + required: :class:`bool` + Whether this prompt is required. + Defaults to ``True``. + in_onboarding: :class:`bool` + Whether this prompt is in the onboarding flow. + Defaults to ``True``. + Attributes ----------- id: :class:`int` - The ID of this prompt. If this was manually created then the ID will be ``0 + The ID of this prompt. If this was manually created then the ID will be ``0``. type: :class:`OnboardingPromptType` The type of this prompt. title: :class:`str` From eeaabef8260ff9fa37d52d9944e0e21cea2f61b8 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:01:28 +0200 Subject: [PATCH 37/39] Update guild.py --- discord/guild.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/discord/guild.py b/discord/guild.py index cbf28ffc3..9f9f427b4 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4908,10 +4908,13 @@ class Guild(Hashable): ----------- prompts: List[:class:`OnboardingPrompt`] The prompts that will be shown to new members. + This overrides the existing prompts and its options. default_channels: List[:class:`abc.Snowflake`] The channels that will be used as the default channels for new members. + This overrides the existing default channels. enabled: :class:`bool` Whether the onboarding configuration is enabled. + This overrides the existing enabled state. mode: :class:`OnboardingMode` The mode that will be used for the onboarding configuration. reason: :class:`str` From 70578ebd56014714270e8d48f594a8b06abdebf8 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:34:46 +0200 Subject: [PATCH 38/39] Correct docstring for Guild.(edit_)onboarding --- discord/guild.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 9f9f427b4..5daded9db 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4876,9 +4876,6 @@ class Guild(Hashable): Fetches the onboarding configuration for this guild. - You must have :attr:`Permissions.manage_guild` and - :attr:`Permissions.manage_roles` to do this. - .. versionadded:: 2.6 Returns @@ -4902,6 +4899,9 @@ class Guild(Hashable): Edits the onboarding configuration for this guild. + You must have :attr:`Permissions.manage_guild` and + :attr:`Permissions.manage_roles` to do this. + .. versionadded:: 2.6 Parameters From 0921dc986f9956222424869151892a51fe7fe287 Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:39:52 +0200 Subject: [PATCH 39/39] Improve all reprs --- discord/onboarding.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/onboarding.py b/discord/onboarding.py index 4d3a33d58..d26258c16 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -128,7 +128,7 @@ class OnboardingPromptOption(Hashable): self._guild: Optional[Guild] = None def __repr__(self) -> str: - return f'' + return f'' @classmethod def from_dict(cls, *, data: PromptOptionPayload, state: ConnectionState, guild: Guild) -> Self: @@ -269,7 +269,7 @@ class OnboardingPrompt: self._guild: Optional[Guild] = None def __repr__(self) -> str: - return f'' + return f'' @classmethod def from_dict(cls, *, data: PromptPayload, state: ConnectionState, guild: Guild) -> Self: @@ -356,7 +356,7 @@ class Onboarding: self.mode: OnboardingMode = try_enum(OnboardingMode, data.get('mode', 0)) def __repr__(self) -> str: - return f'' + return f'' @cached_slot_property('_cs_default_channels') def default_channels(self) -> List[Union[GuildChannel, Thread]]: