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 ~~~~~~~~~~~