Skip to content

feat: Add guild onboarding settings #1646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions interactions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@
NoArgumentConverter,
NSFWLevel,
open_file,
Onboarding,
OnboardingMode,
OnboardingPrompt,
OnboardingPromptOption,
OnboardingPromptType,
OptionType,
OrTrigger,
OverwriteType,
Expand Down Expand Up @@ -564,6 +569,11 @@
"NoArgumentConverter",
"NSFWLevel",
"open_file",
"Onboarding",
"OnboardingMode",
"OnboardingPrompt",
"OnboardingPromptOption",
"OnboardingPromptType",
"OptionType",
"OrTrigger",
"OverwriteType",
Expand Down
36 changes: 36 additions & 0 deletions interactions/api/http/http_requests/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
10 changes: 10 additions & 0 deletions interactions/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@
NSFWLevel,
open_file,
OverwriteType,
Onboarding,
OnboardingMode,
OnboardingPrompt,
OnboardingPromptOption,
OnboardingPromptType,
ParagraphText,
PartialEmoji,
PermissionOverwrite,
Expand Down Expand Up @@ -493,6 +498,11 @@
"NoArgumentConverter",
"NSFWLevel",
"open_file",
"Onboarding",
"OnboardingMode",
"OnboardingPrompt",
"OnboardingPromptOption",
"OnboardingPromptType",
"OptionType",
"OrTrigger",
"OverwriteType",
Expand Down
8 changes: 8 additions & 0 deletions interactions/models/discord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
MessageType,
MFALevel,
NSFWLevel,
OnboardingMode,
OnboardingPromptType,
OverwriteType,
Permissions,
PremiumTier,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -280,6 +283,11 @@
"Modal",
"NSFWLevel",
"open_file",
"Onboarding",
"OnboardingMode",
"OnboardingPrompt",
"OnboardingPromptOption",
"OnboardingPromptType",
"OverwriteType",
"ParagraphText",
"PartialEmoji",
Expand Down
18 changes: 18 additions & 0 deletions interactions/models/discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"MessageType",
"MFALevel",
"NSFWLevel",
"OnboardingMode",
"OnboardingPromptType",
"OverwriteType",
"Permissions",
"PremiumTier",
Expand Down Expand Up @@ -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."""

Expand Down
11 changes: 11 additions & 0 deletions interactions/models/discord/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"""
Expand Down
179 changes: 179 additions & 0 deletions interactions/models/discord/onboarding.py
Original file line number Diff line number Diff line change
@@ -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)