From 1f713f6fc21ad2a3bd3ab9b1a0cf7d801b5299a5 Mon Sep 17 00:00:00 2001 From: david-why Date: Sat, 30 Mar 2024 09:23:31 +0800 Subject: [PATCH] feat: Add guild onboarding settings --- interactions/__init__.py | 10 ++ interactions/api/http/http_requests/guild.py | 36 ++++ interactions/models/__init__.py | 10 ++ interactions/models/discord/__init__.py | 8 + interactions/models/discord/enums.py | 18 ++ interactions/models/discord/guild.py | 11 ++ interactions/models/discord/onboarding.py | 179 +++++++++++++++++++ 7 files changed, 272 insertions(+) create mode 100644 interactions/models/discord/onboarding.py diff --git a/interactions/__init__.py b/interactions/__init__.py index 50cdda95c..b32766ccd 100644 --- a/interactions/__init__.py +++ b/interactions/__init__.py @@ -224,6 +224,11 @@ NoArgumentConverter, NSFWLevel, open_file, + Onboarding, + OnboardingMode, + OnboardingPrompt, + OnboardingPromptOption, + OnboardingPromptType, OptionType, OrTrigger, OverwriteType, @@ -564,6 +569,11 @@ "NoArgumentConverter", "NSFWLevel", "open_file", + "Onboarding", + "OnboardingMode", + "OnboardingPrompt", + "OnboardingPromptOption", + "OnboardingPromptType", "OptionType", "OrTrigger", "OverwriteType", diff --git a/interactions/api/http/http_requests/guild.py b/interactions/api/http/http_requests/guild.py index 216cf40c9..6e95a08fe 100644 --- a/interactions/api/http/http_requests/guild.py +++ b/interactions/api/http/http_requests/guild.py @@ -1041,3 +1041,39 @@ async def delete_auto_moderation_rule( reason=reason, ) return cast(dict, result) + + async def get_guild_onboarding(self, guild_id: "Snowflake_Type") -> discord_typings.GuildOnboardingData: + """ + Get the guild's onboarding settings. + + Args: + guild_id: The ID of the guild + + Returns: + The guild's onboarding object + + """ + result = await self.request(Route("GET", "/guilds/{guild_id}/onboarding", guild_id=guild_id)) + return cast(discord_typings.GuildOnboardingData, result) + + async def modify_guild_onboarding( + self, guild_id: "Snowflake_Type", payload: dict, reason: str | None = None + ) -> discord_typings.GuildOnboardingData: + """ + Modify the guild's onboarding settings. + + Args: + guild_id: The ID of the guild + payload: A dict representing the modified Onboarding + reason: The reason for this action + + Returns: + The updated onboarding object + + """ + result = await self.request( + Route("PUT", "/guilds/{guild_id}/onboarding", guild_id=guild_id), + payload=payload, + reason=reason, + ) + return cast(discord_typings.GuildOnboardingData, result) diff --git a/interactions/models/__init__.py b/interactions/models/__init__.py index e66a5a5aa..dda9f482d 100644 --- a/interactions/models/__init__.py +++ b/interactions/models/__init__.py @@ -110,6 +110,11 @@ NSFWLevel, open_file, OverwriteType, + Onboarding, + OnboardingMode, + OnboardingPrompt, + OnboardingPromptOption, + OnboardingPromptType, ParagraphText, PartialEmoji, PermissionOverwrite, @@ -493,6 +498,11 @@ "NoArgumentConverter", "NSFWLevel", "open_file", + "Onboarding", + "OnboardingMode", + "OnboardingPrompt", + "OnboardingPromptOption", + "OnboardingPromptType", "OptionType", "OrTrigger", "OverwriteType", diff --git a/interactions/models/discord/__init__.py b/interactions/models/discord/__init__.py index 2168dd0c9..fcedbec6e 100644 --- a/interactions/models/discord/__init__.py +++ b/interactions/models/discord/__init__.py @@ -98,6 +98,8 @@ MessageType, MFALevel, NSFWLevel, + OnboardingMode, + OnboardingPromptType, OverwriteType, Permissions, PremiumTier, @@ -149,6 +151,7 @@ process_message_reference, ) from .modal import InputText, Modal, ParagraphText, ShortText, TextStyles +from .onboarding import Onboarding, OnboardingPrompt, OnboardingPromptOption from .reaction import Reaction, ReactionUsers from .role import Role from .scheduled_event import ScheduledEvent @@ -280,6 +283,11 @@ "Modal", "NSFWLevel", "open_file", + "Onboarding", + "OnboardingMode", + "OnboardingPrompt", + "OnboardingPromptOption", + "OnboardingPromptType", "OverwriteType", "ParagraphText", "PartialEmoji", diff --git a/interactions/models/discord/enums.py b/interactions/models/discord/enums.py index 64703dc43..72443036c 100644 --- a/interactions/models/discord/enums.py +++ b/interactions/models/discord/enums.py @@ -32,6 +32,8 @@ "MessageType", "MFALevel", "NSFWLevel", + "OnboardingMode", + "OnboardingPromptType", "OverwriteType", "Permissions", "PremiumTier", @@ -720,6 +722,22 @@ class MentionType(str, Enum): USERS = "users" +class OnboardingMode(CursedIntEnum): + """Defines the criteria used to satisfy Onboarding constraints that are required for enabling.""" + + ONBOARDING_DEFAULT = 0 + """Counts only Default Channels towards constraints""" + ONBOARDING_ADVANCED = 1 + """Counts Default Channels and Questions towards constraints""" + + +class OnboardingPromptType(CursedIntEnum): + """Types of Onboarding prompts.""" + + MULTIPLE_CHOICE = 0 + DROPDOWN = 1 + + class OverwriteType(CursedIntEnum): """Types of permission overwrite.""" diff --git a/interactions/models/discord/guild.py b/interactions/models/discord/guild.py index e29e9887c..cce6914db 100644 --- a/interactions/models/discord/guild.py +++ b/interactions/models/discord/guild.py @@ -26,6 +26,7 @@ ) from interactions.models.discord.auto_mod import AutoModRule, BaseAction, BaseTrigger from interactions.models.discord.file import UPLOADABLE_TYPE +from interactions.models.discord.onboarding import Onboarding from interactions.models.misc.iterator import AsyncIterator from .base import ClientObject, DiscordObject @@ -2035,6 +2036,16 @@ async def fetch_voice_regions(self) -> List["models.VoiceRegion"]: regions_data = await self._client.http.get_guild_voice_regions(self.id) return models.VoiceRegion.from_list(regions_data) + async def fetch_onboarding(self) -> Onboarding: + """ + Fetches the guild's onboarding settings. + + Returns: + The guild's onboarding settings. + + """ + return Onboarding.from_dict(await self._client.http.get_guild_onboarding(self.id), self._client) + @property def gui_sorted_channels(self) -> list["models.TYPE_GUILD_CHANNEL"]: """Return this guilds channels sorted by their gui positions""" diff --git a/interactions/models/discord/onboarding.py b/interactions/models/discord/onboarding.py new file mode 100644 index 000000000..718c27386 --- /dev/null +++ b/interactions/models/discord/onboarding.py @@ -0,0 +1,179 @@ +from typing import Any, Dict, List, Optional, Union + +import attrs + +from interactions.client.const import MISSING, Absent +from interactions.client.mixins.serialization import DictSerializationMixin +from interactions.client.utils.attr_converters import optional +from interactions.models.discord.base import ClientObject +from interactions.models.discord.emoji import PartialEmoji, process_emoji +from interactions.models.discord.enums import OnboardingMode, OnboardingPromptType +from interactions.models.discord.snowflake import ( + Snowflake, + Snowflake_Type, + SnowflakeObject, + to_snowflake, + to_snowflake_list, +) + +__all__ = ("OnboardingPromptOption", "OnboardingPrompt", "Onboarding") + + +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class OnboardingPromptOption(SnowflakeObject, DictSerializationMixin): + channel_ids: List["Snowflake"] = attrs.field(repr=False, converter=to_snowflake_list) + """IDs for channels a member is added to when the option is selected""" + role_ids: List["Snowflake"] = attrs.field(repr=False, converter=to_snowflake_list) + """IDs for roles assigned to a member when the option is selected""" + title: str = attrs.field(repr=False) + """Title of the option""" + description: Optional[str] = attrs.field(repr=False, default=None) + """Description of the option""" + emoji: Optional[PartialEmoji] = attrs.field(repr=False, default=None, converter=optional(PartialEmoji.from_dict)) + """Emoji of the option""" + + # this method is here because Discord needs the id field to be present in the payload + @classmethod + def create( + cls, + title: str, + *, + channel_ids: Optional[List[Snowflake_Type]] = None, + role_ids: Optional[List[Snowflake_Type]] = None, + description: Optional[str] = None, + emoji: Optional[Union[PartialEmoji, dict, str]] = None, + ) -> "OnboardingPromptOption": + """ + Creates a new Onboarding prompt option object. + + Args: + title: Title of the option + channel_ids: Channel IDs that this option represents + role_ids: Role IDs that this option represents + description: Description of the option + emoji: Emoji of the option + + Returns: + The newly created OnboardingPromptOption object + + """ + return cls( + id=0, + channel_ids=channel_ids or [], + role_ids=role_ids or [], + title=title, + description=description, + emoji=process_emoji(emoji), + ) + + def as_dict(self) -> Dict[str, Any]: + data = { + "id": self.id, + "channel_ids": self.channel_ids, + "role_ids": self.role_ids, + "title": self.title, + "description": self.description, + } + # use the separate fields when sending to Discord + if self.emoji is not None: + data["emoji_id"] = self.emoji.id + data["emoji_name"] = self.emoji.name + data["emoji_animated"] = self.emoji.animated + return data + + +@attrs.define(eq=False, order=False, hash=False, kw_only=False) +class OnboardingPrompt(SnowflakeObject, DictSerializationMixin): + type: OnboardingPromptType = attrs.field(repr=False, converter=OnboardingPromptType) + """Type of the prompt""" + options: List[OnboardingPromptOption] = attrs.field(repr=False, converter=OnboardingPromptOption.from_list) + """Options available in the prompt""" + title: str = attrs.field(repr=False) + """Title of the prompt""" + single_select: bool = attrs.field(repr=False) + """Whether users are limited to selecting one option for the prompt""" + required: bool = attrs.field(repr=False) + """Whether users are required to complete this prompt""" + in_onboarding: bool = attrs.field(repr=False) + """Whether the prompt is present in the onboarding flow; otherwise it is only in the Channels & Roles tab""" + + @classmethod + def create( + cls, + *, + type: Union[OnboardingPromptType, int] = OnboardingPromptType.MULTIPLE_CHOICE, + options: List[OnboardingPromptOption], + title: str, + single_select: bool = False, + required: bool = False, + in_onboarding: bool = True, + ) -> "OnboardingPrompt": + """ + Creates a new Onboarding prompt object. + + Args: + type: Type of the prompt + options: Options available in the prompt + title: Title of the prompt + single_select: Whether users are limited to selecting one option for the prompt + required: Whether users are required to complete this prompt + in_onboarding: Whether the prompt is present in the onboarding flow; otherwise it is only in the Channels & Roles tab + + Returns: + The newly created OnboardingPrompt object + + """ + return cls( + id=0, + type=type, + options=options, + title=title, + single_select=single_select, + required=required, + in_onboarding=in_onboarding, + ) + + +@attrs.define(eq=False, order=False, hash=False, kw_only=False) +class Onboarding(ClientObject): + """Represents the onboarding flow for a guild.""" + + guild_id: Snowflake = attrs.field(repr=False, converter=to_snowflake) + """ID of the guild this onboarding is part of""" + prompts: list[OnboardingPrompt] = attrs.field(repr=False, converter=OnboardingPrompt.from_list) + """Prompts shown during onboarding and in customize community""" + default_channel_ids: list[Snowflake] = attrs.field(repr=False, converter=to_snowflake_list) + """Channel IDs that members get opted into automatically""" + enabled: bool = attrs.field(repr=False) + """Whether onboarding is enabled in the guild""" + mode: OnboardingMode = attrs.field(repr=False, converter=OnboardingMode) + """Current mode of onboarding""" + + async def edit( + self, + *, + prompts: Absent[List[OnboardingPrompt]] = MISSING, + default_channel_ids: Absent[list[Snowflake_Type]] = MISSING, + enabled: Absent[bool] = MISSING, + mode: Absent[Union[OnboardingMode, int]] = MISSING, + reason: Absent[str] = MISSING, + ) -> None: + """ + Edits this Onboarding flow. + + Args: + prompts: Prompts shown during onboarding and in customize community + default_channel_ids: Channel IDs that members get opted into automatically + enabled: Whether onboarding is enabled in the guild + mode: Current mode of onboarding + reason: The reason for this change + + """ + payload = { + "prompts": prompts, + "default_channel_ids": default_channel_ids, + "enabled": enabled, + "mode": mode, + } + data = await self._client.http.modify_guild_onboarding(self.guild_id, payload, reason) + self.update_from_dict(data)