From 40b1009dd3c97a512fd65590373451a7a6201be9 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 6 Jan 2023 15:42:11 -0300 Subject: [PATCH 01/99] onboarding temp --- changelog/999.feature.rst | 8 + disnake/__init__.py | 1 + disnake/audit_logs.py | 34 ++++ disnake/enums.py | 14 ++ disnake/flags.py | 87 ++++++++ disnake/guild.py | 79 +++++++- disnake/http.py | 26 +++ disnake/member.py | 36 ++++ disnake/onboarding.py | 341 ++++++++++++++++++++++++++++++++ disnake/types/gateway.py | 1 + disnake/types/member.py | 1 + disnake/types/onboarding.py | 64 ++++++ docs/api.rst | 157 +++++++++++++++ tests/interactions/test_base.py | 1 + tests/test_onboarding.py | 127 ++++++++++++ 15 files changed, 976 insertions(+), 1 deletion(-) create mode 100644 changelog/999.feature.rst create mode 100644 disnake/onboarding.py create mode 100644 disnake/types/onboarding.py create mode 100644 tests/test_onboarding.py diff --git a/changelog/999.feature.rst b/changelog/999.feature.rst new file mode 100644 index 0000000000..3cf3c6efbd --- /dev/null +++ b/changelog/999.feature.rst @@ -0,0 +1,8 @@ +Implement Onboarding. + - Add :class:`Onboarding`, :class:`OnboardingPrompt` and :class:`OnboardingPromptOption`. + - Add :meth:`Guild.get_onboarding`. + - Add new member flags. + - Add :class:`MemberFlags`. + - Add :attr:`Member.flags`. + - Add ``flags`` and ``verified`` parameters to :meth:`Member.edit`. + - Add relevant :class:`AuditLogDiff` information. diff --git a/disnake/__init__.py b/disnake/__init__.py index 3c41f1c09c..0d9858010a 100644 --- a/disnake/__init__.py +++ b/disnake/__init__.py @@ -52,6 +52,7 @@ from .mentions import * from .message import * from .object import * +from .onboarding import * from .partial_emoji import * from .permissions import * from .player import * diff --git a/disnake/audit_logs.py b/disnake/audit_logs.py index d45749c19b..ec16ed6c2c 100644 --- a/disnake/audit_logs.py +++ b/disnake/audit_logs.py @@ -26,6 +26,7 @@ from .invite import Invite from .mixins import Hashable from .object import Object +from .onboarding import OnboardingPrompt, OnboardingPromptOption from .partial_emoji import PartialEmoji from .permissions import PermissionOverwrite, Permissions from .threads import ForumTag, Thread @@ -63,6 +64,10 @@ DefaultReaction as DefaultReactionPayload, PermissionOverwrite as PermissionOverwritePayload, ) + from .types.onboarding import ( + OnboardingPrompt as OnboardingPromptPayload, + OnboardingPromptOption as OnboardingPromptOptionPayload, + ) from .types.role import Role as RolePayload from .types.snowflake import Snowflake from .types.threads import ForumTag as ForumTagPayload @@ -287,6 +292,24 @@ def _transform_default_reaction( ) +def _transform_onboarding_prompt_option( + entry: AuditLogEntry, data: Optional[OnboardingPromptOptionPayload] +) -> Optional[OnboardingPromptOption]: + if data is None: + return None + + return OnboardingPromptOption._from_dict(data=data, state=entry._state) + + +def _transform_onboarding_prompt( + entry: AuditLogEntry, data: Optional[OnboardingPromptPayload] +) -> Optional[OnboardingPrompt]: + if data is None: + return None + + return OnboardingPrompt._from_dict(data=data, state=entry._state) + + class AuditLogDiff: def __len__(self) -> int: return len(self.__dict__) @@ -362,6 +385,9 @@ class AuditLogChanges: "available_tags": (None, _list_transformer(_transform_tag)), "default_reaction_emoji": ("default_reaction", _transform_default_reaction), "default_sort_order": (None, _enum_transformer(enums.ThreadSortOrder)), + "options": (None, _list_transformer(_transform_onboarding_prompt_option)), + "prompts": (None, _list_transformer(_transform_onboarding_prompt)), + "default_channel_ids": ("default_channels", _list_transformer(_transform_channel)), } # fmt: on @@ -837,3 +863,11 @@ def _convert_target_application_command_or_integration( def _convert_target_automod_rule(self, target_id: int) -> Union[AutoModRule, Object]: return self._automod_rules.get(target_id) or Object(id=target_id) + + def _convert_target_onboarding_prompt(self, target_id: int) -> Object: + # Here we have two options: + # 1. Fecth the onboarding and get the prompt (unnecessary) + # 2. Store Onboarding\s when they're fetched (since there are no gateway events for it yet?) + # in ConnectionState and get it here. + # For now I'll return Object. + return Object(id=target_id) diff --git a/disnake/enums.py b/disnake/enums.py index 774e5d33d9..0ee72ac698 100644 --- a/disnake/enums.py +++ b/disnake/enums.py @@ -378,6 +378,11 @@ class AuditLogAction(Enum): automod_block_message = 143 automod_send_alert_message = 144 automod_timeout = 145 + onboarding_prompt_create = 163 + onboarding_prompt_update = 164 + onboarding_prompt_delete = 165 + onboarding_create = 166 + onboarding_update = 167 # fmt: on @property @@ -438,6 +443,11 @@ def category(self) -> Optional[AuditLogActionCategory]: AuditLogAction.automod_block_message: None, AuditLogAction.automod_send_alert_message: None, AuditLogAction.automod_timeout: None, + 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] @@ -483,6 +493,10 @@ def target_type(self) -> Optional[str]: return "automod_rule" elif v < 146: return "user" + elif v < 166: + return "onboarding_prompt" + elif v < 168: + return "onboarding" else: return None diff --git a/disnake/flags.py b/disnake/flags.py index 7c63acfd67..9240de00e9 100644 --- a/disnake/flags.py +++ b/disnake/flags.py @@ -39,6 +39,7 @@ "ApplicationFlags", "ChannelFlags", "AutoModKeywordPresets", + "MemberFlags", ) BF = TypeVar("BF", bound="BaseFlags") @@ -2125,3 +2126,89 @@ def slurs(self): (contains insults or words that may be considered hate speech). """ return 1 << 3 + + +class MemberFlags(BaseFlags): + """Wraps up Discord Member flags. + + .. versionadded:: 2.8 + + .. container:: operations + + .. describe:: x == y + + Checks if two MemberFlags instances are equal. + .. describe:: x != y + + Checks if two MemberFlags instances are not equal. + .. describe:: x <= y + + Checks if an MemberFlags instance is a subset of another MemberFlags instance. + .. describe:: x >= y + + Checks if an MemberFlags instance is a superset of another MemberFlags instance. + .. describe:: x < y + + Checks if an MemberFlags instance is a strict subset of another MemberFlags instance. + .. describe:: x > y + + Checks if an MemberFlags instance is a strict superset of another MemberFlags instance. + .. describe:: x | y, x |= y + + Returns a new MemberFlags instance with all enabled flags from both x and y. + (Using ``|=`` will update in place). + .. describe:: x & y, x &= y + + Returns a new MemberFlags instance with only flags enabled on both x and y. + (Using ``&=`` will update in place). + .. describe:: x ^ y, x ^= y + + Returns a new MemberFlags instance with only flags enabled on one of x or y, but not both. + (Using ``^=`` will update in place). + .. describe:: ~x + + Returns a new MemberFlags instance with all flags from x inverted. + .. describe:: hash(x) + + Return the flag's hash. + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + + Additionally supported are a few operations on class attributes. + + .. describe:: MemberFlags.y | MemberFlags.z, MemberFlags(y=True) | MemberFlags.z + + Returns a MemberFlags instance with all provided flags enabled. + + .. describe:: ~MemberFlags.y + + Returns a MemberFlags instance with all flags except ``y`` inverted from their default value. + + Attributes + ---------- + values: :class:`int` + The raw values. You should query flags via the properties + rather than using these raw values. + """ + + __slots__ = () + + if TYPE_CHECKING: + + @_generated + def __init__(self, *, completed_onboarding: bool, bypasses_verification: bool) -> None: + ... + + @flag_value + def completed_onboarding(self): + """:class:`bool`: Returns ``True`` if the member has completed onboarding.""" + return 1 << 1 + + @flag_value + def bypasses_verification(self): + """:class:`bool`: Returns ``True`` if the member is able to bypass verification.""" + return 1 << 2 diff --git a/disnake/guild.py b/disnake/guild.py index 94d6ca2bd3..a78cca18c3 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -58,13 +58,14 @@ ) from .errors import ClientException, HTTPException, InvalidData from .file import File -from .flags import SystemChannelFlags +from .flags import MemberFlags, SystemChannelFlags from .guild_scheduled_event import GuildScheduledEvent, GuildScheduledEventMetadata from .integrations import Integration, _integration_factory from .invite import Invite from .iterators import AuditLogIterator, BanIterator, MemberIterator from .member import Member, VoiceState from .mixins import Hashable +from .onboarding import Onboarding from .partial_emoji import PartialEmoji from .permissions import PermissionOverwrite from .role import Role @@ -4567,3 +4568,79 @@ async def create_automod_rule( reason=reason, ) return AutoModRule(data=data, guild=self) + + async def get_onboarding(self) -> Onboarding: + """|coro| + + Retrieves the guild onboarding object. + + You must have :attr:`.Permissions.manage_guild` and :attr:`.Permissions.manage_roles` permissions to do this. + + .. versionadded:: 2.8 + + Raises + ------ + Forbidden + You do not have proper permissions to retrieve the guild onboarding object. + HTTPException + Retrieving the guild onboarding object failed. + + Returns + ------- + :class:`Onboarding` + The guild onboarding object. + """ + data = await self._state.http.get_guild_onboarding(self.id) + return Onboarding(data=data, guild=self, state=self._state) + + """ + async def verify_member(self, user: Snowflake, *, reason: Optional[str] = None) -> Member: + |coro| + + Verifies the member. + + TODO: Add more information about what this does. + NOTE: What permissions does this need? + + .. versionadded:: 2.8 + + Raises + ------ + Forbidden + You do not have proper permissions to verify the member. + HTTPException + Verifying the member failed. + + data = await self._state.http.edit_member( + self.id, + user.id, + reason=reason, + flags=MemberFlags.bypasses_verification.flag, + ) + return Member(data=data, guild=self, state=self._state) + + async def unverify_member(self, user: Snowflake, *, reason: Optional[str] = None) -> Member: + |coro| + + Unverifies the member. + + TODO: Add more information about what this does. + NOTE: What permissions does this need? + + .. versionadded:: 2.8 + + Raises + ------ + Forbidden + You do not have proper permissions to unverify the member. + HTTPException + Unverifying the member failed. + + data = await self._state.http.edit_member( + self.id, + user.id, + reason=reason, + flags=0, # NOTE: I can't remove flags if I don't have the other flags + ) + return Member(data=data, guild=self, state=self._state) + """ diff --git a/disnake/http.py b/disnake/http.py index 3e5eb6f533..3d0be8dcc5 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -66,6 +66,7 @@ invite, member, message, + onboarding, role, sticker, template, @@ -2136,6 +2137,8 @@ def edit_guild_welcome_screen( r = Route("PATCH", "/guilds/{guild_id}/welcome-screen", guild_id=guild_id) return self.request(r, json=payload, reason=reason) + # Auto moderation + def get_auto_moderation_rules(self, guild_id: Snowflake) -> Response[List[automod.AutoModRule]]: return self.request( Route("GET", "/guilds/{guild_id}/auto-moderation/rules", guild_id=guild_id) @@ -2225,6 +2228,29 @@ def delete_auto_moderation_rule( reason=reason, ) + # Guild Onboarding + + def get_guild_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]: + return self.request(Route("GET", "/guilds/{guild_id}/onboarding", guild_id=guild_id)) + + def edit_guild_onboarding( + self, + guild_id: Snowflake, + prompts: List[onboarding.PartialOnboardingPrompt], + enable_onboarding_prompts: bool, + enable_default_channels: bool, + default_channel_ids: List[int], + ) -> Response[onboarding.Onboarding]: + payload: onboarding.EditOnboarding = { + "prompts": prompts, + "enable_onboarding_prompts": enable_onboarding_prompts, + "enable_default_channels": enable_default_channels, + "default_channel_ids": default_channel_ids, + } + return self.request( + Route("PATCH", "/guilds/{guild_id}/onboarding", guild_id=guild_id), json=payload + ) + # Application commands (global) def get_global_commands( diff --git a/disnake/member.py b/disnake/member.py index 3a41347bca..f20b79e4f0 100644 --- a/disnake/member.py +++ b/disnake/member.py @@ -28,6 +28,7 @@ from .asset import Asset from .colour import Colour from .enums import Status, try_enum +from .flags import MemberFlags from .object import Object from .permissions import Permissions from .user import BaseUser, User, _UserTag @@ -271,6 +272,7 @@ class Member(disnake.abc.Messageable, _UserTag): "_state", "_avatar", "_communication_disabled_until", + "_flags", ) if TYPE_CHECKING: @@ -336,6 +338,7 @@ def __init__( self._avatar: Optional[str] = data.get("avatar") timeout_datetime = utils.parse_time(data.get("communication_disabled_until")) self._communication_disabled_until: Optional[datetime.datetime] = timeout_datetime + self._flags: int = data["flags"] def __str__(self) -> str: return str(self._user) @@ -399,6 +402,7 @@ def _copy(cls, member: Member) -> Self: self._state = member._state self._avatar = member._avatar self._communication_disabled_until = member.current_timeout + self._flags = member._flags # Reference will not be copied unless necessary by PRESENCE_UPDATE # See below @@ -427,6 +431,7 @@ def _update(self, data: GuildMemberUpdateEvent) -> None: self._avatar = data.get("avatar") timeout_datetime = utils.parse_time(data.get("communication_disabled_until")) self._communication_disabled_until = timeout_datetime + self._flags = data["flags"] def _presence_update( self, data: PresenceData, user: UserPayload @@ -699,6 +704,14 @@ def current_timeout(self) -> Optional[datetime.datetime]: return self._communication_disabled_until + @property + def flags(self) -> MemberFlags: + """:class:`MemberFlags`: Returns the member's flags. + + .. versionadded:: 2.8 + """ + return MemberFlags._from_value(self._flags) + @overload async def ban( self, @@ -759,6 +772,8 @@ async def edit( roles: Sequence[disnake.abc.Snowflake] = MISSING, voice_channel: Optional[VocalGuildChannel] = MISSING, timeout: Optional[Union[float, datetime.timedelta, datetime.datetime]] = MISSING, + flags: MemberFlags = MISSING, + verified: bool = MISSING, reason: Optional[str] = None, ) -> Optional[Member]: """|coro| @@ -782,6 +797,8 @@ async def edit( +------------------------------+-------------------------------------+ | timeout | :attr:`Permissions.moderate_members`| +------------------------------+-------------------------------------+ + | verified | :attr:`Permissions.moderate_members`| + +------------------------------+-------------------------------------+ All parameters are optional. @@ -816,6 +833,17 @@ async def edit( .. versionadded:: 2.3 + flags: :class:`MemberFlags` + The member's new flags. + + .. versionadded:: 2.8 + + verified: :class:`bool` + Whether the member is verified. + TODO: Add more information about this. + + .. versionadded:: 2.8 + reason: Optional[:class:`str`] The reason for editing this member. Shows up on the audit log. @@ -889,6 +917,14 @@ async def edit( else: payload["communication_disabled_until"] = None + if verified is not MISSING: + # create base flags if flags are provided, otherwise use the internal flags. + flags = MemberFlags._from_value(self._flags if flags is MISSING else flags.value) + flags.bypasses_verification = verified + + if flags is not MISSING: + payload["flags"] = flags.value + if payload: data = await http.edit_member(guild_id, self.id, reason=reason, **payload) return Member(data=data, guild=self.guild, state=self._state) diff --git a/disnake/onboarding.py b/disnake/onboarding.py new file mode 100644 index 0000000000..684c368bb2 --- /dev/null +++ b/disnake/onboarding.py @@ -0,0 +1,341 @@ +# SPDX-License-Identifier: MIT +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Optional, Sequence, Union + +from .emoji import Emoji, PartialEmoji, _EmojiTag +from .mixins import Hashable +from .object import Object +from .utils import _get_as_snowflake + +if TYPE_CHECKING: + from typing_extensions import Self + + from .abc import Snowflake + from .guild import Guild, GuildChannel + from .state import ConnectionState + from .types.onboarding import ( + Onboarding as OnboardingPayload, + OnboardingPrompt as OnboardingPromptPayload, + OnboardingPromptOption as OnboardingPromptOptionPayload, + PartialOnboardingPrompt as PartialOnboardingPromptPayload, + PartialOnboardingPromptOption as PartialOnboardingPromptOptionPayload, + ) + +__all__ = ( + "Onboarding", + "OnboardingPrompt", + "OnboardingPromptOption", +) + +# NOTE: From the information I got, onboarding will be read-only for bots. + +# TODO: Audit log events +# TODO: Gateway events? (it currently doesn't have any) +# TODO: Guild methods +# TODO: Member flags +# TODO: Verify/Unverify member method +# TODO: Add documentation +# TODO: Check if any http methods need permissions +# Manage Guild and Manage Roles are required to edit onboarding. + + +class Onboarding: # NOTE: or GuildOnboarding? + """Represents a guild onboarding. + + .. versionadded:: 2.8 + + TO BE DONE... + + Attributes + ---------- + guild: :class:`Guild` + The guild this onboarding belongs to. + prompts: List[:class:`OnboardingPrompt`] + The prompts the onboarding has. + """ + + __slots__ = ( + "guild", + "prompts", + "_guild_id", + "_enable_default_channels", + "_enable_onboarding_prompts", + "_default_channel_ids", + "_responses", + "_state", + ) + + def __init__(self, *, state: ConnectionState, guild: Guild, data: OnboardingPayload): + self._state = state + self.guild = guild + self._from_data(data) + + def _from_data(self, data: OnboardingPayload): + # Is this even required if there are no gateway events? + + # NOTE: I'm not sure if these are always available. + self.prompts = [ + OnboardingPrompt._from_dict(data=prompt, state=self._state) + for prompt in data["prompts"] + ] + self._guild_id = int(data["guild_id"]) # NOTE: is this required? + self._enable_onboarding_prompts = data["enable_onboarding_prompts"] + self._enable_default_channels = data["enable_default_channels"] + self._default_channel_ids = list(map(int, data["default_channel_ids"])) + self._responses = data["responses"] # NOTE: client only? + + def __repr__(self) -> str: + return ( + f"" + ) + + @property + def onboarding_prompts_enabled(self) -> bool: + """:class:`bool`: Whether onboarding prompts are enabled.""" + # TODO: Add more info about this + return self._enable_onboarding_prompts + + @property + def default_channels_enabled(self) -> bool: + """:class:`bool`: Whether default channels are enabled.""" + # TODO: Add more info about this + return self._enable_default_channels + + @property + def default_channels(self) -> List[GuildChannel]: + """List[:class:`abc.GuildChannel`]: A list of channels that will display in Browse Channels.""" + return list(filter(None, map(self.guild._channels.get, self._default_channel_ids))) + + async def edit( + self, + prompts: List[OnboardingPrompt], + onboarding_prompts_enabled: bool, + default_channels_enabled: bool, + default_channel_ids: List[int], + ) -> Self: + # See the first note of this file. I'll keep this here just in case it's supported by bots. + """|coro| + + Edits the onboarding. + + Parameters + ---------- + prompts: List[:class:`OnboardingPrompt`] + The new onboarding prompts. + onboarding_prompts_enabled: :class:`bool` + Whether onboarding prompts are enabled. + default_channels_enabled: :class:`bool` + Whether default channels are enabled. + default_channel_ids: List[:class:`int`] + The new default channel IDs. + + Raises + ------ + Forbidden + You do not have permissions to edit the onboarding. + HTTPException + Editing the onboarding failed. + + Returns + ------- + :class:`Onboarding` + The newly edited onboarding. + """ + data = await self._state.http.edit_guild_onboarding( + self.guild.id, + prompts=[prompt._to_dict() for prompt in prompts], + enable_onboarding_prompts=onboarding_prompts_enabled, + enable_default_channels=default_channels_enabled, + default_channel_ids=default_channel_ids, + ) + return Onboarding(state=self._state, guild=self.guild, data=data) + + +class OnboardingPromptOption(Hashable): + """Represents an onboarding prompt option. + + .. versionadded:: 2.8 + + Attributes + ---------- + id: :class:`int` + The option's ID. Note that if this option was manually constructed, + this will be ``0``. + title: :class:`str` + The option's title. + description: :class:`str` + The option's description. + emoji: Optional[Union[:class:`str`, :class:`PartialEmoji`, :class:`Emoji`]] + The option's emoji. + roles: List[:class:`abc.Snowflake`] + The roles that will be added to the user when they select this option. + channels: List[:class:`abc.Snowflake`] + The channels that the user will see when they select this option. + """ + + __slots__ = ("id", "title", "description", "emoji", "roles", "channels") + + def __init__( + self, + *, + title: str, + description: str, + emoji: Optional[Union[str, PartialEmoji, Emoji]], + roles: Sequence[Snowflake], + channels: Sequence[Snowflake], + ): + # NOTE: (a very very important note), the ID may sometimes be a UNIX timestamp since + # Onboarding changes are saved locally until you send the API request (that's how it works in client) + # so the API needs the timestamp to know what ID it needs to create, should we just add a note about it + # or "try" to create the ID ourselves? + # I'm not sure if this also happens for OnboardingPrompt + self.id = 0 + self.title = title + self.description = description + self.roles = roles + self.channels = channels + + self.emoji: Optional[ + Union[str, PartialEmoji, Emoji] + ] = None # NOTE: not sure if this is nullable + if emoji is None: + self.emoji = None + elif isinstance(emoji, str): + self.emoji = PartialEmoji.from_str(emoji) + elif isinstance(emoji, _EmojiTag): + self.emoji = emoji + else: + raise TypeError("emoji must be None, a str, PartialEmoji, or Emoji instance") + + def __str__(self) -> str: + return self.title + + def __repr__(self) -> str: + return ( + f"" + ) + + def _to_dict(self) -> PartialOnboardingPromptOptionPayload: + emoji_name, emoji_id = PartialEmoji._emoji_to_name_id(self.emoji) + payload: PartialOnboardingPromptOptionPayload = { + "title": self.title, + "description": self.description, + "emoji_name": emoji_name, + "emoji_id": emoji_id, + "role_ids": [role.id for role in self.roles], + "channel_ids": [channel.id for channel in self.channels], + } + + if self.id: + payload["id"] = self.id + + return payload + + @classmethod + def _from_dict(cls, *, data: OnboardingPromptOptionPayload, state: ConnectionState) -> Self: + emoji_id = _get_as_snowflake(data, "emoji_id") + emoji_name = data.get("emoji_name") + emoji = PartialEmoji._emoji_from_name_id(emoji_name, emoji_id, state=state) + + self = cls( + title=data["title"], + description=data["description"], + emoji=emoji, + roles=[ + Object(id=role_id) for role_id in data["role_ids"] + ], # NOTE: should I get the Role here? + channels=[ + Object(id=channel_id) for channel_id in data["channel_ids"] + ], # NOTE: should I get the channel here? + ) + self.id = int(data["id"]) + return self + + # TODO: with_changes + + +class OnboardingPrompt(Hashable): + """Represents an onboarding prompt. + + .. versionadded:: 2.8 + + Attributes + ---------- + id: :class:`int` + The onboarding prompt's ID. Note that if this prompt was manually constructed, + this will be ``0``. + title: :class:`str` + The onboarding prompt's title. + options: List[:class:`OnboardingPromptOption`] + The onboarding prompt's options. + single_select: :class:`bool` + Whether only one option can be selected. + required: :class:`bool` + Whether one option must be selected. + in_onboarding: :class:`bool` + Whether this prompt is in the onboarding. + """ + + __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding") + + def __init__( + self, + *, + title: str, + options: List[OnboardingPromptOption], + single_select: bool, + required: bool, + in_onboarding: bool, + ): + self.id = 0 + self.title = title + self.options = options + self.single_select = single_select + self.required = required + self.in_onboarding = in_onboarding + + def __str__(self) -> str: + return self.title + + def __repr__(self) -> str: + return ( + f"" + ) + + def _to_dict(self) -> PartialOnboardingPromptPayload: + payload: PartialOnboardingPromptPayload = { + "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, + } + + if self.id: + payload["id"] = self.id + + return payload + + @classmethod + def _from_dict(cls, *, data: OnboardingPromptPayload, state: ConnectionState) -> Self: + self = cls( + title=data["title"], + options=[ + OnboardingPromptOption._from_dict(data=option, state=state) + for option in data["options"] + ], + single_select=data["single_select"], + required=data["required"], + in_onboarding=data["in_onboarding"], + ) + self.id = int(data["id"]) + return self + + # TODO: with_changes diff --git a/disnake/types/gateway.py b/disnake/types/gateway.py index 09d1528be0..15eaed92a4 100644 --- a/disnake/types/gateway.py +++ b/disnake/types/gateway.py @@ -436,6 +436,7 @@ class GuildMemberUpdateEvent(TypedDict): mute: NotRequired[bool] pending: NotRequired[bool] communication_disabled_until: NotRequired[Optional[str]] + flags: int # TODO: Check if it's Optional/Nullable # https://discord.com/developers/docs/topics/gateway-events#guild-emojis-update diff --git a/disnake/types/member.py b/disnake/types/member.py index b2107ab41d..8105aba7ed 100644 --- a/disnake/types/member.py +++ b/disnake/types/member.py @@ -19,6 +19,7 @@ class BaseMember(TypedDict): pending: NotRequired[bool] permissions: NotRequired[str] communication_disabled_until: NotRequired[Optional[str]] + flags: int # TODO: Check if this is Optional/Nullable class Member(BaseMember, total=False): diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py new file mode 100644 index 0000000000..88b60262ce --- /dev/null +++ b/disnake/types/onboarding.py @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: MIT + +from typing import List, Optional, TypedDict + +from typing_extensions import NotRequired + +from .snowflake import Snowflake, SnowflakeList + +# NOTE: PartialOnboardingXX is very redundant, TBD + + +class OnboardingPromptOption(TypedDict): + id: Snowflake + title: str + description: str + emoji_id: Optional[Snowflake] + emoji_name: Optional[str] + role_ids: SnowflakeList + channel_ids: SnowflakeList + + +class PartialOnboardingPromptOption(TypedDict): + id: NotRequired[Snowflake] + title: str + description: str + emoji_id: Optional[Snowflake] + emoji_name: Optional[str] + role_ids: SnowflakeList + channel_ids: SnowflakeList + + +class OnboardingPrompt(TypedDict): + id: Snowflake + title: str + options: List[OnboardingPromptOption] + single_select: bool + required: bool + in_onboarding: bool + + +class PartialOnboardingPrompt(TypedDict): + id: NotRequired[Snowflake] + title: str + options: List[PartialOnboardingPromptOption] + single_select: bool + required: bool + in_onboarding: bool + + +class Onboarding(TypedDict): + guild_id: Snowflake + prompts: List[OnboardingPrompt] + enable_onboarding_prompts: bool + enable_default_channels: bool + default_channel_ids: SnowflakeList + # NOTE: client only? + responses: SnowflakeList + + +class EditOnboarding(TypedDict): + prompts: List[PartialOnboardingPrompt] + enable_onboarding_prompts: bool + enable_default_channels: bool + default_channel_ids: SnowflakeList diff --git a/docs/api.rst b/docs/api.rst index 0b7b081f21..0233b762bc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3257,6 +3257,76 @@ of :class:`enum.Enum`. See :attr:`automod_block_message` for more information on how the :attr:`~AuditLogEntry.extra` field is set. + .. attribute:: onboarding_prompt_create + + An onboarding prompt was created. + + When this is the action, the type of :attr:`~AuditLogEntry.target` is + the :class:`Object` with the ID of the onboarding prompt which + was created. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.id` + - :attr:`~AuditLogDiff.title` + - :attr:`~AuditLogDiff.options` + - :attr:`~AuditLogDiff.single_select` + - :attr:`~AuditLogDiff.required` + - :attr:`~AuditLogDiff.in_onboarding` + + .. attribute:: onboarding_prompt_update + + An onboarding prompt was updated. + + When this is the action, the type of :attr:`~AuditLogEntry.target` is + the :class:`Object` with the ID of the onboarding prompt which + was updated. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.title` + - :attr:`~AuditLogDiff.options` + - :attr:`~AuditLogDiff.single_select` + - :attr:`~AuditLogDiff.required` + - :attr:`~AuditLogDiff.in_onboarding` + + .. attribute:: onboarding_prompt_delete + + An onboarding prompt was deleted. + + When this is the action, the type of :attr:`~AuditLogEntry.target` is + the :class:`Object` with the ID of the onboarding prompt which + was deleted. + + Possible attributes for :class:`AuditLogDiff`: + + TBD. + + .. attribute:: onboarding_create + + An onboarding was created. + + When this is the action, the type of :attr:`~AuditLogEntry.target` is + ``None``. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.default_channel_ids` + - :attr:`~AuditLogDiff.enable_default_channels` + - :attr:`~AuditLogDiff.enable_onboarding_prompts` + - :attr:`~AuditLogDiff.prompts` + + .. attribute:: onboarding_update + + An onboarding was updated. + + When this is the action, the type of :attr:`~AuditLogEntry.target` is + ``None``. + + Possible attributes for :class:`AuditLogDiff`: + + TBD. + .. class:: AuditLogActionCategory Represents the category that the :class:`AuditLogAction` belongs to. @@ -4654,6 +4724,61 @@ AuditLogDiff :type: Optional[:class:`ThreadSortOrder`] + .. attribute:: title + + The title of an onboarding prompt or an onboarding prompt option being changed. + + :type: :class:`str` + + .. attribute:: options + + The list of options of an onboarding prompt being changed. + + :type: List[:class:`OnboardingPromptOption`] + + .. attribute:: single_select + + The onboarding prompt is single select or not. + + :type: :class:`bool` + + .. attribute:: required + + The onboarding prompt option is required or not. + + :type: :class:`bool` + + .. attribute:: in_onboarding + + The onboarding prompt option is in onboarding or not. + + :type: :class:`bool` + + .. attribute:: default_channel + + The list of default channels of an onboarding being changed. + + :type: List[:class:`abc.GuildChannel`] + + .. attribute:: enable_default_channels + + The onboarding shows default channels or not. + + :type: :class:`bool` + + .. attribute:: enable_onboarding_prompts + + The onboarding shows onboarding prompts or not. + + :type: :class:`bool` + + .. attribute:: prompts + + The list of prompts of an onboarding being changed. + + :type: List[:class:`OnboardingPrompt`] + + Webhook Support ------------------ @@ -5583,6 +5708,14 @@ AutoModActionExecution .. autoclass:: AutoModActionExecution() :members: +Onboarding +~~~~~~~~~~ + +.. attributetable:: Onboarding + +.. autoclass:: Onboarding + :members: + RawMessageDeleteEvent ~~~~~~~~~~~~~~~~~~~~~~~ @@ -5899,6 +6032,22 @@ AutoModTimeoutAction .. autoclass:: AutoModTimeoutAction :members: +OnboardingPrompt +~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPrompt + +.. autoclass:: OnboardingPrompt + :members: + +OnboardingPromptOption +~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPromptOption + +.. autoclass:: OnboardingPromptOption + :members: + File ~~~~~ @@ -6025,6 +6174,14 @@ PublicUserFlags .. autoclass:: PublicUserFlags() :members: +MemberFlags +~~~~~~~~~~~ + +.. attributetable:: MemberFlags + +.. autoclass:: MemberFlags() + :members: + .. _discord_ui_kit: Bot UI Kit diff --git a/tests/interactions/test_base.py b/tests/interactions/test_base.py index 29140b65f7..a3d7563c28 100644 --- a/tests/interactions/test_base.py +++ b/tests/interactions/test_base.py @@ -141,6 +141,7 @@ def test_init_member(self, state): "joined_at": "2022-09-02T22:00:55.069000+00:00", "deaf": False, "mute": False, + "flags": 0, } user_payload: UserPayload = { diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py new file mode 100644 index 0000000000..4c234ef0c6 --- /dev/null +++ b/tests/test_onboarding.py @@ -0,0 +1,127 @@ +# SPDX-License-Identifier: MIT + +from unittest import mock + +import pytest + +from disnake import ( + Guild, + Object, + Onboarding, + OnboardingPrompt, + OnboardingPromptOption, + PartialEmoji, +) +from disnake.types import onboarding as onboarding_types + +onboarding_prompt_option_payload: onboarding_types.PartialOnboardingPromptOption = { + "title": "test", + "description": "test", + "emoji_id": "123", + "emoji_name": None, + "role_ids": ["456", "789"], + "channel_ids": ["123", "456"], +} + + +@pytest.fixture() +def onboarding_prompt_option() -> OnboardingPromptOption: + + return OnboardingPromptOption( + title="test", + description="test", + emoji=PartialEmoji(name="", id=123), + roles=[Object(id="456"), Object(id="789")], + channels=[Object(id="123"), Object(id="456")], + ) + + +@pytest.fixture() +def onboarding_prompt() -> OnboardingPrompt: + + onboarding_prompt_payload: onboarding_types.PartialOnboardingPrompt = { + "title": "test", + "options": [], + "single_select": True, + "required": True, + "in_onboarding": True, + } + + return OnboardingPrompt(**onboarding_prompt_payload) + + +@pytest.fixture() +def onboarding() -> Onboarding: + + onboarding_payload: onboarding_types.Onboarding = { + "guild_id": "123", + "prompts": [], + "enable_onboarding_prompts": True, + "enable_default_channels": True, + "default_channel_ids": ["456", "789"], + "responses": [], + } + + return Onboarding( + state=mock.Mock(http=mock.AsyncMock()), + guild=mock.Mock(Guild, id=123), + data=onboarding_payload, + ) + + +class TestOnboarding: + def test_onboarding(self, onboarding: Onboarding) -> None: + assert onboarding.guild.id == 123 + assert onboarding.prompts == [] + assert onboarding.onboarding_prompts_enabled is True + assert onboarding.default_channels_enabled is True + assert onboarding._default_channel_ids == [456, 789] + assert onboarding._responses == [] + + def test_onboarding_repr(self, onboarding: Onboarding) -> None: + assert repr(onboarding) == ( + "" + ) + + +class TestOnboardingPrompt: + def test_onboarding_prompt(self, onboarding_prompt: OnboardingPrompt) -> None: + assert onboarding_prompt.title == "test" + assert onboarding_prompt.options == [] + assert onboarding_prompt.single_select is True + assert onboarding_prompt.required is True + assert onboarding_prompt.in_onboarding is True + + def test_onboarding_prompt_str(self, onboarding_prompt: OnboardingPrompt) -> None: + assert str(onboarding_prompt) == "test" + + def test_onboarding_prompt_repr(self, onboarding_prompt: OnboardingPrompt) -> None: + assert repr(onboarding_prompt) == ( + "" + ) + + +class TestOnboardingPromptOption: + def test_onboarding_prompt_option( + self, onboarding_prompt_option: OnboardingPromptOption + ) -> None: + assert onboarding_prompt_option.title == "test" + assert onboarding_prompt_option.description == "test" + assert onboarding_prompt_option.emoji == PartialEmoji(name="", id=123) + assert onboarding_prompt_option.roles == [Object(id="456"), Object(id="789")] + assert onboarding_prompt_option.channels == [Object(id="123"), Object(id="456")] + + def test_onboarding_prompt_option_str( + self, onboarding_prompt_option: OnboardingPromptOption + ) -> None: + assert str(onboarding_prompt_option) == "test" + + def test_onboarding_prompt_option_repr( + self, onboarding_prompt_option: OnboardingPromptOption + ) -> None: + assert repr(onboarding_prompt_option) == ( + " roles=[, ] channels=[, ]>" + ) From 071c71918570334abb8318fc091404882c0954c1 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 15:59:14 -0300 Subject: [PATCH 02/99] fix --- disnake/guild.py | 152 +++++++++++++++++++++--------------------- disnake/http.py | 3 +- disnake/onboarding.py | 11 +-- 3 files changed, 84 insertions(+), 82 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index 359c571c9d..6fda2c0cfc 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4582,6 +4582,82 @@ async def create_automod_rule( ) return AutoModRule(data=data, guild=self) + async def get_onboarding(self) -> Onboarding: + """|coro| + + Retrieves the guild onboarding object. + + You must have :attr:`.Permissions.manage_guild` and :attr:`.Permissions.manage_roles` permissions to do this. + + .. versionadded:: 2.8 + + Raises + ------ + Forbidden + You do not have proper permissions to retrieve the guild onboarding object. + HTTPException + Retrieving the guild onboarding object failed. + + Returns + ------- + :class:`Onboarding` + The guild onboarding object. + """ + data = await self._state.http.get_guild_onboarding(self.id) + return Onboarding(data=data, guild=self, state=self._state) + + """ + async def verify_member(self, user: Snowflake, *, reason: Optional[str] = None) -> Member: + |coro| + + Verifies the member. + + TODO: Add more information about what this does. + NOTE: What permissions does this need? + + .. versionadded:: 2.8 + + Raises + ------ + Forbidden + You do not have proper permissions to verify the member. + HTTPException + Verifying the member failed. + + data = await self._state.http.edit_member( + self.id, + user.id, + reason=reason, + flags=MemberFlags.bypasses_verification.flag, + ) + return Member(data=data, guild=self, state=self._state) + + async def unverify_member(self, user: Snowflake, *, reason: Optional[str] = None) -> Member: + |coro| + + Unverifies the member. + + TODO: Add more information about what this does. + NOTE: What permissions does this need? + + .. versionadded:: 2.8 + + Raises + ------ + Forbidden + You do not have proper permissions to unverify the member. + HTTPException + Unverifying the member failed. + + data = await self._state.http.edit_member( + self.id, + user.id, + reason=reason, + flags=0, # NOTE: I can't remove flags if I don't have the other flags + ) + return Member(data=data, guild=self, state=self._state) + """ + PlaceholderID = NewType("PlaceholderID", int) @@ -5046,79 +5122,3 @@ def add_voice_channel( data["video_quality_mode"] = try_enum_to_int(video_quality_mode) return _id - - async def get_onboarding(self) -> Onboarding: - """|coro| - - Retrieves the guild onboarding object. - - You must have :attr:`.Permissions.manage_guild` and :attr:`.Permissions.manage_roles` permissions to do this. - - .. versionadded:: 2.8 - - Raises - ------ - Forbidden - You do not have proper permissions to retrieve the guild onboarding object. - HTTPException - Retrieving the guild onboarding object failed. - - Returns - ------- - :class:`Onboarding` - The guild onboarding object. - """ - data = await self._state.http.get_guild_onboarding(self.id) - return Onboarding(data=data, guild=self, state=self._state) - - """ - async def verify_member(self, user: Snowflake, *, reason: Optional[str] = None) -> Member: - |coro| - - Verifies the member. - - TODO: Add more information about what this does. - NOTE: What permissions does this need? - - .. versionadded:: 2.8 - - Raises - ------ - Forbidden - You do not have proper permissions to verify the member. - HTTPException - Verifying the member failed. - - data = await self._state.http.edit_member( - self.id, - user.id, - reason=reason, - flags=MemberFlags.bypasses_verification.flag, - ) - return Member(data=data, guild=self, state=self._state) - - async def unverify_member(self, user: Snowflake, *, reason: Optional[str] = None) -> Member: - |coro| - - Unverifies the member. - - TODO: Add more information about what this does. - NOTE: What permissions does this need? - - .. versionadded:: 2.8 - - Raises - ------ - Forbidden - You do not have proper permissions to unverify the member. - HTTPException - Unverifying the member failed. - - data = await self._state.http.edit_member( - self.id, - user.id, - reason=reason, - flags=0, # NOTE: I can't remove flags if I don't have the other flags - ) - return Member(data=data, guild=self, state=self._state) - """ diff --git a/disnake/http.py b/disnake/http.py index a409a0a753..252456628d 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -290,6 +290,7 @@ async def request( # header creation headers: Dict[str, str] = { "User-Agent": self.user_agent, + "x-super-properties": "eyJvcyI6IldpbmRvd3MiLCJicm93c2VyIjoiRGlzY29yZCBDbGllbnQiLCJyZWxlYXNlX2NoYW5uZWwiOiJjYW5hcnkiLCJjbGllbnRfdmVyc2lvbiI6IjEuMC41NSIsIm9zX3ZlcnNpb24iOiIxMC4wLjIyNjIxIiwib3NfYXJjaCI6Ing2NCIsInN5c3RlbV9sb2NhbGUiOiJlcy00MTkiLCJjbGllbnRfYnVpbGRfbnVtYmVyIjoxNjU1NTksIm5hdGl2ZV9idWlsZF9udW1iZXIiOjI4MDk4LCJjbGllbnRfZXZlbnRfc291cmNlIjpudWxsfQ==", } if self.token is not None: @@ -2282,7 +2283,7 @@ def edit_guild_onboarding( "default_channel_ids": default_channel_ids, } return self.request( - Route("PATCH", "/guilds/{guild_id}/onboarding", guild_id=guild_id), json=payload + Route("PUT", "/guilds/{guild_id}/onboarding", guild_id=guild_id), json=payload ) # Application commands (global) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 684c368bb2..4d05983d81 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -6,7 +6,7 @@ from .emoji import Emoji, PartialEmoji, _EmojiTag from .mixins import Hashable from .object import Object -from .utils import _get_as_snowflake +from .utils import MISSING, _get_as_snowflake if TYPE_CHECKING: from typing_extensions import Self @@ -111,10 +111,11 @@ def default_channels(self) -> List[GuildChannel]: async def edit( self, - prompts: List[OnboardingPrompt], - onboarding_prompts_enabled: bool, - default_channels_enabled: bool, - default_channel_ids: List[int], + *, + prompts: List[OnboardingPrompt] = MISSING, + onboarding_prompts_enabled: bool = MISSING, + default_channels_enabled: bool = MISSING, + default_channel_ids: List[int] = MISSING, ) -> Self: # See the first note of this file. I'll keep this here just in case it's supported by bots. """|coro| From 76a89113a6444a5f8fe462c1a234a3d4f58259ea Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 16:02:02 -0300 Subject: [PATCH 03/99] fix: remove onboarding edit --- disnake/http.py | 18 ----------------- disnake/onboarding.py | 45 ------------------------------------------- 2 files changed, 63 deletions(-) diff --git a/disnake/http.py b/disnake/http.py index 252456628d..660c3a5bd2 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -2268,24 +2268,6 @@ def delete_auto_moderation_rule( def get_guild_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]: return self.request(Route("GET", "/guilds/{guild_id}/onboarding", guild_id=guild_id)) - def edit_guild_onboarding( - self, - guild_id: Snowflake, - prompts: List[onboarding.PartialOnboardingPrompt], - enable_onboarding_prompts: bool, - enable_default_channels: bool, - default_channel_ids: List[int], - ) -> Response[onboarding.Onboarding]: - payload: onboarding.EditOnboarding = { - "prompts": prompts, - "enable_onboarding_prompts": enable_onboarding_prompts, - "enable_default_channels": enable_default_channels, - "default_channel_ids": default_channel_ids, - } - return self.request( - Route("PUT", "/guilds/{guild_id}/onboarding", guild_id=guild_id), json=payload - ) - # Application commands (global) def get_global_commands( diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 4d05983d81..44a696cf45 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -109,51 +109,6 @@ def default_channels(self) -> List[GuildChannel]: """List[:class:`abc.GuildChannel`]: A list of channels that will display in Browse Channels.""" return list(filter(None, map(self.guild._channels.get, self._default_channel_ids))) - async def edit( - self, - *, - prompts: List[OnboardingPrompt] = MISSING, - onboarding_prompts_enabled: bool = MISSING, - default_channels_enabled: bool = MISSING, - default_channel_ids: List[int] = MISSING, - ) -> Self: - # See the first note of this file. I'll keep this here just in case it's supported by bots. - """|coro| - - Edits the onboarding. - - Parameters - ---------- - prompts: List[:class:`OnboardingPrompt`] - The new onboarding prompts. - onboarding_prompts_enabled: :class:`bool` - Whether onboarding prompts are enabled. - default_channels_enabled: :class:`bool` - Whether default channels are enabled. - default_channel_ids: List[:class:`int`] - The new default channel IDs. - - Raises - ------ - Forbidden - You do not have permissions to edit the onboarding. - HTTPException - Editing the onboarding failed. - - Returns - ------- - :class:`Onboarding` - The newly edited onboarding. - """ - data = await self._state.http.edit_guild_onboarding( - self.guild.id, - prompts=[prompt._to_dict() for prompt in prompts], - enable_onboarding_prompts=onboarding_prompts_enabled, - enable_default_channels=default_channels_enabled, - default_channel_ids=default_channel_ids, - ) - return Onboarding(state=self._state, guild=self.guild, data=data) - class OnboardingPromptOption(Hashable): """Represents an onboarding prompt option. From bbe55e9b0934225c88247c50063270e962c4b44e Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 16:45:45 -0300 Subject: [PATCH 04/99] feat: add new stuff --- disnake/enums.py | 6 ++++++ disnake/onboarding.py | 17 ++++++++++++++--- disnake/types/onboarding.py | 3 +++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/disnake/enums.py b/disnake/enums.py index bae4284d10..921e6db690 100644 --- a/disnake/enums.py +++ b/disnake/enums.py @@ -61,6 +61,7 @@ "ThreadLayout", "Event", "ApplicationRoleConnectionMetadataType", + "OnboardingPromptType", ) @@ -1299,6 +1300,11 @@ class ApplicationRoleConnectionMetadataType(Enum): boolean_not_equal = 8 +class OnboardingPromptType(Enum): + multiple_choice = 0 + dropdown = 1 + + T = TypeVar("T") diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 44a696cf45..ef74e0b07d 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -4,9 +4,10 @@ from typing import TYPE_CHECKING, List, Optional, Sequence, Union from .emoji import Emoji, PartialEmoji, _EmojiTag +from .enums import OnboardingPromptType, try_enum from .mixins import Hashable from .object import Object -from .utils import MISSING, _get_as_snowflake +from .utils import _get_as_snowflake if TYPE_CHECKING: from typing_extensions import Self @@ -53,11 +54,14 @@ class Onboarding: # NOTE: or GuildOnboarding? The guild this onboarding belongs to. prompts: List[:class:`OnboardingPrompt`] The prompts the onboarding has. + enabled: bool + Whether the onboarding is enabled. """ __slots__ = ( "guild", "prompts", + "enabled", "_guild_id", "_enable_default_channels", "_enable_onboarding_prompts", @@ -79,6 +83,7 @@ def _from_data(self, data: OnboardingPayload): OnboardingPrompt._from_dict(data=prompt, state=self._state) for prompt in data["prompts"] ] + self.enabled = data["enabled"] self._guild_id = int(data["guild_id"]) # NOTE: is this required? self._enable_onboarding_prompts = data["enable_onboarding_prompts"] self._enable_default_channels = data["enable_default_channels"] @@ -235,9 +240,11 @@ class OnboardingPrompt(Hashable): Whether one option must be selected. in_onboarding: :class:`bool` Whether this prompt is in the onboarding. + type: :class:`OnboardingPromptType` + The onboarding prompt's type. """ - __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding") + __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding", "type") def __init__( self, @@ -247,6 +254,7 @@ def __init__( single_select: bool, required: bool, in_onboarding: bool, + type: OnboardingPromptType, ): self.id = 0 self.title = title @@ -254,6 +262,7 @@ def __init__( self.single_select = single_select self.required = required self.in_onboarding = in_onboarding + self.type = type def __str__(self) -> str: return self.title @@ -262,7 +271,7 @@ def __repr__(self) -> str: return ( f"" + f" in_onboarding={self.in_onboarding!r} type={self.type!r}>" ) def _to_dict(self) -> PartialOnboardingPromptPayload: @@ -272,6 +281,7 @@ def _to_dict(self) -> PartialOnboardingPromptPayload: "single_select": self.single_select, "required": self.required, "in_onboarding": self.in_onboarding, + "type": self.type.value, } if self.id: @@ -290,6 +300,7 @@ def _from_dict(cls, *, data: OnboardingPromptPayload, state: ConnectionState) -> single_select=data["single_select"], required=data["required"], in_onboarding=data["in_onboarding"], + type=try_enum(OnboardingPromptType, data["type"]), ) self.id = int(data["id"]) return self diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index 88b60262ce..2d113f2435 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -36,6 +36,7 @@ class OnboardingPrompt(TypedDict): single_select: bool required: bool in_onboarding: bool + type: int class PartialOnboardingPrompt(TypedDict): @@ -45,6 +46,7 @@ class PartialOnboardingPrompt(TypedDict): single_select: bool required: bool in_onboarding: bool + type: int class Onboarding(TypedDict): @@ -55,6 +57,7 @@ class Onboarding(TypedDict): default_channel_ids: SnowflakeList # NOTE: client only? responses: SnowflakeList + enabled: bool class EditOnboarding(TypedDict): From 53a3f54a2d9e113dac50c5e911a9578bf387fbc4 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 16:58:19 -0300 Subject: [PATCH 05/99] feat: remove stuff --- disnake/onboarding.py | 35 ----------------------------------- disnake/types/onboarding.py | 11 ----------- 2 files changed, 46 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index ef74e0b07d..f51a74ae51 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -29,16 +29,7 @@ "OnboardingPromptOption", ) -# NOTE: From the information I got, onboarding will be read-only for bots. - # TODO: Audit log events -# TODO: Gateway events? (it currently doesn't have any) -# TODO: Guild methods -# TODO: Member flags -# TODO: Verify/Unverify member method -# TODO: Add documentation -# TODO: Check if any http methods need permissions -# Manage Guild and Manage Roles are required to edit onboarding. class Onboarding: # NOTE: or GuildOnboarding? @@ -63,10 +54,7 @@ class Onboarding: # NOTE: or GuildOnboarding? "prompts", "enabled", "_guild_id", - "_enable_default_channels", - "_enable_onboarding_prompts", "_default_channel_ids", - "_responses", "_state", ) @@ -76,39 +64,20 @@ def __init__(self, *, state: ConnectionState, guild: Guild, data: OnboardingPayl self._from_data(data) def _from_data(self, data: OnboardingPayload): - # Is this even required if there are no gateway events? - - # NOTE: I'm not sure if these are always available. self.prompts = [ OnboardingPrompt._from_dict(data=prompt, state=self._state) for prompt in data["prompts"] ] self.enabled = data["enabled"] self._guild_id = int(data["guild_id"]) # NOTE: is this required? - self._enable_onboarding_prompts = data["enable_onboarding_prompts"] - self._enable_default_channels = data["enable_default_channels"] self._default_channel_ids = list(map(int, data["default_channel_ids"])) - self._responses = data["responses"] # NOTE: client only? def __repr__(self) -> str: return ( f"" ) - @property - def onboarding_prompts_enabled(self) -> bool: - """:class:`bool`: Whether onboarding prompts are enabled.""" - # TODO: Add more info about this - return self._enable_onboarding_prompts - - @property - def default_channels_enabled(self) -> bool: - """:class:`bool`: Whether default channels are enabled.""" - # TODO: Add more info about this - return self._enable_default_channels - @property def default_channels(self) -> List[GuildChannel]: """List[:class:`abc.GuildChannel`]: A list of channels that will display in Browse Channels.""" @@ -217,8 +186,6 @@ def _from_dict(cls, *, data: OnboardingPromptOptionPayload, state: ConnectionSta self.id = int(data["id"]) return self - # TODO: with_changes - class OnboardingPrompt(Hashable): """Represents an onboarding prompt. @@ -304,5 +271,3 @@ def _from_dict(cls, *, data: OnboardingPromptPayload, state: ConnectionState) -> ) self.id = int(data["id"]) return self - - # TODO: with_changes diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index 2d113f2435..46652acaa9 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -52,16 +52,5 @@ class PartialOnboardingPrompt(TypedDict): class Onboarding(TypedDict): guild_id: Snowflake prompts: List[OnboardingPrompt] - enable_onboarding_prompts: bool - enable_default_channels: bool default_channel_ids: SnowflakeList - # NOTE: client only? - responses: SnowflakeList enabled: bool - - -class EditOnboarding(TypedDict): - prompts: List[PartialOnboardingPrompt] - enable_onboarding_prompts: bool - enable_default_channels: bool - default_channel_ids: SnowflakeList From 292922f7480b30391672b852a25677325b7330c5 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:25:06 -0300 Subject: [PATCH 06/99] revert --- disnake/flags.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/disnake/flags.py b/disnake/flags.py index 4e010cf5fa..bbb9e3fca6 100644 --- a/disnake/flags.py +++ b/disnake/flags.py @@ -2151,8 +2151,6 @@ def slurs(self): class MemberFlags(BaseFlags): """Wraps up Discord Member flags. - .. versionadded:: 2.8 - .. container:: operations .. describe:: x == y @@ -2207,11 +2205,13 @@ class MemberFlags(BaseFlags): Returns a MemberFlags instance with all flags except ``y`` inverted from their default value. + .. versionadded:: 2.8 + Attributes ---------- - values: :class:`int` - The raw values. You should query flags via the properties - rather than using these raw values. + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. """ __slots__ = () From 0d7313bc63e3c444cc496cd20f4178d4000a94d0 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:26:39 -0300 Subject: [PATCH 07/99] remove unnecessary stuff --- disnake/guild.py | 52 ----------------------------------------- disnake/types/member.py | 2 +- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index 6fda2c0cfc..fbe044a7ca 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4606,58 +4606,6 @@ async def get_onboarding(self) -> Onboarding: data = await self._state.http.get_guild_onboarding(self.id) return Onboarding(data=data, guild=self, state=self._state) - """ - async def verify_member(self, user: Snowflake, *, reason: Optional[str] = None) -> Member: - |coro| - - Verifies the member. - - TODO: Add more information about what this does. - NOTE: What permissions does this need? - - .. versionadded:: 2.8 - - Raises - ------ - Forbidden - You do not have proper permissions to verify the member. - HTTPException - Verifying the member failed. - - data = await self._state.http.edit_member( - self.id, - user.id, - reason=reason, - flags=MemberFlags.bypasses_verification.flag, - ) - return Member(data=data, guild=self, state=self._state) - - async def unverify_member(self, user: Snowflake, *, reason: Optional[str] = None) -> Member: - |coro| - - Unverifies the member. - - TODO: Add more information about what this does. - NOTE: What permissions does this need? - - .. versionadded:: 2.8 - - Raises - ------ - Forbidden - You do not have proper permissions to unverify the member. - HTTPException - Unverifying the member failed. - - data = await self._state.http.edit_member( - self.id, - user.id, - reason=reason, - flags=0, # NOTE: I can't remove flags if I don't have the other flags - ) - return Member(data=data, guild=self, state=self._state) - """ - PlaceholderID = NewType("PlaceholderID", int) diff --git a/disnake/types/member.py b/disnake/types/member.py index 8105aba7ed..9daeab5467 100644 --- a/disnake/types/member.py +++ b/disnake/types/member.py @@ -19,7 +19,7 @@ class BaseMember(TypedDict): pending: NotRequired[bool] permissions: NotRequired[str] communication_disabled_until: NotRequired[Optional[str]] - flags: int # TODO: Check if this is Optional/Nullable + flags: int class Member(BaseMember, total=False): From 183a6f65aed9bf659d37df982394ea8a1e763777 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:28:05 -0300 Subject: [PATCH 08/99] revert unnecessary stuff --- disnake/flags.py | 2 +- disnake/types/gateway.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/flags.py b/disnake/flags.py index bbb9e3fca6..7103cd067c 100644 --- a/disnake/flags.py +++ b/disnake/flags.py @@ -2205,7 +2205,7 @@ class MemberFlags(BaseFlags): Returns a MemberFlags instance with all flags except ``y`` inverted from their default value. - .. versionadded:: 2.8 + .. versionadded:: 2.8 Attributes ---------- diff --git a/disnake/types/gateway.py b/disnake/types/gateway.py index d084b86d83..968f551ac8 100644 --- a/disnake/types/gateway.py +++ b/disnake/types/gateway.py @@ -437,7 +437,7 @@ class GuildMemberUpdateEvent(TypedDict): mute: NotRequired[bool] pending: NotRequired[bool] communication_disabled_until: NotRequired[Optional[str]] - flags: int # TODO: Check if it's Optional/Nullable + flags: int # https://discord.com/developers/docs/topics/gateway-events#guild-emojis-update From 0b9ddd8e5e45c8b3b838d1f0dd4132c241c32e04 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:31:53 -0300 Subject: [PATCH 09/99] 2.8 -> 2.9 --- disnake/guild.py | 2 +- disnake/onboarding.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index fbe044a7ca..ca523aa2fa 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4589,7 +4589,7 @@ async def get_onboarding(self) -> Onboarding: You must have :attr:`.Permissions.manage_guild` and :attr:`.Permissions.manage_roles` permissions to do this. - .. versionadded:: 2.8 + .. versionadded:: 2.9 Raises ------ diff --git a/disnake/onboarding.py b/disnake/onboarding.py index f51a74ae51..09405c9248 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -35,7 +35,7 @@ class Onboarding: # NOTE: or GuildOnboarding? """Represents a guild onboarding. - .. versionadded:: 2.8 + .. versionadded:: 2.9 TO BE DONE... @@ -87,7 +87,7 @@ def default_channels(self) -> List[GuildChannel]: class OnboardingPromptOption(Hashable): """Represents an onboarding prompt option. - .. versionadded:: 2.8 + .. versionadded:: 2.9 Attributes ---------- @@ -190,7 +190,7 @@ def _from_dict(cls, *, data: OnboardingPromptOptionPayload, state: ConnectionSta class OnboardingPrompt(Hashable): """Represents an onboarding prompt. - .. versionadded:: 2.8 + .. versionadded:: 2.9 Attributes ---------- From 2f0b741108dcf06faa2173ae785c60d8cd7139e7 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:45:45 -0300 Subject: [PATCH 10/99] fix: tests --- disnake/guild.py | 2 +- disnake/onboarding.py | 2 +- tests/test_onboarding.py | 16 +++++++--------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index ca523aa2fa..6a7ea81018 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -59,7 +59,7 @@ ) from .errors import ClientException, HTTPException, InvalidData from .file import File -from .flags import MemberFlags, SystemChannelFlags +from .flags import SystemChannelFlags from .guild_scheduled_event import GuildScheduledEvent, GuildScheduledEventMetadata from .integrations import Integration, _integration_factory from .invite import Invite diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 09405c9248..c216ff69b0 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -75,7 +75,7 @@ def _from_data(self, data: OnboardingPayload): def __repr__(self) -> str: return ( f"" + f" default_channel_ids={self._default_channel_ids!r} enabled={self.enabled!r}>" ) @property diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 4c234ef0c6..61df111d8b 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -10,6 +10,7 @@ Onboarding, OnboardingPrompt, OnboardingPromptOption, + OnboardingPromptType, PartialEmoji, ) from disnake.types import onboarding as onboarding_types @@ -45,6 +46,7 @@ def onboarding_prompt() -> OnboardingPrompt: "single_select": True, "required": True, "in_onboarding": True, + "type": OnboardingPromptType.multiple_choice, } return OnboardingPrompt(**onboarding_prompt_payload) @@ -56,10 +58,8 @@ def onboarding() -> Onboarding: onboarding_payload: onboarding_types.Onboarding = { "guild_id": "123", "prompts": [], - "enable_onboarding_prompts": True, - "enable_default_channels": True, "default_channel_ids": ["456", "789"], - "responses": [], + "enabled": True, } return Onboarding( @@ -73,15 +73,12 @@ class TestOnboarding: def test_onboarding(self, onboarding: Onboarding) -> None: assert onboarding.guild.id == 123 assert onboarding.prompts == [] - assert onboarding.onboarding_prompts_enabled is True - assert onboarding.default_channels_enabled is True assert onboarding._default_channel_ids == [456, 789] - assert onboarding._responses == [] + assert onboarding.enabled is True def test_onboarding_repr(self, onboarding: Onboarding) -> None: assert repr(onboarding) == ( - "" + "" ) @@ -92,6 +89,7 @@ def test_onboarding_prompt(self, onboarding_prompt: OnboardingPrompt) -> None: assert onboarding_prompt.single_select is True assert onboarding_prompt.required is True assert onboarding_prompt.in_onboarding is True + assert onboarding_prompt.type == OnboardingPromptType.multiple_choice def test_onboarding_prompt_str(self, onboarding_prompt: OnboardingPrompt) -> None: assert str(onboarding_prompt) == "test" @@ -99,7 +97,7 @@ def test_onboarding_prompt_str(self, onboarding_prompt: OnboardingPrompt) -> Non def test_onboarding_prompt_repr(self, onboarding_prompt: OnboardingPrompt) -> None: assert repr(onboarding_prompt) == ( "" + " in_onboarding=True type=>" ) From de1424c4474e5ccd98359e424aad6d34ab09e7e1 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 9 Feb 2023 18:44:20 -0300 Subject: [PATCH 11/99] types --- disnake/types/onboarding.py | 8 +++++--- tests/test_onboarding.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index 46652acaa9..8807dfb01b 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT -from typing import List, Optional, TypedDict +from typing import List, Literal, Optional, TypedDict from typing_extensions import NotRequired @@ -8,6 +8,8 @@ # NOTE: PartialOnboardingXX is very redundant, TBD +OnboardingPromptType = Literal[0, 1] + class OnboardingPromptOption(TypedDict): id: Snowflake @@ -36,7 +38,7 @@ class OnboardingPrompt(TypedDict): single_select: bool required: bool in_onboarding: bool - type: int + type: OnboardingPromptType class PartialOnboardingPrompt(TypedDict): @@ -46,7 +48,7 @@ class PartialOnboardingPrompt(TypedDict): single_select: bool required: bool in_onboarding: bool - type: int + type: OnboardingPromptType class Onboarding(TypedDict): diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 61df111d8b..e7fb684deb 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -46,7 +46,7 @@ def onboarding_prompt() -> OnboardingPrompt: "single_select": True, "required": True, "in_onboarding": True, - "type": OnboardingPromptType.multiple_choice, + "type": OnboardingPromptType.multiple_choice, # type: ignore } return OnboardingPrompt(**onboarding_prompt_payload) From 5f53a1f821170bebe8ba5f63b692867e3be5a587 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:08:57 -0300 Subject: [PATCH 12/99] feat: change the way emoji is handled see d7d4d5bb08d0bba0760a001a80bce4745e441311 --- disnake/emoji.py | 3 +++ disnake/onboarding.py | 23 ++++++----------------- disnake/types/onboarding.py | 9 ++++----- tests/test_onboarding.py | 7 +++---- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/disnake/emoji.py b/disnake/emoji.py index 55e6eff8fc..634ad67e82 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -105,6 +105,9 @@ def _from_data(self, emoji: EmojiPayload) -> None: def _to_partial(self) -> PartialEmoji: return PartialEmoji(name=self.name, animated=self.animated, id=self.id) + def to_dict(self) -> EmojiPayload: + return self._to_partial().to_dict() + def __iter__(self) -> Iterator[Tuple[str, Any]]: for attr in self.__slots__: if attr[0] != "_": diff --git a/disnake/onboarding.py b/disnake/onboarding.py index c216ff69b0..a56b7702a4 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -7,7 +7,6 @@ from .enums import OnboardingPromptType, try_enum from .mixins import Hashable from .object import Object -from .utils import _get_as_snowflake if TYPE_CHECKING: from typing_extensions import Self @@ -37,8 +36,6 @@ class Onboarding: # NOTE: or GuildOnboarding? .. versionadded:: 2.9 - TO BE DONE... - Attributes ---------- guild: :class:`Guild` @@ -113,7 +110,7 @@ def __init__( *, title: str, description: str, - emoji: Optional[Union[str, PartialEmoji, Emoji]], + emoji: Union[str, PartialEmoji, Emoji], roles: Sequence[Snowflake], channels: Sequence[Snowflake], ): @@ -128,17 +125,13 @@ def __init__( self.roles = roles self.channels = channels - self.emoji: Optional[ - Union[str, PartialEmoji, Emoji] - ] = None # NOTE: not sure if this is nullable - if emoji is None: - self.emoji = None - elif isinstance(emoji, str): + self.emoji: Union[PartialEmoji, Emoji] + if isinstance(emoji, str): self.emoji = PartialEmoji.from_str(emoji) elif isinstance(emoji, _EmojiTag): self.emoji = emoji else: - raise TypeError("emoji must be None, a str, PartialEmoji, or Emoji instance") + raise TypeError("emoji must be a str, PartialEmoji, or Emoji instance") def __str__(self) -> str: return self.title @@ -151,12 +144,10 @@ def __repr__(self) -> str: ) def _to_dict(self) -> PartialOnboardingPromptOptionPayload: - emoji_name, emoji_id = PartialEmoji._emoji_to_name_id(self.emoji) payload: PartialOnboardingPromptOptionPayload = { "title": self.title, "description": self.description, - "emoji_name": emoji_name, - "emoji_id": emoji_id, + "emoji": self.emoji.to_dict(), "role_ids": [role.id for role in self.roles], "channel_ids": [channel.id for channel in self.channels], } @@ -168,9 +159,7 @@ def _to_dict(self) -> PartialOnboardingPromptOptionPayload: @classmethod def _from_dict(cls, *, data: OnboardingPromptOptionPayload, state: ConnectionState) -> Self: - emoji_id = _get_as_snowflake(data, "emoji_id") - emoji_name = data.get("emoji_name") - emoji = PartialEmoji._emoji_from_name_id(emoji_name, emoji_id, state=state) + emoji = PartialEmoji.from_dict(data["emoji"]) self = cls( title=data["title"], diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index 8807dfb01b..1449048b82 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -1,9 +1,10 @@ # SPDX-License-Identifier: MIT -from typing import List, Literal, Optional, TypedDict +from typing import List, Literal, TypedDict from typing_extensions import NotRequired +from .emoji import Emoji from .snowflake import Snowflake, SnowflakeList # NOTE: PartialOnboardingXX is very redundant, TBD @@ -15,8 +16,7 @@ class OnboardingPromptOption(TypedDict): id: Snowflake title: str description: str - emoji_id: Optional[Snowflake] - emoji_name: Optional[str] + emoji: Emoji role_ids: SnowflakeList channel_ids: SnowflakeList @@ -25,8 +25,7 @@ class PartialOnboardingPromptOption(TypedDict): id: NotRequired[Snowflake] title: str description: str - emoji_id: Optional[Snowflake] - emoji_name: Optional[str] + emoji: Emoji role_ids: SnowflakeList channel_ids: SnowflakeList diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index e7fb684deb..5fe886a977 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -18,8 +18,7 @@ onboarding_prompt_option_payload: onboarding_types.PartialOnboardingPromptOption = { "title": "test", "description": "test", - "emoji_id": "123", - "emoji_name": None, + "emoji": {"id": "123", "name": "", "animated": False}, "role_ids": ["456", "789"], "channel_ids": ["123", "456"], } @@ -31,7 +30,7 @@ def onboarding_prompt_option() -> OnboardingPromptOption: return OnboardingPromptOption( title="test", description="test", - emoji=PartialEmoji(name="", id=123), + emoji=PartialEmoji(name="", id=123, animated=False), roles=[Object(id="456"), Object(id="789")], channels=[Object(id="123"), Object(id="456")], ) @@ -107,7 +106,7 @@ def test_onboarding_prompt_option( ) -> None: assert onboarding_prompt_option.title == "test" assert onboarding_prompt_option.description == "test" - assert onboarding_prompt_option.emoji == PartialEmoji(name="", id=123) + assert onboarding_prompt_option.emoji == PartialEmoji(name="", id=123, animated=False) assert onboarding_prompt_option.roles == [Object(id="456"), Object(id="789")] assert onboarding_prompt_option.channels == [Object(id="123"), Object(id="456")] From a8fc69424cf438a6dcc52ad22defa9dc605182ba Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:10:24 -0300 Subject: [PATCH 13/99] docs: change changelog name --- changelog/{999.feature.rst => 928.feature.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog/{999.feature.rst => 928.feature.rst} (100%) diff --git a/changelog/999.feature.rst b/changelog/928.feature.rst similarity index 100% rename from changelog/999.feature.rst rename to changelog/928.feature.rst From a86e7e8ec1545777a5d00fbb66ad021785b317d8 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:26:16 -0300 Subject: [PATCH 14/99] fix: nox --- disnake/onboarding.py | 2 +- docs/api.rst | 14 ++++++++++++++ tests/test_onboarding.py | 5 +---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index a56b7702a4..6d485be92f 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional, Sequence, Union +from typing import TYPE_CHECKING, List, Sequence, Union from .emoji import Emoji, PartialEmoji, _EmojiTag from .enums import OnboardingPromptType, try_enum diff --git a/docs/api.rst b/docs/api.rst index 0ea757f406..5cac4101a6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3980,6 +3980,20 @@ of :class:`enum.Enum`. The metadata value (``integer``) is not equal to the guild's configured value. +.. class:: OnboardingPromptType + + Represents the type of onboarding prompt. + + .. versionadded:: 2.9 + + .. attribute:: multiple_choice + + The prompt is a multiple choice prompt. + + .. attribute:: dropdown + + The prompt is a dropdown prompt. + .. autoclass:: Event :members: diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 5fe886a977..4146874441 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -26,7 +26,6 @@ @pytest.fixture() def onboarding_prompt_option() -> OnboardingPromptOption: - return OnboardingPromptOption( title="test", description="test", @@ -38,7 +37,6 @@ def onboarding_prompt_option() -> OnboardingPromptOption: @pytest.fixture() def onboarding_prompt() -> OnboardingPrompt: - onboarding_prompt_payload: onboarding_types.PartialOnboardingPrompt = { "title": "test", "options": [], @@ -48,12 +46,11 @@ def onboarding_prompt() -> OnboardingPrompt: "type": OnboardingPromptType.multiple_choice, # type: ignore } - return OnboardingPrompt(**onboarding_prompt_payload) + return OnboardingPrompt(**onboarding_prompt_payload) # type: ignore @pytest.fixture() def onboarding() -> Onboarding: - onboarding_payload: onboarding_types.Onboarding = { "guild_id": "123", "prompts": [], From af3f09c5340e6871273017ace7f42731e45e1253 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:41:43 -0300 Subject: [PATCH 15/99] fix: docs --- docs/api.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 5cac4101a6..edb99276d1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3355,7 +3355,7 @@ of :class:`enum.Enum`. Possible attributes for :class:`AuditLogDiff`: - - :attr:`~AuditLogDiff.default_channel_ids` + - :attr:`~AuditLogDiff.default_channels` - :attr:`~AuditLogDiff.enable_default_channels` - :attr:`~AuditLogDiff.enable_onboarding_prompts` - :attr:`~AuditLogDiff.prompts` @@ -4886,6 +4886,12 @@ AuditLogDiff :type: List[:class:`OnboardingPrompt`] + .. attribute:: default_channels + + The list of default channels of an onboarding being changed. + + :type: List[:class:`~.abc.GuildChannel`] + Webhook Support ------------------ @@ -6155,6 +6161,7 @@ ApplicationRoleConnectionMetadata .. attributetable:: ApplicationRoleConnectionMetadata .. autoclass:: ApplicationRoleConnectionMetadata + OnboardingPrompt ~~~~~~~~~~~~~~~~ From 5c52ba9762d57d67d7a3bcc0758987bcfa065278 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:45:26 -0300 Subject: [PATCH 16/99] docs: this doesn't need permission --- disnake/guild.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index 9480f79b2c..f9871a23fd 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4592,14 +4592,10 @@ async def get_onboarding(self) -> Onboarding: Retrieves the guild onboarding object. - You must have :attr:`.Permissions.manage_guild` and :attr:`.Permissions.manage_roles` permissions to do this. - .. versionadded:: 2.9 Raises ------ - Forbidden - You do not have proper permissions to retrieve the guild onboarding object. HTTPException Retrieving the guild onboarding object failed. From f57a29d7b9ac2eccc424bdc03d7135c1e860b96e Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 25 Feb 2023 23:41:00 -0300 Subject: [PATCH 17/99] get -> fetch --- disnake/guild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/guild.py b/disnake/guild.py index f9871a23fd..ad805a4443 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4587,7 +4587,7 @@ async def create_automod_rule( ) return AutoModRule(data=data, guild=self) - async def get_onboarding(self) -> Onboarding: + async def fetch_onboarding(self) -> Onboarding: """|coro| Retrieves the guild onboarding object. From a7846ef4ba2723894c0430aedd3ab6b2f932c3b5 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:16:06 -0300 Subject: [PATCH 18/99] refactor: use `emoji._to_partial().to_dict()` instead --- disnake/emoji.py | 3 --- disnake/onboarding.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/disnake/emoji.py b/disnake/emoji.py index c5d139cef4..9d21a21700 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -105,9 +105,6 @@ def _from_data(self, emoji: EmojiPayload) -> None: def _to_partial(self) -> PartialEmoji: return PartialEmoji(name=self.name, animated=self.animated, id=self.id) - def to_dict(self) -> EmojiPayload: - return self._to_partial().to_dict() - def __iter__(self) -> Iterator[Tuple[str, Any]]: for attr in self.__slots__: if attr[0] != "_": diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 6d485be92f..90e0c21bc5 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -147,7 +147,7 @@ def _to_dict(self) -> PartialOnboardingPromptOptionPayload: payload: PartialOnboardingPromptOptionPayload = { "title": self.title, "description": self.description, - "emoji": self.emoji.to_dict(), + "emoji": self.emoji._to_partial().to_dict(), "role_ids": [role.id for role in self.roles], "channel_ids": [channel.id for channel in self.channels], } From a62cd2ae6bd6a096437c5ea3914be7aca4f5eef9 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:21:44 -0300 Subject: [PATCH 19/99] misc: remove some todos/notes --- disnake/onboarding.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 90e0c21bc5..c5b71c3605 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -28,10 +28,8 @@ "OnboardingPromptOption", ) -# TODO: Audit log events - -class Onboarding: # NOTE: or GuildOnboarding? +class Onboarding: """Represents a guild onboarding. .. versionadded:: 2.9 From 7bce38c395bb9884c70d794227611bf4b5e4c1d5 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:22:39 -0300 Subject: [PATCH 20/99] fix: don't store useless id --- disnake/onboarding.py | 1 - 1 file changed, 1 deletion(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index c5b71c3605..7e477e482d 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -64,7 +64,6 @@ def _from_data(self, data: OnboardingPayload): for prompt in data["prompts"] ] self.enabled = data["enabled"] - self._guild_id = int(data["guild_id"]) # NOTE: is this required? self._default_channel_ids = list(map(int, data["default_channel_ids"])) def __repr__(self) -> str: From 90d9813fd1c211a08bee1a146003f24a90d0304f Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:23:29 -0300 Subject: [PATCH 21/99] fix: improve code --- disnake/onboarding.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 7e477e482d..428f8a5bd2 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -50,17 +50,15 @@ class Onboarding: "enabled", "_guild_id", "_default_channel_ids", - "_state", ) - def __init__(self, *, state: ConnectionState, guild: Guild, data: OnboardingPayload): - self._state = state + def __init__(self, *, guild: Guild, data: OnboardingPayload): self.guild = guild self._from_data(data) def _from_data(self, data: OnboardingPayload): self.prompts = [ - OnboardingPrompt._from_dict(data=prompt, state=self._state) + OnboardingPrompt._from_dict(data=prompt, state=self.guild._state) for prompt in data["prompts"] ] self.enabled = data["enabled"] From cf92da34c7db1633ab9ed802f0c893db56654599 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:24:56 -0300 Subject: [PATCH 22/99] feat: use Guild.get_channel instead of private attribute --- disnake/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 428f8a5bd2..fd68458ab7 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -73,7 +73,7 @@ def __repr__(self) -> str: @property def default_channels(self) -> List[GuildChannel]: """List[:class:`abc.GuildChannel`]: A list of channels that will display in Browse Channels.""" - return list(filter(None, map(self.guild._channels.get, self._default_channel_ids))) + return list(filter(None, map(self.guild.get_channel, self._default_channel_ids))) class OnboardingPromptOption(Hashable): From 2c704bd2874b1aa7be74ee7f31c42e137554296c Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:31:57 -0300 Subject: [PATCH 23/99] feat: add `roles` and `channels` attributes to `OnboardingPromptOption` --- disnake/onboarding.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index fd68458ab7..179181111a 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -13,7 +13,7 @@ from .abc import Snowflake from .guild import Guild, GuildChannel - from .state import ConnectionState + from .role import Role from .types.onboarding import ( Onboarding as OnboardingPayload, OnboardingPrompt as OnboardingPromptPayload, @@ -58,8 +58,7 @@ def __init__(self, *, guild: Guild, data: OnboardingPayload): def _from_data(self, data: OnboardingPayload): self.prompts = [ - OnboardingPrompt._from_dict(data=prompt, state=self.guild._state) - for prompt in data["prompts"] + OnboardingPrompt._from_dict(data=prompt, guild=self.guild) for prompt in data["prompts"] ] self.enabled = data["enabled"] self._default_channel_ids = list(map(int, data["default_channel_ids"])) @@ -98,7 +97,7 @@ class OnboardingPromptOption(Hashable): The channels that the user will see when they select this option. """ - __slots__ = ("id", "title", "description", "emoji", "roles", "channels") + __slots__ = ("id", "title", "description", "emoji", "guild", "_roles_ids", "_channels_ids") def __init__( self, @@ -108,6 +107,7 @@ def __init__( emoji: Union[str, PartialEmoji, Emoji], roles: Sequence[Snowflake], channels: Sequence[Snowflake], + guild: Guild, ): # NOTE: (a very very important note), the ID may sometimes be a UNIX timestamp since # Onboarding changes are saved locally until you send the API request (that's how it works in client) @@ -117,8 +117,9 @@ def __init__( self.id = 0 self.title = title self.description = description - self.roles = roles - self.channels = channels + self.guild = guild + self._roles_ids = [role.id for role in roles] + self._channels_ids = [channel.id for channel in channels] self.emoji: Union[PartialEmoji, Emoji] if isinstance(emoji, str): @@ -143,8 +144,8 @@ def _to_dict(self) -> PartialOnboardingPromptOptionPayload: "title": self.title, "description": self.description, "emoji": self.emoji._to_partial().to_dict(), - "role_ids": [role.id for role in self.roles], - "channel_ids": [channel.id for channel in self.channels], + "role_ids": self._roles_ids, + "channel_ids": self._channels_ids, } if self.id: @@ -153,23 +154,30 @@ def _to_dict(self) -> PartialOnboardingPromptOptionPayload: return payload @classmethod - def _from_dict(cls, *, data: OnboardingPromptOptionPayload, state: ConnectionState) -> Self: + def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: emoji = PartialEmoji.from_dict(data["emoji"]) self = cls( title=data["title"], description=data["description"], emoji=emoji, - roles=[ - Object(id=role_id) for role_id in data["role_ids"] - ], # NOTE: should I get the Role here? - channels=[ - Object(id=channel_id) for channel_id in data["channel_ids"] - ], # NOTE: should I get the channel here? + roles=[Object(id=role_id) for role_id in data["role_ids"]], + channels=[Object(id=channel_id) for channel_id in data["channel_ids"]], + guild=guild, ) self.id = int(data["id"]) return self + @property + def roles(self) -> List[Role]: + """List[:class:`Role`]: A list of roles that will be added to the user when they select this option.""" + return list(filter(None, map(self.guild.get_role, self._roles_ids))) + + @property + def channels(self) -> List[GuildChannel]: + """List[:class:`abc.GuildChannel`]: A list of channels that the user will see when they select this option.""" + return list(filter(None, map(self.guild.get_channel, self._channels_ids))) + class OnboardingPrompt(Hashable): """Represents an onboarding prompt. @@ -241,11 +249,11 @@ def _to_dict(self) -> PartialOnboardingPromptPayload: return payload @classmethod - def _from_dict(cls, *, data: OnboardingPromptPayload, state: ConnectionState) -> Self: + def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: self = cls( title=data["title"], options=[ - OnboardingPromptOption._from_dict(data=option, state=state) + OnboardingPromptOption._from_dict(data=option, guild=guild) for option in data["options"] ], single_select=data["single_select"], From 4b0f2b3d90700a68f681572abd5b99132c4f93f0 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:35:17 -0300 Subject: [PATCH 24/99] tests: change OnboardingPrompt fixture --- tests/test_onboarding.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 4146874441..c9893bb114 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -24,7 +24,7 @@ } -@pytest.fixture() +@pytest.fixture def onboarding_prompt_option() -> OnboardingPromptOption: return OnboardingPromptOption( title="test", @@ -35,21 +35,24 @@ def onboarding_prompt_option() -> OnboardingPromptOption: ) -@pytest.fixture() +@pytest.fixture def onboarding_prompt() -> OnboardingPrompt: - onboarding_prompt_payload: onboarding_types.PartialOnboardingPrompt = { + onboarding_prompt_payload: onboarding_types.OnboardingPrompt = { + "id": 0, "title": "test", "options": [], "single_select": True, "required": True, "in_onboarding": True, - "type": OnboardingPromptType.multiple_choice, # type: ignore + "type": OnboardingPromptType.multiple_choice.value, } - return OnboardingPrompt(**onboarding_prompt_payload) # type: ignore + return OnboardingPrompt._from_dict( + data=onboarding_prompt_payload, guild=mock.Mock(Guild, id=123) + ) -@pytest.fixture() +@pytest.fixture def onboarding() -> Onboarding: onboarding_payload: onboarding_types.Onboarding = { "guild_id": "123", From 7e4998d01fc1607f52fef2c14ff5d497cf09cbad Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:38:22 -0300 Subject: [PATCH 25/99] fix: this doesn't need state anymore --- disnake/guild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/guild.py b/disnake/guild.py index 89f7c07420..8389785179 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4639,7 +4639,7 @@ async def fetch_onboarding(self) -> Onboarding: The guild onboarding object. """ data = await self._state.http.get_guild_onboarding(self.id) - return Onboarding(data=data, guild=self, state=self._state) + return Onboarding(data=data, guild=self) PlaceholderID = NewType("PlaceholderID", int) From 46597446c818ca1d19dde27c0e6f1a915cbd5674 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:39:00 -0300 Subject: [PATCH 26/99] feat: remove useless x-super-properties --- disnake/http.py | 1 - 1 file changed, 1 deletion(-) diff --git a/disnake/http.py b/disnake/http.py index 7b21592357..4fe718bf5e 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -283,7 +283,6 @@ async def request( # header creation headers: Dict[str, str] = { "User-Agent": self.user_agent, - "x-super-properties": "eyJvcyI6IldpbmRvd3MiLCJicm93c2VyIjoiRGlzY29yZCBDbGllbnQiLCJyZWxlYXNlX2NoYW5uZWwiOiJjYW5hcnkiLCJjbGllbnRfdmVyc2lvbiI6IjEuMC41NSIsIm9zX3ZlcnNpb24iOiIxMC4wLjIyNjIxIiwib3NfYXJjaCI6Ing2NCIsInN5c3RlbV9sb2NhbGUiOiJlcy00MTkiLCJjbGllbnRfYnVpbGRfbnVtYmVyIjoxNjU1NTksIm5hdGl2ZV9idWlsZF9udW1iZXIiOjI4MDk4LCJjbGllbnRfZXZlbnRfc291cmNlIjpudWxsfQ==", } if self.token is not None: From 87826920d26c6cd45f1b4fe8b7f2055af491c0e2 Mon Sep 17 00:00:00 2001 From: Victor <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 19:24:23 -0300 Subject: [PATCH 27/99] docs: apply suggestions Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: Victor <67214928+Victorsitou@users.noreply.github.com> --- changelog/928.feature.rst | 4 ---- disnake/guild.py | 6 +++--- disnake/onboarding.py | 18 +++++++++--------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/changelog/928.feature.rst b/changelog/928.feature.rst index 3cf3c6efbd..f0686f6cf0 100644 --- a/changelog/928.feature.rst +++ b/changelog/928.feature.rst @@ -1,8 +1,4 @@ Implement Onboarding. - Add :class:`Onboarding`, :class:`OnboardingPrompt` and :class:`OnboardingPromptOption`. - Add :meth:`Guild.get_onboarding`. - - Add new member flags. - - Add :class:`MemberFlags`. - - Add :attr:`Member.flags`. - - Add ``flags`` and ``verified`` parameters to :meth:`Member.edit`. - Add relevant :class:`AuditLogDiff` information. diff --git a/disnake/guild.py b/disnake/guild.py index 8389785179..b4fc184a22 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4624,19 +4624,19 @@ async def create_automod_rule( async def fetch_onboarding(self) -> Onboarding: """|coro| - Retrieves the guild onboarding object. + Retrieves the guild onboarding data. .. versionadded:: 2.9 Raises ------ HTTPException - Retrieving the guild onboarding object failed. + Retrieving the guild onboarding data failed. Returns ------- :class:`Onboarding` - The guild onboarding object. + The guild onboarding data. """ data = await self._state.http.get_guild_onboarding(self.id) return Onboarding(data=data, guild=self) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 179181111a..9aa465c687 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -37,11 +37,11 @@ class Onboarding: Attributes ---------- guild: :class:`Guild` - The guild this onboarding belongs to. + The guild this onboarding is part of. prompts: List[:class:`OnboardingPrompt`] - The prompts the onboarding has. + The onboarding prompts. enabled: bool - Whether the onboarding is enabled. + Whether onboarding is enabled. """ __slots__ = ( @@ -71,7 +71,7 @@ def __repr__(self) -> str: @property def default_channels(self) -> List[GuildChannel]: - """List[:class:`abc.GuildChannel`]: A list of channels that will display in Browse Channels.""" + """List[:class:`abc.GuildChannel`]: The list of channels that will be shown to new members by default.""" return list(filter(None, map(self.guild.get_channel, self._default_channel_ids))) @@ -89,12 +89,12 @@ class OnboardingPromptOption(Hashable): The option's title. description: :class:`str` The option's description. - emoji: Optional[Union[:class:`str`, :class:`PartialEmoji`, :class:`Emoji`]] + emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`]] The option's emoji. roles: List[:class:`abc.Snowflake`] - The roles that will be added to the user when they select this option. + The roles that will be assigned to the user when they select this option. channels: List[:class:`abc.Snowflake`] - The channels that the user will see when they select this option. + The channels that the user will be opted into when they select this option. """ __slots__ = ("id", "title", "description", "emoji", "guild", "_roles_ids", "_channels_ids") @@ -196,9 +196,9 @@ class OnboardingPrompt(Hashable): single_select: :class:`bool` Whether only one option can be selected. required: :class:`bool` - Whether one option must be selected. + Whether at least one option must be selected. in_onboarding: :class:`bool` - Whether this prompt is in the onboarding. + Whether this prompt is in the initial onboarding flow. type: :class:`OnboardingPromptType` The onboarding prompt's type. """ From 091d1a852853b011c2ce1cd0ea0f573c1bc6b96d Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 19:28:06 -0300 Subject: [PATCH 28/99] lint: fix pyright --- disnake/audit_logs.py | 4 ++-- tests/test_onboarding.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/disnake/audit_logs.py b/disnake/audit_logs.py index 0ae3525807..bbfdac388f 100644 --- a/disnake/audit_logs.py +++ b/disnake/audit_logs.py @@ -299,7 +299,7 @@ def _transform_onboarding_prompt_option( if data is None: return None - return OnboardingPromptOption._from_dict(data=data, state=entry._state) + return OnboardingPromptOption._from_dict(data=data, guild=entry.guild) def _transform_onboarding_prompt( @@ -308,7 +308,7 @@ def _transform_onboarding_prompt( if data is None: return None - return OnboardingPrompt._from_dict(data=data, state=entry._state) + return OnboardingPrompt._from_dict(data=data, guild=entry.guild) class AuditLogDiff: diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index c9893bb114..cd50f9815d 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -32,6 +32,7 @@ def onboarding_prompt_option() -> OnboardingPromptOption: emoji=PartialEmoji(name="", id=123, animated=False), roles=[Object(id="456"), Object(id="789")], channels=[Object(id="123"), Object(id="456")], + guild=mock.Mock(Guild, id=123), ) @@ -62,7 +63,6 @@ def onboarding() -> Onboarding: } return Onboarding( - state=mock.Mock(http=mock.AsyncMock()), guild=mock.Mock(Guild, id=123), data=onboarding_payload, ) From 61ddcf41e3f249e5491011b06d7384362e975fda Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 21:03:15 -0300 Subject: [PATCH 29/99] changelog: get -> fetch --- changelog/928.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/928.feature.rst b/changelog/928.feature.rst index f0686f6cf0..99a82a1285 100644 --- a/changelog/928.feature.rst +++ b/changelog/928.feature.rst @@ -1,4 +1,4 @@ Implement Onboarding. - Add :class:`Onboarding`, :class:`OnboardingPrompt` and :class:`OnboardingPromptOption`. - - Add :meth:`Guild.get_onboarding`. + - Add :meth:`Guild.fetch_onboarding`. - Add relevant :class:`AuditLogDiff` information. From 1d8cd920ec3715f6a3c5b1eddb9e58420f764ea3 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 2 Mar 2023 21:32:29 -0300 Subject: [PATCH 30/99] feat: use `_emoji_from_name_id` instead --- disnake/onboarding.py | 22 ++++++++++++++++------ disnake/partial_emoji.py | 4 ++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 9aa465c687..0aac8746b0 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT from __future__ import annotations -from typing import TYPE_CHECKING, List, Sequence, Union +from typing import TYPE_CHECKING, List, Optional, Sequence, Union from .emoji import Emoji, PartialEmoji, _EmojiTag from .enums import OnboardingPromptType, try_enum @@ -104,7 +104,7 @@ def __init__( *, title: str, description: str, - emoji: Union[str, PartialEmoji, Emoji], + emoji: Optional[Union[str, PartialEmoji, Emoji]], roles: Sequence[Snowflake], channels: Sequence[Snowflake], guild: Guild, @@ -121,8 +121,10 @@ def __init__( self._roles_ids = [role.id for role in roles] self._channels_ids = [channel.id for channel in channels] - self.emoji: Union[PartialEmoji, Emoji] - if isinstance(emoji, str): + self.emoji: Optional[Union[PartialEmoji, Emoji]] = None + if emoji is None: + self.emoji = None + elif isinstance(emoji, str): self.emoji = PartialEmoji.from_str(emoji) elif isinstance(emoji, _EmojiTag): self.emoji = emoji @@ -143,7 +145,7 @@ def _to_dict(self) -> PartialOnboardingPromptOptionPayload: payload: PartialOnboardingPromptOptionPayload = { "title": self.title, "description": self.description, - "emoji": self.emoji._to_partial().to_dict(), + "emoji": self.emoji._to_partial().to_dict() if self.emoji else {"name": "", "id": None}, "role_ids": self._roles_ids, "channel_ids": self._channels_ids, } @@ -155,7 +157,15 @@ def _to_dict(self) -> PartialOnboardingPromptOptionPayload: @classmethod def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: - emoji = PartialEmoji.from_dict(data["emoji"]) + name = data["emoji"]["name"] + id = data["emoji"]["id"] + animated = data["emoji"]["animated"] if "animated" in data["emoji"] else False + emoji = PartialEmoji._emoji_from_name_id( + name=name, + id=int(id) if id else None, + animated=animated, + state=guild._state, + ) self = cls( title=data["title"], diff --git a/disnake/partial_emoji.py b/disnake/partial_emoji.py index 7bc3a47fae..98d1972d00 100644 --- a/disnake/partial_emoji.py +++ b/disnake/partial_emoji.py @@ -271,7 +271,7 @@ def _emoji_to_name_id( # utility method for unusual emoji model in forums @staticmethod def _emoji_from_name_id( - name: Optional[str], id: Optional[int], *, state: ConnectionState + name: Optional[str], id: Optional[int], animated: bool = False, *, state: ConnectionState ) -> Optional[Union[Emoji, PartialEmoji]]: if not (name or id): return None @@ -286,6 +286,6 @@ def _emoji_from_name_id( # This may change in a future API version, but for now we'll just have to accept it. name=name or "", id=id, - # `animated` is unknown but presumably we already got the (animated) emoji from the guild cache at this point + animated=animated, ) return emoji From 5afb86aae856f88204da4d02f6b77c430effb970 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Wed, 8 Mar 2023 10:36:48 -0300 Subject: [PATCH 31/99] tests: remove repr test --- tests/test_onboarding.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index cd50f9815d..756ed1135d 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -75,11 +75,6 @@ def test_onboarding(self, onboarding: Onboarding) -> None: assert onboarding._default_channel_ids == [456, 789] assert onboarding.enabled is True - def test_onboarding_repr(self, onboarding: Onboarding) -> None: - assert repr(onboarding) == ( - "" - ) - class TestOnboardingPrompt: def test_onboarding_prompt(self, onboarding_prompt: OnboardingPrompt) -> None: @@ -93,12 +88,6 @@ def test_onboarding_prompt(self, onboarding_prompt: OnboardingPrompt) -> None: def test_onboarding_prompt_str(self, onboarding_prompt: OnboardingPrompt) -> None: assert str(onboarding_prompt) == "test" - def test_onboarding_prompt_repr(self, onboarding_prompt: OnboardingPrompt) -> None: - assert repr(onboarding_prompt) == ( - ">" - ) - class TestOnboardingPromptOption: def test_onboarding_prompt_option( @@ -114,11 +103,3 @@ def test_onboarding_prompt_option_str( self, onboarding_prompt_option: OnboardingPromptOption ) -> None: assert str(onboarding_prompt_option) == "test" - - def test_onboarding_prompt_option_repr( - self, onboarding_prompt_option: OnboardingPromptOption - ) -> None: - assert repr(onboarding_prompt_option) == ( - " roles=[, ] channels=[, ]>" - ) From b16f9b5b83d691ce41511cd0e1961fab83f10223 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Wed, 8 Mar 2023 10:38:43 -0300 Subject: [PATCH 32/99] docs: remove duplicate description --- disnake/onboarding.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 0aac8746b0..f52dcdfe72 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -91,10 +91,6 @@ class OnboardingPromptOption(Hashable): The option's description. emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`]] The option's emoji. - roles: List[:class:`abc.Snowflake`] - The roles that will be assigned to the user when they select this option. - channels: List[:class:`abc.Snowflake`] - The channels that the user will be opted into when they select this option. """ __slots__ = ("id", "title", "description", "emoji", "guild", "_roles_ids", "_channels_ids") From d71866dcda85604dd5f00bb7df185381430746cb Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 31 Mar 2023 22:43:25 -0300 Subject: [PATCH 33/99] docs: update --- disnake/onboarding.py | 64 ++++++++++--------------------------- disnake/types/onboarding.py | 23 ------------- 2 files changed, 16 insertions(+), 71 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index f52dcdfe72..a4b56d0463 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -18,8 +18,6 @@ Onboarding as OnboardingPayload, OnboardingPrompt as OnboardingPromptPayload, OnboardingPromptOption as OnboardingPromptOptionPayload, - PartialOnboardingPrompt as PartialOnboardingPromptPayload, - PartialOnboardingPromptOption as PartialOnboardingPromptOptionPayload, ) __all__ = ( @@ -40,7 +38,7 @@ class Onboarding: The guild this onboarding is part of. prompts: List[:class:`OnboardingPrompt`] The onboarding prompts. - enabled: bool + enabled: :class:`bool` Whether onboarding is enabled. """ @@ -83,14 +81,13 @@ class OnboardingPromptOption(Hashable): Attributes ---------- id: :class:`int` - The option's ID. Note that if this option was manually constructed, - this will be ``0``. + The prompt option's ID. + emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`]] + The prompt option's emoji. title: :class:`str` - The option's title. + The prompt option's title. description: :class:`str` - The option's description. - emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`]] - The option's emoji. + The prompt option's description. """ __slots__ = ("id", "title", "description", "emoji", "guild", "_roles_ids", "_channels_ids") @@ -105,7 +102,7 @@ def __init__( channels: Sequence[Snowflake], guild: Guild, ): - # NOTE: (a very very important note), the ID may sometimes be a UNIX timestamp since + # NOTE: The ID may sometimes be a UNIX timestamp since # Onboarding changes are saved locally until you send the API request (that's how it works in client) # so the API needs the timestamp to know what ID it needs to create, should we just add a note about it # or "try" to create the ID ourselves? @@ -137,20 +134,6 @@ def __repr__(self) -> str: f"roles={self.roles!r} channels={self.channels!r}>" ) - def _to_dict(self) -> PartialOnboardingPromptOptionPayload: - payload: PartialOnboardingPromptOptionPayload = { - "title": self.title, - "description": self.description, - "emoji": self.emoji._to_partial().to_dict() if self.emoji else {"name": "", "id": None}, - "role_ids": self._roles_ids, - "channel_ids": self._channels_ids, - } - - if self.id: - payload["id"] = self.id - - return payload - @classmethod def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: name = data["emoji"]["name"] @@ -193,20 +176,20 @@ class OnboardingPrompt(Hashable): Attributes ---------- id: :class:`int` - The onboarding prompt's ID. Note that if this prompt was manually constructed, - this will be ``0``. - title: :class:`str` - The onboarding prompt's title. + The onboarding prompt's ID. + type: :class:`OnboardingPromptType` + The onboarding prompt's type. options: List[:class:`OnboardingPromptOption`] The onboarding prompt's options. + title: :class:`str` + The onboarding prompt's title. single_select: :class:`bool` - Whether only one option can be selected. + Whether users are limited to selecting one option for the prompt. required: :class:`bool` - Whether at least one option must be selected. + Whether the prompt is required before a user completes the onboarding flow. in_onboarding: :class:`bool` - Whether this prompt is in the initial onboarding flow. - type: :class:`OnboardingPromptType` - The onboarding prompt's type. + Whether the prompt is present in the onboarding flow. + If `false`, the prompt will only appear in the Channels & Roles tab """ __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding", "type") @@ -239,21 +222,6 @@ def __repr__(self) -> str: f" in_onboarding={self.in_onboarding!r} type={self.type!r}>" ) - def _to_dict(self) -> PartialOnboardingPromptPayload: - payload: PartialOnboardingPromptPayload = { - "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, - "type": self.type.value, - } - - if self.id: - payload["id"] = self.id - - return payload - @classmethod def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: self = cls( diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index 1449048b82..de1132c134 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -2,13 +2,9 @@ from typing import List, Literal, TypedDict -from typing_extensions import NotRequired - from .emoji import Emoji from .snowflake import Snowflake, SnowflakeList -# NOTE: PartialOnboardingXX is very redundant, TBD - OnboardingPromptType = Literal[0, 1] @@ -21,15 +17,6 @@ class OnboardingPromptOption(TypedDict): channel_ids: SnowflakeList -class PartialOnboardingPromptOption(TypedDict): - id: NotRequired[Snowflake] - title: str - description: str - emoji: Emoji - role_ids: SnowflakeList - channel_ids: SnowflakeList - - class OnboardingPrompt(TypedDict): id: Snowflake title: str @@ -40,16 +27,6 @@ class OnboardingPrompt(TypedDict): type: OnboardingPromptType -class PartialOnboardingPrompt(TypedDict): - id: NotRequired[Snowflake] - title: str - options: List[PartialOnboardingPromptOption] - single_select: bool - required: bool - in_onboarding: bool - type: OnboardingPromptType - - class Onboarding(TypedDict): guild_id: Snowflake prompts: List[OnboardingPrompt] From 2b81f4c90af51506ae7f6c0843f514b58be80f7a Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 31 Mar 2023 22:47:13 -0300 Subject: [PATCH 34/99] revert!: audit logs :c --- disnake/audit_logs.py | 34 ----------- disnake/enums.py | 14 ----- docs/api.rst | 130 ------------------------------------------ 3 files changed, 178 deletions(-) diff --git a/disnake/audit_logs.py b/disnake/audit_logs.py index bbfdac388f..1ac6baca26 100644 --- a/disnake/audit_logs.py +++ b/disnake/audit_logs.py @@ -27,7 +27,6 @@ from .invite import Invite from .mixins import Hashable from .object import Object -from .onboarding import OnboardingPrompt, OnboardingPromptOption from .partial_emoji import PartialEmoji from .permissions import PermissionOverwrite, Permissions from .threads import ForumTag, Thread @@ -65,10 +64,6 @@ DefaultReaction as DefaultReactionPayload, PermissionOverwrite as PermissionOverwritePayload, ) - from .types.onboarding import ( - OnboardingPrompt as OnboardingPromptPayload, - OnboardingPromptOption as OnboardingPromptOptionPayload, - ) from .types.role import Role as RolePayload from .types.snowflake import Snowflake from .types.threads import ForumTag as ForumTagPayload @@ -293,24 +288,6 @@ def _transform_default_reaction( ) -def _transform_onboarding_prompt_option( - entry: AuditLogEntry, data: Optional[OnboardingPromptOptionPayload] -) -> Optional[OnboardingPromptOption]: - if data is None: - return None - - return OnboardingPromptOption._from_dict(data=data, guild=entry.guild) - - -def _transform_onboarding_prompt( - entry: AuditLogEntry, data: Optional[OnboardingPromptPayload] -) -> Optional[OnboardingPrompt]: - if data is None: - return None - - return OnboardingPrompt._from_dict(data=data, guild=entry.guild) - - class AuditLogDiff: def __len__(self) -> int: return len(self.__dict__) @@ -386,9 +363,6 @@ class AuditLogChanges: "available_tags": (None, _list_transformer(_transform_tag)), "default_reaction_emoji": ("default_reaction", _transform_default_reaction), "default_sort_order": (None, _enum_transformer(enums.ThreadSortOrder)), - "options": (None, _list_transformer(_transform_onboarding_prompt_option)), - "prompts": (None, _list_transformer(_transform_onboarding_prompt)), - "default_channel_ids": ("default_channels", _list_transformer(_transform_channel)), } # fmt: on @@ -868,11 +842,3 @@ def _convert_target_application_command_or_integration( def _convert_target_automod_rule(self, target_id: int) -> Union[AutoModRule, Object]: return self._automod_rules.get(target_id) or Object(id=target_id) - - def _convert_target_onboarding_prompt(self, target_id: int) -> Object: - # Here we have two options: - # 1. Fecth the onboarding and get the prompt (unnecessary) - # 2. Store Onboarding\s when they're fetched (since there are no gateway events for it yet?) - # in ConnectionState and get it here. - # For now I'll return Object. - return Object(id=target_id) diff --git a/disnake/enums.py b/disnake/enums.py index 5fec02f6aa..0f9e2204b9 100644 --- a/disnake/enums.py +++ b/disnake/enums.py @@ -393,11 +393,6 @@ class AuditLogAction(Enum): automod_block_message = 143 automod_send_alert_message = 144 automod_timeout = 145 - onboarding_prompt_create = 163 - onboarding_prompt_update = 164 - onboarding_prompt_delete = 165 - onboarding_create = 166 - onboarding_update = 167 # fmt: on @property @@ -458,11 +453,6 @@ def category(self) -> Optional[AuditLogActionCategory]: AuditLogAction.automod_block_message: None, AuditLogAction.automod_send_alert_message: None, AuditLogAction.automod_timeout: None, - 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] @@ -508,10 +498,6 @@ def target_type(self) -> Optional[str]: return "automod_rule" elif v < 146: return "user" - elif v < 166: - return "onboarding_prompt" - elif v < 168: - return "onboarding" else: return None diff --git a/docs/api.rst b/docs/api.rst index 67244a6b94..7a17f69932 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3326,76 +3326,6 @@ of :class:`enum.Enum`. See :attr:`automod_block_message` for more information on how the :attr:`~AuditLogEntry.extra` field is set. - .. attribute:: onboarding_prompt_create - - An onboarding prompt was created. - - When this is the action, the type of :attr:`~AuditLogEntry.target` is - the :class:`Object` with the ID of the onboarding prompt which - was created. - - Possible attributes for :class:`AuditLogDiff`: - - - :attr:`~AuditLogDiff.id` - - :attr:`~AuditLogDiff.title` - - :attr:`~AuditLogDiff.options` - - :attr:`~AuditLogDiff.single_select` - - :attr:`~AuditLogDiff.required` - - :attr:`~AuditLogDiff.in_onboarding` - - .. attribute:: onboarding_prompt_update - - An onboarding prompt was updated. - - When this is the action, the type of :attr:`~AuditLogEntry.target` is - the :class:`Object` with the ID of the onboarding prompt which - was updated. - - Possible attributes for :class:`AuditLogDiff`: - - - :attr:`~AuditLogDiff.title` - - :attr:`~AuditLogDiff.options` - - :attr:`~AuditLogDiff.single_select` - - :attr:`~AuditLogDiff.required` - - :attr:`~AuditLogDiff.in_onboarding` - - .. attribute:: onboarding_prompt_delete - - An onboarding prompt was deleted. - - When this is the action, the type of :attr:`~AuditLogEntry.target` is - the :class:`Object` with the ID of the onboarding prompt which - was deleted. - - Possible attributes for :class:`AuditLogDiff`: - - TBD. - - .. attribute:: onboarding_create - - An onboarding was created. - - When this is the action, the type of :attr:`~AuditLogEntry.target` is - ``None``. - - Possible attributes for :class:`AuditLogDiff`: - - - :attr:`~AuditLogDiff.default_channels` - - :attr:`~AuditLogDiff.enable_default_channels` - - :attr:`~AuditLogDiff.enable_onboarding_prompts` - - :attr:`~AuditLogDiff.prompts` - - .. attribute:: onboarding_update - - An onboarding was updated. - - When this is the action, the type of :attr:`~AuditLogEntry.target` is - ``None``. - - Possible attributes for :class:`AuditLogDiff`: - - TBD. - .. class:: AuditLogActionCategory Represents the category that the :class:`AuditLogAction` belongs to. @@ -4858,66 +4788,6 @@ AuditLogDiff :type: Optional[:class:`ThreadSortOrder`] - .. attribute:: title - - The title of an onboarding prompt or an onboarding prompt option being changed. - - :type: :class:`str` - - .. attribute:: options - - The list of options of an onboarding prompt being changed. - - :type: List[:class:`OnboardingPromptOption`] - - .. attribute:: single_select - - The onboarding prompt is single select or not. - - :type: :class:`bool` - - .. attribute:: required - - The onboarding prompt option is required or not. - - :type: :class:`bool` - - .. attribute:: in_onboarding - - The onboarding prompt option is in onboarding or not. - - :type: :class:`bool` - - .. attribute:: default_channel - - The list of default channels of an onboarding being changed. - - :type: List[:class:`abc.GuildChannel`] - - .. attribute:: enable_default_channels - - The onboarding shows default channels or not. - - :type: :class:`bool` - - .. attribute:: enable_onboarding_prompts - - The onboarding shows onboarding prompts or not. - - :type: :class:`bool` - - .. attribute:: prompts - - The list of prompts of an onboarding being changed. - - :type: List[:class:`OnboardingPrompt`] - - .. attribute:: default_channels - - The list of default channels of an onboarding being changed. - - :type: List[:class:`~.abc.GuildChannel`] - Webhook Support ------------------ From 98daea963d14e8209584c21ec1d1a43a95f0ba77 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 31 Mar 2023 23:05:08 -0300 Subject: [PATCH 35/99] tests: fix test --- tests/test_onboarding.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 756ed1135d..649d3323df 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -15,7 +15,8 @@ ) from disnake.types import onboarding as onboarding_types -onboarding_prompt_option_payload: onboarding_types.PartialOnboardingPromptOption = { +onboarding_prompt_option_payload: onboarding_types.OnboardingPromptOption = { + "id": "0", "title": "test", "description": "test", "emoji": {"id": "123", "name": "", "animated": False}, From aa528cf8eb5f95af82e5b1a1812c91b4cfae73ff Mon Sep 17 00:00:00 2001 From: Victor <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:07:38 -0300 Subject: [PATCH 36/99] docs: update Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: Victor <67214928+Victorsitou@users.noreply.github.com> --- changelog/928.feature.rst | 1 - disnake/onboarding.py | 7 +++---- docs/api.rst | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/changelog/928.feature.rst b/changelog/928.feature.rst index 99a82a1285..62b363d49a 100644 --- a/changelog/928.feature.rst +++ b/changelog/928.feature.rst @@ -1,4 +1,3 @@ Implement Onboarding. - Add :class:`Onboarding`, :class:`OnboardingPrompt` and :class:`OnboardingPromptOption`. - Add :meth:`Guild.fetch_onboarding`. - - Add relevant :class:`AuditLogDiff` information. diff --git a/disnake/onboarding.py b/disnake/onboarding.py index a4b56d0463..8ad8ba4e8d 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -28,7 +28,7 @@ class Onboarding: - """Represents a guild onboarding. + """Represents a guild onboarding object. .. versionadded:: 2.9 @@ -37,7 +37,7 @@ class Onboarding: guild: :class:`Guild` The guild this onboarding is part of. prompts: List[:class:`OnboardingPrompt`] - The onboarding prompts. + The prompts shown during onboarding and in community customization. enabled: :class:`bool` Whether onboarding is enabled. """ @@ -46,7 +46,6 @@ class Onboarding: "guild", "prompts", "enabled", - "_guild_id", "_default_channel_ids", ) @@ -189,7 +188,7 @@ class OnboardingPrompt(Hashable): Whether the prompt is required before a user completes the onboarding flow. in_onboarding: :class:`bool` Whether the prompt is present in the onboarding flow. - If `false`, the prompt will only appear in the Channels & Roles tab + If ``False``, the prompt will only appear in community customization. """ __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding", "type") diff --git a/docs/api.rst b/docs/api.rst index 7a17f69932..8670ca238e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -6057,6 +6057,7 @@ ApplicationRoleConnectionMetadata .. attributetable:: ApplicationRoleConnectionMetadata .. autoclass:: ApplicationRoleConnectionMetadata + :members: OnboardingPrompt ~~~~~~~~~~~~~~~~ From dae932be51d4522f81a40a11a416f251f3bc2354 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:16:30 -0300 Subject: [PATCH 37/99] feat: apply code suggestions --- disnake/onboarding.py | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 8ad8ba4e8d..2ec4a0d60d 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -40,13 +40,15 @@ class Onboarding: The prompts shown during onboarding and in community customization. enabled: :class:`bool` Whether onboarding is enabled. + default_channel_ids: FrozenSet[:class:`int`] + The IDs of the channels that will automatically be shown to new members. """ __slots__ = ( "guild", "prompts", "enabled", - "_default_channel_ids", + "default_channel_ids", ) def __init__(self, *, guild: Guild, data: OnboardingPayload): @@ -58,18 +60,21 @@ def _from_data(self, data: OnboardingPayload): OnboardingPrompt._from_dict(data=prompt, guild=self.guild) for prompt in data["prompts"] ] self.enabled = data["enabled"] - self._default_channel_ids = list(map(int, data["default_channel_ids"])) + self.default_channel_ids = ( + frozenset(map(int, exempt_channels)) + if (exempt_channels := data["default_channel_ids"]) + else frozenset() + ) def __repr__(self) -> str: return ( - f"" + f"" ) @property def default_channels(self) -> List[GuildChannel]: """List[:class:`abc.GuildChannel`]: The list of channels that will be shown to new members by default.""" - return list(filter(None, map(self.guild.get_channel, self._default_channel_ids))) + return list(filter(None, map(self.guild.get_channel, self.default_channel_ids))) class OnboardingPromptOption(Hashable): @@ -87,9 +92,13 @@ class OnboardingPromptOption(Hashable): The prompt option's title. description: :class:`str` The prompt option's description. + roles_ids: FrozenSet[:class:`int`] + The IDs of the roles that will be added to the user when they select this option. + channels_ids: FrozenSet[:class:`int`] + The IDs of the channels that will be shown to the user when they select this option. """ - __slots__ = ("id", "title", "description", "emoji", "guild", "_roles_ids", "_channels_ids") + __slots__ = ("id", "title", "description", "emoji", "guild", "roles_ids", "channels_ids") def __init__( self, @@ -110,8 +119,16 @@ def __init__( self.title = title self.description = description self.guild = guild - self._roles_ids = [role.id for role in roles] - self._channels_ids = [channel.id for channel in channels] + self.roles_ids = ( + frozenset(map(int, roles_ids)) + if (roles_ids := [role.id for role in roles]) + else frozenset() + ) + self.channels_ids = ( + frozenset(map(int, channels_ids)) + if (channels_ids := [channel.id for channel in channels]) + else frozenset() + ) self.emoji: Optional[Union[PartialEmoji, Emoji]] = None if emoji is None: @@ -129,8 +146,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return ( f"" + f"description={self.description!r} emoji={self.emoji!r}>" ) @classmethod @@ -159,12 +175,12 @@ def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Sel @property def roles(self) -> List[Role]: """List[:class:`Role`]: A list of roles that will be added to the user when they select this option.""" - return list(filter(None, map(self.guild.get_role, self._roles_ids))) + return list(filter(None, map(self.guild.get_role, self.roles_ids))) @property def channels(self) -> List[GuildChannel]: """List[:class:`abc.GuildChannel`]: A list of channels that the user will see when they select this option.""" - return list(filter(None, map(self.guild.get_channel, self._channels_ids))) + return list(filter(None, map(self.guild.get_channel, self.channels_ids))) class OnboardingPrompt(Hashable): From 4228c6033cbb4c0c9f2be8cea1ab1ed13f531f0d Mon Sep 17 00:00:00 2001 From: Victor <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:17:32 -0300 Subject: [PATCH 38/99] refactor: make emoji handling easier Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: Victor <67214928+Victorsitou@users.noreply.github.com> --- disnake/onboarding.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 2ec4a0d60d..c62a4028f9 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -151,15 +151,10 @@ def __repr__(self) -> str: @classmethod def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: - name = data["emoji"]["name"] - id = data["emoji"]["id"] - animated = data["emoji"]["animated"] if "animated" in data["emoji"] else False - emoji = PartialEmoji._emoji_from_name_id( - name=name, - id=int(id) if id else None, - animated=animated, - state=guild._state, - ) + if emoji_data := data.get("emoji"): + emoji = guild._state.get_reaction_emoji(emoji_data) + else: + emoji = None self = cls( title=data["title"], From 07d839b85a81f1facceaf2323833b3ab7af85ec7 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:19:07 -0300 Subject: [PATCH 39/99] types: make OnboardingPromptOption.description optional --- disnake/onboarding.py | 2 +- disnake/types/onboarding.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index c62a4028f9..cc869b0252 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -158,7 +158,7 @@ def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Sel self = cls( title=data["title"], - description=data["description"], + description=data.get("description") or "", emoji=emoji, roles=[Object(id=role_id) for role_id in data["role_ids"]], channels=[Object(id=channel_id) for channel_id in data["channel_ids"]], diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index de1132c134..79d1bb86a2 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT -from typing import List, Literal, TypedDict +from typing import List, Literal, Optional, TypedDict from .emoji import Emoji from .snowflake import Snowflake, SnowflakeList @@ -11,7 +11,7 @@ class OnboardingPromptOption(TypedDict): id: Snowflake title: str - description: str + description: Optional[str] emoji: Emoji role_ids: SnowflakeList channel_ids: SnowflakeList From 52a487cb3c3d3f30bd38465183e6cb22e53828bb Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:19:29 -0300 Subject: [PATCH 40/99] move stuff --- disnake/onboarding.py | 142 +++++++++++++++++++++--------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index cc869b0252..ca3e2c4c49 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -77,6 +77,77 @@ def default_channels(self) -> List[GuildChannel]: return list(filter(None, map(self.guild.get_channel, self.default_channel_ids))) +class OnboardingPrompt(Hashable): + """Represents an onboarding prompt. + + .. versionadded:: 2.9 + + Attributes + ---------- + id: :class:`int` + The onboarding prompt's ID. + type: :class:`OnboardingPromptType` + The onboarding prompt's type. + options: List[:class:`OnboardingPromptOption`] + The onboarding prompt's options. + title: :class:`str` + The onboarding prompt's title. + single_select: :class:`bool` + Whether users are limited to selecting one option for the prompt. + required: :class:`bool` + Whether the prompt is required before a user completes the onboarding flow. + in_onboarding: :class:`bool` + Whether the prompt is present in the onboarding flow. + If ``False``, the prompt will only appear in community customization. + """ + + __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding", "type") + + def __init__( + self, + *, + title: str, + options: List[OnboardingPromptOption], + single_select: bool, + required: bool, + in_onboarding: bool, + type: OnboardingPromptType, + ): + self.id = 0 + self.title = title + self.options = options + self.single_select = single_select + self.required = required + self.in_onboarding = in_onboarding + self.type = type + + def __str__(self) -> str: + return self.title + + def __repr__(self) -> str: + return ( + f"" + ) + + @classmethod + def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: + self = cls( + title=data["title"], + options=[ + OnboardingPromptOption._from_dict(data=option, guild=guild) + for option in data["options"] + ], + single_select=data["single_select"], + required=data["required"], + in_onboarding=data["in_onboarding"], + type=try_enum(OnboardingPromptType, data["type"]), + ) + self.id = int(data["id"]) + return self + + class OnboardingPromptOption(Hashable): """Represents an onboarding prompt option. @@ -176,74 +247,3 @@ def roles(self) -> List[Role]: def channels(self) -> List[GuildChannel]: """List[:class:`abc.GuildChannel`]: A list of channels that the user will see when they select this option.""" return list(filter(None, map(self.guild.get_channel, self.channels_ids))) - - -class OnboardingPrompt(Hashable): - """Represents an onboarding prompt. - - .. versionadded:: 2.9 - - Attributes - ---------- - id: :class:`int` - The onboarding prompt's ID. - type: :class:`OnboardingPromptType` - The onboarding prompt's type. - options: List[:class:`OnboardingPromptOption`] - The onboarding prompt's options. - title: :class:`str` - The onboarding prompt's title. - single_select: :class:`bool` - Whether users are limited to selecting one option for the prompt. - required: :class:`bool` - Whether the prompt is required before a user completes the onboarding flow. - in_onboarding: :class:`bool` - Whether the prompt is present in the onboarding flow. - If ``False``, the prompt will only appear in community customization. - """ - - __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding", "type") - - def __init__( - self, - *, - title: str, - options: List[OnboardingPromptOption], - single_select: bool, - required: bool, - in_onboarding: bool, - type: OnboardingPromptType, - ): - self.id = 0 - self.title = title - self.options = options - self.single_select = single_select - self.required = required - self.in_onboarding = in_onboarding - self.type = type - - def __str__(self) -> str: - return self.title - - def __repr__(self) -> str: - return ( - f"" - ) - - @classmethod - def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: - self = cls( - title=data["title"], - options=[ - OnboardingPromptOption._from_dict(data=option, guild=guild) - for option in data["options"] - ], - single_select=data["single_select"], - required=data["required"], - in_onboarding=data["in_onboarding"], - type=try_enum(OnboardingPromptType, data["type"]), - ) - self.id = int(data["id"]) - return self From 7466f3a7aeff7868b8ef8c84e078ca0fd523cb60 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:27:47 -0300 Subject: [PATCH 41/99] refactor: avoid `_from_data` and create object in `__init__` --- disnake/onboarding.py | 118 ++++++++++++------------------------------ 1 file changed, 34 insertions(+), 84 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index ca3e2c4c49..65c4f17bb9 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -1,17 +1,12 @@ # SPDX-License-Identifier: MIT from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional, Sequence, Union +from typing import TYPE_CHECKING, List -from .emoji import Emoji, PartialEmoji, _EmojiTag from .enums import OnboardingPromptType, try_enum from .mixins import Hashable -from .object import Object if TYPE_CHECKING: - from typing_extensions import Self - - from .abc import Snowflake from .guild import Guild, GuildChannel from .role import Role from .types.onboarding import ( @@ -57,7 +52,7 @@ def __init__(self, *, guild: Guild, data: OnboardingPayload): def _from_data(self, data: OnboardingPayload): self.prompts = [ - OnboardingPrompt._from_dict(data=prompt, guild=self.guild) for prompt in data["prompts"] + OnboardingPrompt(data=prompt, guild=self.guild) for prompt in data["prompts"] ] self.enabled = data["enabled"] self.default_channel_ids = ( @@ -101,25 +96,29 @@ class OnboardingPrompt(Hashable): If ``False``, the prompt will only appear in community customization. """ - __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding", "type") - - def __init__( - self, - *, - title: str, - options: List[OnboardingPromptOption], - single_select: bool, - required: bool, - in_onboarding: bool, - type: OnboardingPromptType, - ): + __slots__ = ( + "guild", + "id", + "title", + "options", + "single_select", + "required", + "in_onboarding", + "type", + ) + + def __init__(self, *, guild: Guild, data: OnboardingPromptPayload): + self.guild = guild + self.id = 0 - self.title = title - self.options = options - self.single_select = single_select - self.required = required - self.in_onboarding = in_onboarding - self.type = type + self.title = data["title"] + self.options = [ + OnboardingPromptOption(data=option, guild=guild) for option in data["options"] + ] + self.single_select = data["single_select"] + self.required = data["required"] + self.in_onboarding = data["in_onboarding"] + self.type = try_enum(OnboardingPromptType, data["type"]) def __str__(self) -> str: return self.title @@ -131,22 +130,6 @@ def __repr__(self) -> str: f" in_onboarding={self.in_onboarding!r} type={self.type!r}>" ) - @classmethod - def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: - self = cls( - title=data["title"], - options=[ - OnboardingPromptOption._from_dict(data=option, guild=guild) - for option in data["options"] - ], - single_select=data["single_select"], - required=data["required"], - in_onboarding=data["in_onboarding"], - type=try_enum(OnboardingPromptType, data["type"]), - ) - self.id = int(data["id"]) - return self - class OnboardingPromptOption(Hashable): """Represents an onboarding prompt option. @@ -171,45 +154,30 @@ class OnboardingPromptOption(Hashable): __slots__ = ("id", "title", "description", "emoji", "guild", "roles_ids", "channels_ids") - def __init__( - self, - *, - title: str, - description: str, - emoji: Optional[Union[str, PartialEmoji, Emoji]], - roles: Sequence[Snowflake], - channels: Sequence[Snowflake], - guild: Guild, - ): + def __init__(self, *, guild: Guild, data: OnboardingPromptOptionPayload): # NOTE: The ID may sometimes be a UNIX timestamp since # Onboarding changes are saved locally until you send the API request (that's how it works in client) # so the API needs the timestamp to know what ID it needs to create, should we just add a note about it # or "try" to create the ID ourselves? # I'm not sure if this also happens for OnboardingPrompt - self.id = 0 - self.title = title - self.description = description self.guild = guild + + self.id = 0 + self.title = data["title"] + self.description = data["description"] self.roles_ids = ( - frozenset(map(int, roles_ids)) - if (roles_ids := [role.id for role in roles]) - else frozenset() + frozenset(map(int, roles_ids)) if (roles_ids := data.get("role_ids")) else frozenset() ) self.channels_ids = ( frozenset(map(int, channels_ids)) - if (channels_ids := [channel.id for channel in channels]) + if (channels_ids := data.get("channel_ids")) else frozenset() ) - self.emoji: Optional[Union[PartialEmoji, Emoji]] = None - if emoji is None: - self.emoji = None - elif isinstance(emoji, str): - self.emoji = PartialEmoji.from_str(emoji) - elif isinstance(emoji, _EmojiTag): - self.emoji = emoji + if emoji_data := data.get("emoji"): + self.emoji = guild._state.get_reaction_emoji(emoji_data) else: - raise TypeError("emoji must be a str, PartialEmoji, or Emoji instance") + self.emoji = None def __str__(self) -> str: return self.title @@ -220,24 +188,6 @@ def __repr__(self) -> str: f"description={self.description!r} emoji={self.emoji!r}>" ) - @classmethod - def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: - if emoji_data := data.get("emoji"): - emoji = guild._state.get_reaction_emoji(emoji_data) - else: - emoji = None - - self = cls( - title=data["title"], - description=data.get("description") or "", - emoji=emoji, - roles=[Object(id=role_id) for role_id in data["role_ids"]], - channels=[Object(id=channel_id) for channel_id in data["channel_ids"]], - guild=guild, - ) - self.id = int(data["id"]) - return self - @property def roles(self) -> List[Role]: """List[:class:`Role`]: A list of roles that will be added to the user when they select this option.""" From e08cf9fa827d202f1bbfccda111aee168ba7bf5d Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:37:39 -0300 Subject: [PATCH 42/99] test: update test --- tests/test_onboarding.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 649d3323df..2d71365f7c 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -49,9 +49,7 @@ def onboarding_prompt() -> OnboardingPrompt: "type": OnboardingPromptType.multiple_choice.value, } - return OnboardingPrompt._from_dict( - data=onboarding_prompt_payload, guild=mock.Mock(Guild, id=123) - ) + return OnboardingPrompt(data=onboarding_prompt_payload, guild=mock.Mock(Guild, id=123)) @pytest.fixture From cfab72d037a7d3cd6303c1817e99ec19895f2a39 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:37:47 -0300 Subject: [PATCH 43/99] misc: minor fixes --- disnake/onboarding.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 65c4f17bb9..eaf7a60d5d 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -110,7 +110,7 @@ class OnboardingPrompt(Hashable): def __init__(self, *, guild: Guild, data: OnboardingPromptPayload): self.guild = guild - self.id = 0 + self.id = int(data["id"]) self.title = data["title"] self.options = [ OnboardingPromptOption(data=option, guild=guild) for option in data["options"] @@ -144,7 +144,7 @@ class OnboardingPromptOption(Hashable): The prompt option's emoji. title: :class:`str` The prompt option's title. - description: :class:`str` + description: Optional[:class:`str`] The prompt option's description. roles_ids: FrozenSet[:class:`int`] The IDs of the roles that will be added to the user when they select this option. @@ -162,7 +162,7 @@ def __init__(self, *, guild: Guild, data: OnboardingPromptOptionPayload): # I'm not sure if this also happens for OnboardingPrompt self.guild = guild - self.id = 0 + self.id = int(data["id"]) self.title = data["title"] self.description = data["description"] self.roles_ids = ( From 7a1e2b2ac07e6abd6fc7de5412647958e5fa1c7f Mon Sep 17 00:00:00 2001 From: shiftinv Date: Sat, 1 Apr 2023 23:17:08 +0200 Subject: [PATCH 44/99] chore: rename `roles_ids`/`channels_ids` --- disnake/onboarding.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index eaf7a60d5d..17cacac614 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -146,31 +146,26 @@ class OnboardingPromptOption(Hashable): The prompt option's title. description: Optional[:class:`str`] The prompt option's description. - roles_ids: FrozenSet[:class:`int`] + role_ids: FrozenSet[:class:`int`] The IDs of the roles that will be added to the user when they select this option. - channels_ids: FrozenSet[:class:`int`] + channel_ids: FrozenSet[:class:`int`] The IDs of the channels that will be shown to the user when they select this option. """ - __slots__ = ("id", "title", "description", "emoji", "guild", "roles_ids", "channels_ids") + __slots__ = ("id", "title", "description", "emoji", "guild", "role_ids", "channel_ids") def __init__(self, *, guild: Guild, data: OnboardingPromptOptionPayload): - # NOTE: The ID may sometimes be a UNIX timestamp since - # Onboarding changes are saved locally until you send the API request (that's how it works in client) - # so the API needs the timestamp to know what ID it needs to create, should we just add a note about it - # or "try" to create the ID ourselves? - # I'm not sure if this also happens for OnboardingPrompt self.guild = guild self.id = int(data["id"]) self.title = data["title"] self.description = data["description"] - self.roles_ids = ( - frozenset(map(int, roles_ids)) if (roles_ids := data.get("role_ids")) else frozenset() + self.role_ids = ( + frozenset(map(int, role_ids)) if (role_ids := data.get("role_ids")) else frozenset() ) - self.channels_ids = ( - frozenset(map(int, channels_ids)) - if (channels_ids := data.get("channel_ids")) + self.channel_ids = ( + frozenset(map(int, channel_ids)) + if (channel_ids := data.get("channel_ids")) else frozenset() ) @@ -191,9 +186,9 @@ def __repr__(self) -> str: @property def roles(self) -> List[Role]: """List[:class:`Role`]: A list of roles that will be added to the user when they select this option.""" - return list(filter(None, map(self.guild.get_role, self.roles_ids))) + return list(filter(None, map(self.guild.get_role, self.role_ids))) @property def channels(self) -> List[GuildChannel]: """List[:class:`abc.GuildChannel`]: A list of channels that the user will see when they select this option.""" - return list(filter(None, map(self.guild.get_channel, self.channels_ids))) + return list(filter(None, map(self.guild.get_channel, self.channel_ids))) From c2ce2cc76acec06a265cfdc38e811315aeceb856 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 23:09:52 -0300 Subject: [PATCH 45/99] docs: move stuff to Discord Model --- docs/api.rst | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 8670ca238e..7847fd1081 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -5735,6 +5735,22 @@ Onboarding .. autoclass:: Onboarding :members: +OnboardingPrompt +~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPrompt + +.. autoclass:: OnboardingPrompt + :members: + +OnboardingPromptOption +~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPromptOption + +.. autoclass:: OnboardingPromptOption + :members: + RawMessageDeleteEvent ~~~~~~~~~~~~~~~~~~~~~~~ @@ -6059,22 +6075,6 @@ ApplicationRoleConnectionMetadata .. autoclass:: ApplicationRoleConnectionMetadata :members: -OnboardingPrompt -~~~~~~~~~~~~~~~~ - -.. attributetable:: OnboardingPrompt - -.. autoclass:: OnboardingPrompt - :members: - -OnboardingPromptOption -~~~~~~~~~~~~~~~~~~~~~~ - -.. attributetable:: OnboardingPromptOption - -.. autoclass:: OnboardingPromptOption - :members: - File ~~~~~ From d9d0c00772c29d379e72a40b9711581a36588b8b Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 23:12:47 -0300 Subject: [PATCH 46/99] typing: add type annotations --- disnake/onboarding.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index eaf7a60d5d..17b283340f 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -1,12 +1,13 @@ # SPDX-License-Identifier: MIT from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, FrozenSet, List, Optional, Union from .enums import OnboardingPromptType, try_enum from .mixins import Hashable if TYPE_CHECKING: + from .emoji import Emoji, PartialEmoji from .guild import Guild, GuildChannel from .role import Role from .types.onboarding import ( @@ -47,15 +48,15 @@ class Onboarding: ) def __init__(self, *, guild: Guild, data: OnboardingPayload): - self.guild = guild + self.guild: Guild = guild self._from_data(data) def _from_data(self, data: OnboardingPayload): - self.prompts = [ + self.prompts: List[OnboardingPrompt] = [ OnboardingPrompt(data=prompt, guild=self.guild) for prompt in data["prompts"] ] - self.enabled = data["enabled"] - self.default_channel_ids = ( + self.enabled: bool = data["enabled"] + self.default_channel_ids: FrozenSet[int] = ( frozenset(map(int, exempt_channels)) if (exempt_channels := data["default_channel_ids"]) else frozenset() @@ -110,15 +111,15 @@ class OnboardingPrompt(Hashable): def __init__(self, *, guild: Guild, data: OnboardingPromptPayload): self.guild = guild - self.id = int(data["id"]) - self.title = data["title"] - self.options = [ + self.id: int = int(data["id"]) + self.title: str = data["title"] + self.options: List[OnboardingPromptOption] = [ OnboardingPromptOption(data=option, guild=guild) for option in data["options"] ] - self.single_select = data["single_select"] - self.required = data["required"] - self.in_onboarding = data["in_onboarding"] - self.type = try_enum(OnboardingPromptType, data["type"]) + 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 __str__(self) -> str: return self.title @@ -160,20 +161,21 @@ def __init__(self, *, guild: Guild, data: OnboardingPromptOptionPayload): # so the API needs the timestamp to know what ID it needs to create, should we just add a note about it # or "try" to create the ID ourselves? # I'm not sure if this also happens for OnboardingPrompt - self.guild = guild + self.guild: Guild = guild - self.id = int(data["id"]) - self.title = data["title"] - self.description = data["description"] - self.roles_ids = ( + self.id: int = int(data["id"]) + self.title: str = data["title"] + self.description: Optional[str] = data["description"] + self.roles_ids: FrozenSet[int] = ( frozenset(map(int, roles_ids)) if (roles_ids := data.get("role_ids")) else frozenset() ) - self.channels_ids = ( + self.channels_ids: FrozenSet[int] = ( frozenset(map(int, channels_ids)) if (channels_ids := data.get("channel_ids")) else frozenset() ) + self.emoji: Optional[Union[Emoji, PartialEmoji]] if emoji_data := data.get("emoji"): self.emoji = guild._state.get_reaction_emoji(emoji_data) else: From 90b746c51d5eb72cb964055be2d95f3550e22b8a Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 23:29:52 -0300 Subject: [PATCH 47/99] tests: fix text --- tests/test_onboarding.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 2d71365f7c..077c8fb7a3 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -6,12 +6,10 @@ from disnake import ( Guild, - Object, Onboarding, OnboardingPrompt, OnboardingPromptOption, OnboardingPromptType, - PartialEmoji, ) from disnake.types import onboarding as onboarding_types @@ -28,12 +26,15 @@ @pytest.fixture def onboarding_prompt_option() -> OnboardingPromptOption: return OnboardingPromptOption( - title="test", - description="test", - emoji=PartialEmoji(name="", id=123, animated=False), - roles=[Object(id="456"), Object(id="789")], - channels=[Object(id="123"), Object(id="456")], guild=mock.Mock(Guild, id=123), + data=onboarding_types.OnboardingPromptOption( + id="0", + title="test", + description="test", + emoji={"name": "", "id": 123, "animated": False}, + role_ids=["456", "789"], + channel_ids=["123", "456"], + ), ) @@ -71,7 +72,7 @@ class TestOnboarding: def test_onboarding(self, onboarding: Onboarding) -> None: assert onboarding.guild.id == 123 assert onboarding.prompts == [] - assert onboarding._default_channel_ids == [456, 789] + assert onboarding.default_channel_ids == frozenset([456, 789]) assert onboarding.enabled is True @@ -94,9 +95,6 @@ def test_onboarding_prompt_option( ) -> None: assert onboarding_prompt_option.title == "test" assert onboarding_prompt_option.description == "test" - assert onboarding_prompt_option.emoji == PartialEmoji(name="", id=123, animated=False) - assert onboarding_prompt_option.roles == [Object(id="456"), Object(id="789")] - assert onboarding_prompt_option.channels == [Object(id="123"), Object(id="456")] def test_onboarding_prompt_option_str( self, onboarding_prompt_option: OnboardingPromptOption From b70ba05d7f0da2c356252357c35ff8ba68507564 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 1 Apr 2023 23:42:15 -0300 Subject: [PATCH 48/99] chore: run codemod --- disnake/onboarding.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index e0bbcc7038..31bbc2674f 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -47,11 +47,11 @@ class Onboarding: "default_channel_ids", ) - def __init__(self, *, guild: Guild, data: OnboardingPayload): + def __init__(self, *, guild: Guild, data: OnboardingPayload) -> None: self.guild: Guild = guild self._from_data(data) - def _from_data(self, data: OnboardingPayload): + def _from_data(self, data: OnboardingPayload) -> None: self.prompts: List[OnboardingPrompt] = [ OnboardingPrompt(data=prompt, guild=self.guild) for prompt in data["prompts"] ] @@ -108,7 +108,7 @@ class OnboardingPrompt(Hashable): "type", ) - def __init__(self, *, guild: Guild, data: OnboardingPromptPayload): + def __init__(self, *, guild: Guild, data: OnboardingPromptPayload) -> None: self.guild = guild self.id: int = int(data["id"]) @@ -155,7 +155,7 @@ class OnboardingPromptOption(Hashable): __slots__ = ("id", "title", "description", "emoji", "guild", "role_ids", "channel_ids") - def __init__(self, *, guild: Guild, data: OnboardingPromptOptionPayload): + def __init__(self, *, guild: Guild, data: OnboardingPromptOptionPayload) -> None: # NOTE: The ID may sometimes be a UNIX timestamp since # Onboarding changes are saved locally until you send the API request (that's how it works in client) # so the API needs the timestamp to know what ID it needs to create, should we just add a note about it From adc4b0171890027dda2dd78fd7986e32b01fbdcb Mon Sep 17 00:00:00 2001 From: Victor <67214928+Victorsitou@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:32:36 -0400 Subject: [PATCH 49/99] Apply suggestions from code review Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: Victor <67214928+Victorsitou@users.noreply.github.com> --- disnake/onboarding.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 31bbc2674f..343bab2a2d 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -141,7 +141,7 @@ class OnboardingPromptOption(Hashable): ---------- id: :class:`int` The prompt option's ID. - emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`]] + emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] The prompt option's emoji. title: :class:`str` The prompt option's title. @@ -156,11 +156,6 @@ class OnboardingPromptOption(Hashable): __slots__ = ("id", "title", "description", "emoji", "guild", "role_ids", "channel_ids") def __init__(self, *, guild: Guild, data: OnboardingPromptOptionPayload) -> None: - # NOTE: The ID may sometimes be a UNIX timestamp since - # Onboarding changes are saved locally until you send the API request (that's how it works in client) - # so the API needs the timestamp to know what ID it needs to create, should we just add a note about it - # or "try" to create the ID ourselves? - # I'm not sure if this also happens for OnboardingPrompt self.guild: Guild = guild self.id: int = int(data["id"]) @@ -175,7 +170,7 @@ def __init__(self, *, guild: Guild, data: OnboardingPromptOptionPayload) -> None else frozenset() ) - self.emoji: Optional[Union[Emoji, PartialEmoji]] + self.emoji: Optional[Union[Emoji, PartialEmoji, str]] if emoji_data := data.get("emoji"): self.emoji = guild._state.get_reaction_emoji(emoji_data) else: From 32b870cc6bf9870a36a577a3e847bf60f88f81e7 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 8 Apr 2023 15:38:23 -0400 Subject: [PATCH 50/99] docs: update docs --- docs/api/guilds.rst | 323 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 docs/api/guilds.rst diff --git a/docs/api/guilds.rst b/docs/api/guilds.rst new file mode 100644 index 0000000000..675e75d3cb --- /dev/null +++ b/docs/api/guilds.rst @@ -0,0 +1,323 @@ +.. SPDX-License-Identifier: MIT + +.. currentmodule:: disnake + +Guilds +====== + +This section documents everything related to +:ddocs:`Guilds ` - main hubs for communication in Discord, +referred to as Servers in the official UI. + +Discord Models +--------------- + +Guild +~~~~~ + +.. attributetable:: Guild + +.. autoclass:: Guild() + :members: + :exclude-members: fetch_members, audit_logs + + .. automethod:: fetch_members + :async-for: + + .. automethod:: audit_logs + :async-for: + +GuildPreview +~~~~~~~~~~~~ + +.. attributetable:: GuildPreview + +.. autoclass:: GuildPreview() + :members: + +Template +~~~~~~~~ + +.. attributetable:: Template + +.. autoclass:: Template() + :members: + +WelcomeScreen +~~~~~~~~~~~~~ + +.. attributetable:: WelcomeScreen + +.. autoclass:: WelcomeScreen() + :members: + +BanEntry +~~~~~~~~ + +.. class:: BanEntry + + A namedtuple which represents a ban returned from :meth:`~Guild.bans`. + + .. attribute:: reason + + The reason this user was banned. + + :type: Optional[:class:`str`] + .. attribute:: user + + The :class:`User` that was banned. + + :type: :class:`User` + +Onboarding +~~~~~~~~~~ + +.. attributetable:: Onboarding + +.. autoclass:: Onboarding + :members: + +OnboardingPrompt +~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPrompt + +.. autoclass:: OnboardingPrompt + :members: + +OnboardingPromptOption +~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: OnboardingPromptOption + +.. autoclass:: OnboardingPromptOption + :members: + +Data Classes +------------ + +SystemChannelFlags +~~~~~~~~~~~~~~~~~~ + +.. attributetable:: SystemChannelFlags + +.. autoclass:: SystemChannelFlags() + :members: + +WelcomeScreenChannel +~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: WelcomeScreenChannel + +.. autoclass:: WelcomeScreenChannel() + +GuildBuilder +~~~~~~~~~~~~~ + +.. attributetable:: GuildBuilder + +.. autoclass:: GuildBuilder() + :members: + :exclude-members: add_category_channel + +Enumerations +------------ + +VerificationLevel +~~~~~~~~~~~~~~~~~ + +.. class:: VerificationLevel + + Specifies a :class:`Guild`\'s verification level, which is the criteria in + which a member must meet before being able to send messages to the guild. + + .. container:: operations + + .. versionadded:: 2.0 + + .. describe:: x == y + + Checks if two verification levels are equal. + .. describe:: x != y + + Checks if two verification levels are not equal. + .. describe:: x > y + + Checks if a verification level is higher than another. + .. describe:: x < y + + Checks if a verification level is lower than another. + .. describe:: x >= y + + Checks if a verification level is higher or equal to another. + .. describe:: x <= y + + Checks if a verification level is lower or equal to another. + + .. attribute:: none + + No criteria set. + .. attribute:: low + + Member must have a verified email on their Discord account. + .. attribute:: medium + + Member must have a verified email and be registered on Discord for more + than five minutes. + .. attribute:: high + + Member must have a verified email, be registered on Discord for more + than five minutes, and be a member of the guild itself for more than + ten minutes. + .. attribute:: highest + + Member must have a verified phone on their Discord account. + +NotificationLevel +~~~~~~~~~~~~~~~~~ + +.. class:: NotificationLevel + + Specifies whether a :class:`Guild` has notifications on for all messages or mentions only by default. + + .. container:: operations + + .. versionadded:: 2.0 + + .. describe:: x == y + + Checks if two notification levels are equal. + .. describe:: x != y + + Checks if two notification levels are not equal. + .. describe:: x > y + + Checks if a notification level is higher than another. + .. describe:: x < y + + Checks if a notification level is lower than another. + .. describe:: x >= y + + Checks if a notification level is higher or equal to another. + .. describe:: x <= y + + Checks if a notification level is lower or equal to another. + + .. attribute:: all_messages + + Members receive notifications for every message regardless of them being mentioned. + .. attribute:: only_mentions + + Members receive notifications for messages they are mentioned in. + +ContentFilter +~~~~~~~~~~~~~ + +.. class:: ContentFilter + + Specifies a :class:`Guild`\'s explicit content filter, which is the machine + learning algorithms that Discord uses to detect if an image contains + NSFW content. + + .. container:: operations + + .. versionadded:: 2.0 + + .. describe:: x == y + + Checks if two content filter levels are equal. + .. describe:: x != y + + Checks if two content filter levels are not equal. + .. describe:: x > y + + Checks if a content filter level is higher than another. + .. describe:: x < y + + Checks if a content filter level is lower than another. + .. describe:: x >= y + + Checks if a content filter level is higher or equal to another. + .. describe:: x <= y + + Checks if a content filter level is lower or equal to another. + + .. attribute:: disabled + + The guild does not have the content filter enabled. + .. attribute:: no_role + + The guild has the content filter enabled for members without a role. + .. attribute:: all_members + + The guild has the content filter enabled for every member. + +NSFWLevel +~~~~~~~~~ + +.. class:: NSFWLevel + + Represents the NSFW level of a guild. + + .. versionadded:: 2.0 + + .. container:: operations + + .. describe:: x == y + + Checks if two NSFW levels are equal. + .. describe:: x != y + + Checks if two NSFW levels are not equal. + .. describe:: x > y + + Checks if a NSFW level is higher than another. + .. describe:: x < y + + Checks if a NSFW level is lower than another. + .. describe:: x >= y + + Checks if a NSFW level is higher or equal to another. + .. describe:: x <= y + + Checks if a NSFW level is lower or equal to another. + + .. attribute:: default + + The guild has not been categorised yet. + + .. attribute:: explicit + + The guild contains NSFW content. + + .. attribute:: safe + + The guild does not contain any NSFW content. + + .. attribute:: age_restricted + + The guild may contain NSFW content. + +.. class:: OnboardingPromptType + + Represents the type of onboarding prompt. + + .. versionadded:: 2.9 + + .. attribute:: multiple_choice + + The prompt is a multiple choice prompt. + + .. attribute:: dropdown + + The prompt is a dropdown prompt. + + +Events +------ + +- :func:`on_guild_join(guild) ` +- :func:`on_guild_remove(guild) ` +- :func:`on_guild_update(before, after) ` +- :func:`on_guild_available(guild) ` +- :func:`on_guild_unavailable(guild) ` From 59cf67b71bda60f384e3a1fe970992ea4da7e580 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 8 Apr 2023 15:43:55 -0400 Subject: [PATCH 51/99] docs: match docs of the same stuff --- disnake/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 343bab2a2d..93c027baf9 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -37,7 +37,7 @@ class Onboarding: enabled: :class:`bool` Whether onboarding is enabled. default_channel_ids: FrozenSet[:class:`int`] - The IDs of the channels that will automatically be shown to new members. + The list of channels that will be shown to new members by default. """ __slots__ = ( From 39790ac59b043136b1686c6442cdbc88a71ad6b6 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 8 Apr 2023 16:11:01 -0400 Subject: [PATCH 52/99] docs: update docs --- disnake/onboarding.py | 4 ++-- docs/api/guilds.rst | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 93c027baf9..069e0fb7fa 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -37,7 +37,7 @@ class Onboarding: enabled: :class:`bool` Whether onboarding is enabled. default_channel_ids: FrozenSet[:class:`int`] - The list of channels that will be shown to new members by default. + The IDs of the channels that will be shown to new members by default. """ __slots__ = ( @@ -150,7 +150,7 @@ class OnboardingPromptOption(Hashable): role_ids: FrozenSet[:class:`int`] The IDs of the roles that will be added to the user when they select this option. channel_ids: FrozenSet[:class:`int`] - The IDs of the channels that will be shown to the user when they select this option. + The IDs of the channels that the user will see when they select this option. """ __slots__ = ("id", "title", "description", "emoji", "guild", "role_ids", "channel_ids") diff --git a/docs/api/guilds.rst b/docs/api/guilds.rst index 675e75d3cb..85f8d3d494 100644 --- a/docs/api/guilds.rst +++ b/docs/api/guilds.rst @@ -74,7 +74,7 @@ Onboarding .. attributetable:: Onboarding -.. autoclass:: Onboarding +.. autoclass:: Onboarding() :members: OnboardingPrompt @@ -82,7 +82,7 @@ OnboardingPrompt .. attributetable:: OnboardingPrompt -.. autoclass:: OnboardingPrompt +.. autoclass:: OnboardingPrompt() :members: OnboardingPromptOption @@ -90,7 +90,7 @@ OnboardingPromptOption .. attributetable:: OnboardingPromptOption -.. autoclass:: OnboardingPromptOption +.. autoclass:: OnboardingPromptOption() :members: Data Classes @@ -298,6 +298,10 @@ NSFWLevel The guild may contain NSFW content. + +OnboardingPromptType +~~~~~~~~~~~~~~~~~~~~ + .. class:: OnboardingPromptType Represents the type of onboarding prompt. From a99c8287ecea61bd1befc72eba6355dcb559c525 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Wed, 19 Apr 2023 19:01:01 -0400 Subject: [PATCH 53/99] feat(Onboarding): add edit method --- disnake/enums.py | 6 ++ disnake/guild.py | 37 +++++++ disnake/http.py | 23 ++++ disnake/onboarding.py | 210 +++++++++++++++++++++++------------- disnake/types/onboarding.py | 9 ++ 5 files changed, 208 insertions(+), 77 deletions(-) diff --git a/disnake/enums.py b/disnake/enums.py index 561e31608a..b7ef1198aa 100644 --- a/disnake/enums.py +++ b/disnake/enums.py @@ -67,6 +67,7 @@ "Event", "ApplicationRoleConnectionMetadataType", "OnboardingPromptType", + "OnboardingMode", ) @@ -1301,6 +1302,11 @@ class OnboardingPromptType(Enum): dropdown = 1 +class OnboardingMode(Enum): + onboarding_default = 0 + onboarding_advanced = 1 + + T = TypeVar("T") diff --git a/disnake/guild.py b/disnake/guild.py index 5533331859..e0dc4181a9 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -10,6 +10,7 @@ Any, ClassVar, Dict, + Iterable, List, Literal, NamedTuple, @@ -50,6 +51,7 @@ Locale, NotificationLevel, NSFWLevel, + OnboardingMode, ThreadSortOrder, VerificationLevel, VideoQualityMode, @@ -91,6 +93,7 @@ from .app_commands import APIApplicationCommand from .asset import AssetBytes from .automod import AutoModAction, AutoModTriggerMetadata + from .onboarding import OnboardingPrompt from .permissions import Permissions from .state import ConnectionState from .template import Template @@ -4653,6 +4656,40 @@ async def fetch_onboarding(self) -> Onboarding: data = await self._state.http.get_guild_onboarding(self.id) return Onboarding(data=data, guild=self) + async def edit_guild_onboarding( + self, + prompts: List[OnboardingPrompt], + default_channels: Iterable[Snowflake], + enabled: bool, + mode: OnboardingMode, + reason: Optional[str] = None, + ) -> Onboarding: + """|coro| + + Edits the guild onboarding. + + .. versionadded:: 2.9 + + Raises + ------ + HTTPException + Editing the guild onboarding failed. + + Returns + ------- + :class:`Onboarding` + The newly edited guild onboarding. + """ + data = await self._state.http.edit_guild_onboarding( + self.id, + prompts=[p.to_dict() for p in prompts], + default_channel_ids=[c.id for c in default_channels], + enabled=enabled, + mode=mode.value, + reason=reason, + ) + return Onboarding(data=data, guild=self) + PlaceholderID = NewType("PlaceholderID", int) diff --git a/disnake/http.py b/disnake/http.py index e52fecc71a..f8c1612270 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -2264,6 +2264,29 @@ def delete_auto_moderation_rule( def get_guild_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]: return self.request(Route("GET", "/guilds/{guild_id}/onboarding", guild_id=guild_id)) + def edit_guild_onboarding( + self, + guild_id: Snowflake, + *, + prompts: List[onboarding.OnboardingPrompt], + default_channel_ids: SnowflakeList, + enabled: bool, + mode: onboarding.OnboardingMode, + reason: Optional[str] = None, # TODO: test this + ) -> Response[onboarding.Onboarding]: + payload: onboarding.EditOnboarding = { + "prompts": prompts, + "default_channel_ids": default_channel_ids, + "enabled": enabled, + "mode": mode, + } + + return self.request( + Route("PUT", "/guilds/{guild_id}/onboarding", guild_id=guild_id), + json=payload, + reason=reason, + ) + # Application commands (global) def get_global_commands( diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 069e0fb7fa..c0784cfd7c 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -1,15 +1,18 @@ # SPDX-License-Identifier: MIT from __future__ import annotations -from typing import TYPE_CHECKING, FrozenSet, List, Optional, Union +from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Union -from .enums import OnboardingPromptType, try_enum +from .emoji import Emoji, PartialEmoji +from .enums import OnboardingMode, OnboardingPromptType, try_enum from .mixins import Hashable +from .object import Object if TYPE_CHECKING: - from .emoji import Emoji, PartialEmoji + from typing_extensions import Self + + from .abc import Snowflake from .guild import Guild, GuildChannel - from .role import Role from .types.onboarding import ( Onboarding as OnboardingPayload, OnboardingPrompt as OnboardingPromptPayload, @@ -38,14 +41,11 @@ class Onboarding: Whether onboarding is enabled. default_channel_ids: FrozenSet[:class:`int`] The IDs of the channels that will be shown to new members by default. + mode: :class:`OnboardingMode` + Current mode of the onboarding. """ - __slots__ = ( - "guild", - "prompts", - "enabled", - "default_channel_ids", - ) + __slots__ = ("guild", "prompts", "enabled", "default_channel_ids", "mode") def __init__(self, *, guild: Guild, data: OnboardingPayload) -> None: self.guild: Guild = guild @@ -53,7 +53,7 @@ def __init__(self, *, guild: Guild, data: OnboardingPayload) -> None: def _from_data(self, data: OnboardingPayload) -> None: self.prompts: List[OnboardingPrompt] = [ - OnboardingPrompt(data=prompt, guild=self.guild) for prompt in data["prompts"] + OnboardingPrompt._from_dict(data=prompt, guild=self.guild) for prompt in data["prompts"] ] self.enabled: bool = data["enabled"] self.default_channel_ids: FrozenSet[int] = ( @@ -61,6 +61,7 @@ def _from_data(self, data: OnboardingPayload) -> None: if (exempt_channels := data["default_channel_ids"]) else frozenset() ) + self.mode: OnboardingMode = try_enum(OnboardingMode, data["mode"]) def __repr__(self) -> str: return ( @@ -78,16 +79,14 @@ class OnboardingPrompt(Hashable): .. versionadded:: 2.9 - Attributes + Parameters ---------- - id: :class:`int` - The onboarding prompt's ID. - type: :class:`OnboardingPromptType` - The onboarding prompt's type. - options: List[:class:`OnboardingPromptOption`] - The onboarding prompt's options. title: :class:`str` The onboarding prompt's title. + options: List[:class:`OnboardingPromptOption`] + The onboarding prompt's options. + type: :class:`OnboardingPromptType` + The onboarding prompt's type. single_select: :class:`bool` Whether users are limited to selecting one option for the prompt. required: :class:`bool` @@ -97,29 +96,25 @@ class OnboardingPrompt(Hashable): If ``False``, the prompt will only appear in community customization. """ - __slots__ = ( - "guild", - "id", - "title", - "options", - "single_select", - "required", - "in_onboarding", - "type", - ) - - def __init__(self, *, guild: Guild, data: OnboardingPromptPayload) -> None: - self.guild = guild - - self.id: int = int(data["id"]) - self.title: str = data["title"] - self.options: List[OnboardingPromptOption] = [ - OnboardingPromptOption(data=option, guild=guild) for option 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"]) + __slots__ = ("title", "options", "single_select", "required", "in_onboarding", "type", "_id") + + def __init__( + self, + *, + title: str, + options: List[OnboardingPromptOption], + type: OnboardingPromptType, + single_select: bool, + required: bool, + in_onboarding: bool, + ) -> None: + self._id: int = 0 + self.title: str = title + self.options: List[OnboardingPromptOption] = options + self.single_select: bool = single_select + self.required: bool = required + self.in_onboarding: bool = in_onboarding + self.type: OnboardingPromptType = type def __str__(self) -> str: return self.title @@ -131,50 +126,79 @@ def __repr__(self) -> str: f" in_onboarding={self.in_onboarding!r} type={self.type!r}>" ) + @property + def id(self) -> int: + """:class:`int`: The onboarding prompt's ID.""" + return self._id + + @classmethod + def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: + self = cls( + title=data["title"], + options=[ + OnboardingPromptOption._from_dict(data=option, guild=guild) + for option in data["options"] + ], + single_select=data["single_select"], + required=data["required"], + in_onboarding=data["in_onboarding"], + type=try_enum(OnboardingPromptType, data["type"]), + ) + if "id" in data: + self._id = int(data["id"]) + return self + + def to_dict(self) -> OnboardingPromptPayload: + return { + "id": self.id, + "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, + "type": self.type.value, + } + class OnboardingPromptOption(Hashable): """Represents an onboarding prompt option. .. versionadded:: 2.9 - Attributes + Parameters ---------- - id: :class:`int` - The prompt option's ID. - emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] - The prompt option's emoji. title: :class:`str` The prompt option's title. description: Optional[:class:`str`] The prompt option's description. - role_ids: FrozenSet[:class:`int`] + emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] + The prompt option's emoji. + roles: FrozenSet[:class:`int`] The IDs of the roles that will be added to the user when they select this option. - channel_ids: FrozenSet[:class:`int`] + channels: FrozenSet[:class:`int`] The IDs of the channels that the user will see when they select this option. """ - __slots__ = ("id", "title", "description", "emoji", "guild", "role_ids", "channel_ids") - - def __init__(self, *, guild: Guild, data: OnboardingPromptOptionPayload) -> None: - self.guild: Guild = guild - - self.id: int = int(data["id"]) - self.title: str = data["title"] - self.description: Optional[str] = data["description"] - self.role_ids: FrozenSet[int] = ( - frozenset(map(int, roles_ids)) if (roles_ids := data.get("role_ids")) else frozenset() - ) - self.channel_ids: FrozenSet[int] = ( - frozenset(map(int, channels_ids)) - if (channels_ids := data.get("channel_ids")) - else frozenset() - ) - - self.emoji: Optional[Union[Emoji, PartialEmoji, str]] - if emoji_data := data.get("emoji"): - self.emoji = guild._state.get_reaction_emoji(emoji_data) - else: - self.emoji = None + __slots__ = ("title", "description", "emoji", "guild", "roles", "channels", "_id") + + def __init__( + self, + *, + title: str, + description: Optional[str], + emoji: Optional[Union[str, PartialEmoji, Emoji]] = None, + roles: Optional[Iterable[Snowflake]] = None, + channels: Optional[Iterable[Snowflake]] = None, + ) -> None: + if roles is None and channels is None: + raise TypeError("Either roles or channels must be provided.") + + self._id: int = 0 + self.title: str = title + self.description: Optional[str] = description + self.roles: FrozenSet[Snowflake] = frozenset(roles) if roles else frozenset() + self.channels: FrozenSet[Snowflake] = frozenset(channels) if channels else frozenset() + self.emoji: Optional[Union[Emoji, PartialEmoji, str]] = emoji def __str__(self) -> str: return self.title @@ -186,11 +210,43 @@ def __repr__(self) -> str: ) @property - def roles(self) -> List[Role]: - """List[:class:`Role`]: A list of roles that will be added to the user when they select this option.""" - return list(filter(None, map(self.guild.get_role, self.role_ids))) + def id(self) -> int: + """:class:`int`: The prompt option's ID.""" + return self._id - @property - def channels(self) -> List[GuildChannel]: - """List[:class:`abc.GuildChannel`]: A list of channels that the user will see when they select this option.""" - return list(filter(None, map(self.guild.get_channel, self.channel_ids))) + @classmethod + def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: + if emoji_data := data.get("emoji"): + emoji = guild._state.get_reaction_emoji(emoji_data) + else: + emoji = None + + self = cls( + title=data["title"], + description=data.get("description"), + emoji=emoji, + roles=[Object(id=role_id) for role_id in data["role_ids"]], + channels=[Object(id=channel_id) for channel_id in data["channel_ids"]], + ) + if "id" in data: + self._id = int(data["id"]) + return self + + def to_dict(self) -> OnboardingPromptOptionPayload: + emoji = {} + + if isinstance(self.emoji, (Emoji, PartialEmoji)): + emoji["id"] = self.emoji.id + emoji["name"] = self.emoji.name + emoji["animated"] = self.emoji.animated + elif isinstance(self.emoji, str): + emoji["name"] = self.emoji + + return { + "id": self.id, + "title": self.title, + "description": self.description, + "emoji": emoji, + "role_ids": [role.id for role in self.roles], + "channel_ids": [channel.id for channel in self.channels], + } diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index 79d1bb86a2..a6473c29a9 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -6,6 +6,7 @@ from .snowflake import Snowflake, SnowflakeList OnboardingPromptType = Literal[0, 1] +OnboardingMode = Literal[0, 1] class OnboardingPromptOption(TypedDict): @@ -32,3 +33,11 @@ class Onboarding(TypedDict): prompts: List[OnboardingPrompt] default_channel_ids: SnowflakeList enabled: bool + mode: OnboardingMode + + +class EditOnboarding(TypedDict): + prompts: List[OnboardingPrompt] + default_channel_ids: SnowflakeList + enabled: bool + mode: OnboardingMode From d0cbc5d721d8c20b06a32312e1a4f31022827a33 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Wed, 19 Apr 2023 19:31:38 -0400 Subject: [PATCH 54/99] chore(lint): fix --- disnake/onboarding.py | 3 ++- tests/test_onboarding.py | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index c0784cfd7c..2195028d4f 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -13,6 +13,7 @@ from .abc import Snowflake from .guild import Guild, GuildChannel + from .types.emoji import Emoji as EmojiPayload from .types.onboarding import ( Onboarding as OnboardingPayload, OnboardingPrompt as OnboardingPromptPayload, @@ -233,7 +234,7 @@ def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Sel return self def to_dict(self) -> OnboardingPromptOptionPayload: - emoji = {} + emoji: EmojiPayload = {} # type: ignore if isinstance(self.emoji, (Emoji, PartialEmoji)): emoji["id"] = self.emoji.id diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 077c8fb7a3..eaa2fdda6c 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -25,7 +25,7 @@ @pytest.fixture def onboarding_prompt_option() -> OnboardingPromptOption: - return OnboardingPromptOption( + return OnboardingPromptOption._from_dict( guild=mock.Mock(Guild, id=123), data=onboarding_types.OnboardingPromptOption( id="0", @@ -50,7 +50,9 @@ def onboarding_prompt() -> OnboardingPrompt: "type": OnboardingPromptType.multiple_choice.value, } - return OnboardingPrompt(data=onboarding_prompt_payload, guild=mock.Mock(Guild, id=123)) + return OnboardingPrompt._from_dict( + data=onboarding_prompt_payload, guild=mock.Mock(Guild, id=123) + ) @pytest.fixture @@ -60,6 +62,7 @@ def onboarding() -> Onboarding: "prompts": [], "default_channel_ids": ["456", "789"], "enabled": True, + "mode": 0, } return Onboarding( From 14e76d43107c5be109ba63cbdb13e5e44bfd8601 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Wed, 19 Apr 2023 19:34:50 -0400 Subject: [PATCH 55/99] docs: add docs --- disnake/guild.py | 13 +++++++++++++ disnake/http.py | 2 +- docs/api/guilds.rst | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/disnake/guild.py b/disnake/guild.py index e0dc4181a9..8663477647 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4670,6 +4670,19 @@ async def edit_guild_onboarding( .. versionadded:: 2.9 + Parameters + ---------- + prompts: List[:class:`OnboardingPrompt`] + The onboarding's new prompts. + default_channels: Iterable[:class:`abc.Snowflake`] + The onboarding's new default channels. + enabled: :class:`bool` + Whether the onboarding is enabled. + mode: :class:`OnboardingMode` + The onboarding's new mode. + reason: Optional[:class:`str`] + The reason for editing the guild onboarding. Shows up on the audit log. + Raises ------ HTTPException diff --git a/disnake/http.py b/disnake/http.py index f8c1612270..96febad252 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -2272,7 +2272,7 @@ def edit_guild_onboarding( default_channel_ids: SnowflakeList, enabled: bool, mode: onboarding.OnboardingMode, - reason: Optional[str] = None, # TODO: test this + reason: Optional[str] = None, ) -> Response[onboarding.Onboarding]: payload: onboarding.EditOnboarding = { "prompts": prompts, diff --git a/docs/api/guilds.rst b/docs/api/guilds.rst index 85f8d3d494..19b4811817 100644 --- a/docs/api/guilds.rst +++ b/docs/api/guilds.rst @@ -316,6 +316,23 @@ OnboardingPromptType The prompt is a dropdown prompt. +OnboardingMode +~~~~~~~~~~~~~~ + +.. class:: OnboardingMode + + Represents the mode of onboarding. + + .. versionadded:: 2.9 + + .. attribute:: onboarding_default + + Counts only Default Channels towards constraints. + + .. attribute:: onboarding_advanced + + Counts Default Channels and Questions towards constraints. + Events ------ From f695832106948c19392693fe3792169ee4cf59e2 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:01:01 -0400 Subject: [PATCH 56/99] docs: document mode --- disnake/guild.py | 3 ++- docs/api/guilds.rst | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index 8663477647..8a4c4318bf 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4679,7 +4679,8 @@ async def edit_guild_onboarding( enabled: :class:`bool` Whether the onboarding is enabled. mode: :class:`OnboardingMode` - The onboarding's new mode. + The onboarding's new mode. This defines the criteria used to satisfy Onboarding + constraints that are required for enabling it. reason: Optional[:class:`str`] The reason for editing the guild onboarding. Shows up on the audit log. diff --git a/docs/api/guilds.rst b/docs/api/guilds.rst index 19b4811817..58b4c11871 100644 --- a/docs/api/guilds.rst +++ b/docs/api/guilds.rst @@ -321,7 +321,7 @@ OnboardingMode .. class:: OnboardingMode - Represents the mode of onboarding. + Represents the criteria used to satisfy onboarding constraints that are required for enabling it. .. versionadded:: 2.9 From 63c08f0a56cee9359136872b354d6a17a13c25bf Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 22 Apr 2023 12:18:15 -0400 Subject: [PATCH 57/99] feat!: rename `edit_guild_onboarding` --- disnake/guild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/guild.py b/disnake/guild.py index 1a27b3e84f..c03749a413 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4656,7 +4656,7 @@ async def onboarding(self) -> Onboarding: data = await self._state.http.get_guild_onboarding(self.id) return Onboarding(data=data, guild=self) - async def edit_guild_onboarding( + async def edit_onboarding( self, prompts: List[OnboardingPrompt], default_channels: Iterable[Snowflake], From 9169cc3862fbb153a9fd7a8cba8a31da80fa2a36 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 22 Apr 2023 12:19:35 -0400 Subject: [PATCH 58/99] revert: fix 928 changelog --- changelog/928.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/928.feature.rst b/changelog/928.feature.rst index 62b363d49a..41365022e0 100644 --- a/changelog/928.feature.rst +++ b/changelog/928.feature.rst @@ -1,3 +1,3 @@ Implement Onboarding. - Add :class:`Onboarding`, :class:`OnboardingPrompt` and :class:`OnboardingPromptOption`. - - Add :meth:`Guild.fetch_onboarding`. + - Add :meth:`Guild.onboarding`. From ba9a1b877f14ff96c546d3027c1fa4bbd0c87b80 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 22 Apr 2023 12:41:17 -0400 Subject: [PATCH 59/99] docs: add changelog entry --- changelog/1011.feature.rst | 6 ++++++ changelog/928.feature.rst | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelog/1011.feature.rst diff --git a/changelog/1011.feature.rst b/changelog/1011.feature.rst new file mode 100644 index 0000000000..3c35190ae1 --- /dev/null +++ b/changelog/1011.feature.rst @@ -0,0 +1,6 @@ +Implement Onboarding. + - Add :class:`Onboarding`, :class:`OnboardingPrompt` and :class:`OnboardingPromptOption`. + - Add :meth:`Guild.onboarding` and :meth:`Guild.edit_onboarding`. + - Add new enums. + - :class:`OnboardingPromptType` + - :class:`OnboardingMode` diff --git a/changelog/928.feature.rst b/changelog/928.feature.rst index 41365022e0..3c35190ae1 100644 --- a/changelog/928.feature.rst +++ b/changelog/928.feature.rst @@ -1,3 +1,6 @@ Implement Onboarding. - Add :class:`Onboarding`, :class:`OnboardingPrompt` and :class:`OnboardingPromptOption`. - - Add :meth:`Guild.onboarding`. + - Add :meth:`Guild.onboarding` and :meth:`Guild.edit_onboarding`. + - Add new enums. + - :class:`OnboardingPromptType` + - :class:`OnboardingMode` From ee0a969be5b775f425b1444d65baf7caafa4743b Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:36:42 -0400 Subject: [PATCH 60/99] feat: make it kwargs --- disnake/guild.py | 1 + 1 file changed, 1 insertion(+) diff --git a/disnake/guild.py b/disnake/guild.py index c03749a413..35293dde0f 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4658,6 +4658,7 @@ async def onboarding(self) -> Onboarding: async def edit_onboarding( self, + *, prompts: List[OnboardingPrompt], default_channels: Iterable[Snowflake], enabled: bool, From 06589f05ed9324d7e1c50fbbb399399f82843797 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:39:28 -0400 Subject: [PATCH 61/99] docs: update docs --- disnake/onboarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 2195028d4f..8a6334617f 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -174,9 +174,9 @@ class OnboardingPromptOption(Hashable): The prompt option's description. emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] The prompt option's emoji. - roles: FrozenSet[:class:`int`] + roles: Optional[Iterable[:class:`abc.Snowflake`]] The IDs of the roles that will be added to the user when they select this option. - channels: FrozenSet[:class:`int`] + channels: Optional[Iterable[:class:`abc.Snowflake`]] The IDs of the channels that the user will see when they select this option. """ From 15be7cb82f3761cc90194f7de3c5c007089bcd7f Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 25 Apr 2023 17:58:47 -0400 Subject: [PATCH 62/99] feat: edit onboarding parameters are optional --- disnake/guild.py | 28 ++++++++++++++++++++-------- disnake/http.py | 13 +++---------- disnake/types/onboarding.py | 2 +- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index 35293dde0f..436cfa1afd 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -108,6 +108,7 @@ MFALevel, ) from .types.integration import IntegrationType + from .types.onboarding import EditOnboarding as EditOnboardingPayload from .types.role import CreateRole as CreateRolePayload from .types.sticker import CreateGuildSticker as CreateStickerPayload from .types.threads import Thread as ThreadPayload, ThreadArchiveDurationLiteral @@ -4659,10 +4660,10 @@ async def onboarding(self) -> Onboarding: async def edit_onboarding( self, *, - prompts: List[OnboardingPrompt], - default_channels: Iterable[Snowflake], - enabled: bool, - mode: OnboardingMode, + prompts: List[OnboardingPrompt] = MISSING, + default_channels: Iterable[Snowflake] = MISSING, + enabled: bool = MISSING, + mode: OnboardingMode = MISSING, reason: Optional[str] = None, ) -> Onboarding: """|coro| @@ -4695,12 +4696,23 @@ async def edit_onboarding( :class:`Onboarding` The newly edited guild onboarding. """ + edit_payload: EditOnboardingPayload = {} + + if prompts is not MISSING: + edit_payload["prompts"] = [p.to_dict() for p in prompts] + + if default_channels is not MISSING: + edit_payload["default_channel_ids"] = [c.id for c in default_channels] + + if enabled is not MISSING: + edit_payload["enabled"] = enabled + + if mode is not MISSING: + edit_payload["mode"] = mode.value + data = await self._state.http.edit_guild_onboarding( self.id, - prompts=[p.to_dict() for p in prompts], - default_channel_ids=[c.id for c in default_channels], - enabled=enabled, - mode=mode.value, + **edit_payload, reason=reason, ) return Onboarding(data=data, guild=self) diff --git a/disnake/http.py b/disnake/http.py index 96febad252..eef31245e3 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -2268,18 +2268,11 @@ def edit_guild_onboarding( self, guild_id: Snowflake, *, - prompts: List[onboarding.OnboardingPrompt], - default_channel_ids: SnowflakeList, - enabled: bool, - mode: onboarding.OnboardingMode, reason: Optional[str] = None, + **fields: Any, ) -> Response[onboarding.Onboarding]: - payload: onboarding.EditOnboarding = { - "prompts": prompts, - "default_channel_ids": default_channel_ids, - "enabled": enabled, - "mode": mode, - } + valid_keys = ("prompts", "default_channels_ids", "enabled", "mode") + payload = {k: v for k, v in fields.items() if k in valid_keys} return self.request( Route("PUT", "/guilds/{guild_id}/onboarding", guild_id=guild_id), diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index a6473c29a9..e419a12848 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -36,7 +36,7 @@ class Onboarding(TypedDict): mode: OnboardingMode -class EditOnboarding(TypedDict): +class EditOnboarding(TypedDict, total=False): prompts: List[OnboardingPrompt] default_channel_ids: SnowflakeList enabled: bool From b1edff065e7cc3dd6f2b736d6bfeb65c539aa3a1 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 25 Apr 2023 17:59:22 -0400 Subject: [PATCH 63/99] revert: `_id` property --- disnake/onboarding.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 8a6334617f..a06a2b4071 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -97,7 +97,7 @@ class OnboardingPrompt(Hashable): If ``False``, the prompt will only appear in community customization. """ - __slots__ = ("title", "options", "single_select", "required", "in_onboarding", "type", "_id") + __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding", "type") def __init__( self, @@ -109,7 +109,7 @@ def __init__( required: bool, in_onboarding: bool, ) -> None: - self._id: int = 0 + self.id: int = 0 self.title: str = title self.options: List[OnboardingPromptOption] = options self.single_select: bool = single_select @@ -127,11 +127,6 @@ def __repr__(self) -> str: f" in_onboarding={self.in_onboarding!r} type={self.type!r}>" ) - @property - def id(self) -> int: - """:class:`int`: The onboarding prompt's ID.""" - return self._id - @classmethod def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: self = cls( @@ -146,7 +141,7 @@ def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: type=try_enum(OnboardingPromptType, data["type"]), ) if "id" in data: - self._id = int(data["id"]) + self.id = int(data["id"]) return self def to_dict(self) -> OnboardingPromptPayload: From b8b3b4c4bfece92193510a102de0afe67b7310f6 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 25 Apr 2023 18:03:41 -0400 Subject: [PATCH 64/99] docs: use Attributes instead of Parameters --- disnake/onboarding.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index a06a2b4071..65943046ea 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -80,7 +80,7 @@ class OnboardingPrompt(Hashable): .. versionadded:: 2.9 - Parameters + Attributes ---------- title: :class:`str` The onboarding prompt's title. @@ -161,7 +161,7 @@ class OnboardingPromptOption(Hashable): .. versionadded:: 2.9 - Parameters + Attributes ---------- title: :class:`str` The prompt option's title. @@ -169,9 +169,9 @@ class OnboardingPromptOption(Hashable): The prompt option's description. emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] The prompt option's emoji. - roles: Optional[Iterable[:class:`abc.Snowflake`]] + roles: Optional[FrozenSet[:class:`abc.Snowflake`]] The IDs of the roles that will be added to the user when they select this option. - channels: Optional[Iterable[:class:`abc.Snowflake`]] + channels: Optional[FrozenSet[:class:`abc.Snowflake`]] The IDs of the channels that the user will see when they select this option. """ From ac1e322ddef2759887caa279b395351e34da0f32 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 25 Apr 2023 18:04:59 -0400 Subject: [PATCH 65/99] feat(OnboardinMode): `onboarding_x` to `x` --- disnake/enums.py | 4 ++-- docs/api/guilds.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/disnake/enums.py b/disnake/enums.py index b7ef1198aa..bfafde917a 100644 --- a/disnake/enums.py +++ b/disnake/enums.py @@ -1303,8 +1303,8 @@ class OnboardingPromptType(Enum): class OnboardingMode(Enum): - onboarding_default = 0 - onboarding_advanced = 1 + default = 0 + advanced = 1 T = TypeVar("T") diff --git a/docs/api/guilds.rst b/docs/api/guilds.rst index 58b4c11871..eee75cceab 100644 --- a/docs/api/guilds.rst +++ b/docs/api/guilds.rst @@ -325,11 +325,11 @@ OnboardingMode .. versionadded:: 2.9 - .. attribute:: onboarding_default + .. attribute:: default Counts only Default Channels towards constraints. - .. attribute:: onboarding_advanced + .. attribute:: advanced Counts Default Channels and Questions towards constraints. From c547dccb26ef02141045a93f930336a493e0fe3b Mon Sep 17 00:00:00 2001 From: Victor <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 25 Apr 2023 18:07:27 -0400 Subject: [PATCH 66/99] feat: use `PartialEmoji.to_dict` instead Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: Victor <67214928+Victorsitou@users.noreply.github.com> --- disnake/onboarding.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 65943046ea..412c28262a 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -232,9 +232,7 @@ def to_dict(self) -> OnboardingPromptOptionPayload: emoji: EmojiPayload = {} # type: ignore if isinstance(self.emoji, (Emoji, PartialEmoji)): - emoji["id"] = self.emoji.id - emoji["name"] = self.emoji.name - emoji["animated"] = self.emoji.animated + emoji = self.emoji.to_dict() elif isinstance(self.emoji, str): emoji["name"] = self.emoji From 07cfc32df106f16f7b5171f2978386c581ae3cd2 Mon Sep 17 00:00:00 2001 From: Victor <67214928+Victorsitou@users.noreply.github.com> Date: Tue, 25 Apr 2023 18:07:46 -0400 Subject: [PATCH 67/99] docs: update mode docs Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: Victor <67214928+Victorsitou@users.noreply.github.com> --- disnake/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 412c28262a..7ba4125c98 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -43,7 +43,7 @@ class Onboarding: default_channel_ids: FrozenSet[:class:`int`] The IDs of the channels that will be shown to new members by default. mode: :class:`OnboardingMode` - Current mode of the onboarding. + The onboarding mode, defining criteria for enabling onboarding. """ __slots__ = ("guild", "prompts", "enabled", "default_channel_ids", "mode") From d1b46aad4b38287220440969799b331fa8091c2d Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 29 Apr 2023 12:04:26 -0400 Subject: [PATCH 68/99] revert: use `id` instead --- disnake/onboarding.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 7ba4125c98..21e1d66c66 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -175,7 +175,7 @@ class OnboardingPromptOption(Hashable): The IDs of the channels that the user will see when they select this option. """ - __slots__ = ("title", "description", "emoji", "guild", "roles", "channels", "_id") + __slots__ = ("id", "title", "description", "emoji", "guild", "roles", "channels") def __init__( self, @@ -189,7 +189,7 @@ def __init__( if roles is None and channels is None: raise TypeError("Either roles or channels must be provided.") - self._id: int = 0 + self.id: int = 0 self.title: str = title self.description: Optional[str] = description self.roles: FrozenSet[Snowflake] = frozenset(roles) if roles else frozenset() @@ -205,11 +205,6 @@ def __repr__(self) -> str: f"description={self.description!r} emoji={self.emoji!r}>" ) - @property - def id(self) -> int: - """:class:`int`: The prompt option's ID.""" - return self._id - @classmethod def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: if emoji_data := data.get("emoji"): @@ -225,7 +220,7 @@ def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Sel channels=[Object(id=channel_id) for channel_id in data["channel_ids"]], ) if "id" in data: - self._id = int(data["id"]) + self.id = int(data["id"]) return self def to_dict(self) -> OnboardingPromptOptionPayload: From e44f3806383c89a7387a6d1fe5b03739ebdb7119 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 29 Apr 2023 12:15:48 -0400 Subject: [PATCH 69/99] fix: `self.emoji.to_dict()` raising an error is emoji is `Emoji` --- disnake/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 21e1d66c66..14f5030501 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -227,7 +227,7 @@ def to_dict(self) -> OnboardingPromptOptionPayload: emoji: EmojiPayload = {} # type: ignore if isinstance(self.emoji, (Emoji, PartialEmoji)): - emoji = self.emoji.to_dict() + emoji = self.emoji._to_partial().to_dict() elif isinstance(self.emoji, str): emoji["name"] = self.emoji From ed04464a79d39500f8a8d4748b9d65f90156d7bd Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Mon, 12 Jun 2023 15:06:42 -0400 Subject: [PATCH 70/99] docs: add required permissions --- disnake/guild.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/disnake/guild.py b/disnake/guild.py index 436cfa1afd..b2be62df87 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4670,6 +4670,9 @@ async def edit_onboarding( Edits the guild onboarding. + You must have :attr:`.Permissions.manage_guild` and :attr:`.Permissions.manage_roles` + permissions to do this. + .. versionadded:: 2.9 Parameters From 8c436cc79bd6b5dacc802c2745be878793a356bb Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:22:01 -0400 Subject: [PATCH 71/99] chore: it's 2.10 now --- disnake/guild.py | 2 +- docs/api/guilds.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index 0f438ca3a9..d63f86b03b 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -4751,7 +4751,7 @@ async def edit_onboarding( You must have :attr:`.Permissions.manage_guild` and :attr:`.Permissions.manage_roles` permissions to do this. - .. versionadded:: 2.9 + .. versionadded:: 2.10 Parameters ---------- diff --git a/docs/api/guilds.rst b/docs/api/guilds.rst index eee75cceab..d14d65a7de 100644 --- a/docs/api/guilds.rst +++ b/docs/api/guilds.rst @@ -323,7 +323,7 @@ OnboardingMode Represents the criteria used to satisfy onboarding constraints that are required for enabling it. - .. versionadded:: 2.9 + .. versionadded:: 2.10 .. attribute:: default From dab534815ff270a464936e860f9892a8c1d311f1 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 14 Jul 2023 11:11:23 -0400 Subject: [PATCH 72/99] fix: description isn't required --- disnake/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 14f5030501..c459873c7b 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -181,7 +181,7 @@ def __init__( self, *, title: str, - description: Optional[str], + description: Optional[str] = None, emoji: Optional[Union[str, PartialEmoji, Emoji]] = None, roles: Optional[Iterable[Snowflake]] = None, channels: Optional[Iterable[Snowflake]] = None, From aa05df37afb84bf61d0cb7fda776f86c234999b4 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 15 Jul 2023 08:28:15 -0400 Subject: [PATCH 73/99] docs: resolve documentation reviews --- disnake/onboarding.py | 6 ++++++ docs/api/guilds.rst | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index c459873c7b..d45d71183b 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -82,6 +82,9 @@ class OnboardingPrompt(Hashable): Attributes ---------- + id: :class:`int` + The onboarding prompt's ID. Note that if this onboarding prompt was manually constructed, + this will be ``0``. title: :class:`str` The onboarding prompt's title. options: List[:class:`OnboardingPromptOption`] @@ -163,6 +166,9 @@ class OnboardingPromptOption(Hashable): Attributes ---------- + id: :class:`int` + The onboarding prompt option's ID. Note that if this onboarding prompt option was manually constructed, + this will be ``0``. title: :class:`str` The prompt option's title. description: Optional[:class:`str`] diff --git a/docs/api/guilds.rst b/docs/api/guilds.rst index d14d65a7de..2978aba823 100644 --- a/docs/api/guilds.rst +++ b/docs/api/guilds.rst @@ -327,11 +327,11 @@ OnboardingMode .. attribute:: default - Counts only Default Channels towards constraints. + Counts only :attr:`Onboarding.default_channels` towards constraints. .. attribute:: advanced - Counts Default Channels and Questions towards constraints. + Counts :attr:`Onboarding.default_channels` and :attr:`~Onboarding.prompts` towards constraints. Events From 8e1534009b6d854743d668f812fe7b68fa761523 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 15 Jul 2023 08:36:16 -0400 Subject: [PATCH 74/99] refactor: `id` is always available --- disnake/onboarding.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index d45d71183b..bd3f4fb983 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -143,8 +143,7 @@ def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: in_onboarding=data["in_onboarding"], type=try_enum(OnboardingPromptType, data["type"]), ) - if "id" in data: - self.id = int(data["id"]) + self.id = int(data["id"]) return self def to_dict(self) -> OnboardingPromptPayload: @@ -225,8 +224,7 @@ def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Sel roles=[Object(id=role_id) for role_id in data["role_ids"]], channels=[Object(id=channel_id) for channel_id in data["channel_ids"]], ) - if "id" in data: - self.id = int(data["id"]) + self.id = int(data["id"]) return self def to_dict(self) -> OnboardingPromptOptionPayload: From 8087ef57f0b5a436103b9199f0f779495412dad6 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 15 Jul 2023 08:45:35 -0400 Subject: [PATCH 75/99] fix: remove local check for `roles` and `channels` --- disnake/onboarding.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index bd3f4fb983..03e07bce98 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -176,8 +176,10 @@ class OnboardingPromptOption(Hashable): The prompt option's emoji. roles: Optional[FrozenSet[:class:`abc.Snowflake`]] The IDs of the roles that will be added to the user when they select this option. + At creation, this must be set if :attr:`.channels` is not set. channels: Optional[FrozenSet[:class:`abc.Snowflake`]] The IDs of the channels that the user will see when they select this option. + At creation, this must be set if :attr:`.roles` is not set. """ __slots__ = ("id", "title", "description", "emoji", "guild", "roles", "channels") @@ -191,9 +193,6 @@ def __init__( roles: Optional[Iterable[Snowflake]] = None, channels: Optional[Iterable[Snowflake]] = None, ) -> None: - if roles is None and channels is None: - raise TypeError("Either roles or channels must be provided.") - self.id: int = 0 self.title: str = title self.description: Optional[str] = description From 414e35fa7bddb42432e4a24dfe536e81286f64e3 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 15 Jul 2023 08:46:16 -0400 Subject: [PATCH 76/99] fix: don't use deprecated name --- disnake/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 03e07bce98..b5c26d354d 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -212,7 +212,7 @@ def __repr__(self) -> str: @classmethod def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: if emoji_data := data.get("emoji"): - emoji = guild._state.get_reaction_emoji(emoji_data) + emoji = guild._state._get_emoji_from_data(emoji_data) else: emoji = None From 59b49d69aea32314e84acf9b9584ab82b4d7f948 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 15 Jul 2023 08:55:24 -0400 Subject: [PATCH 77/99] refactor: use state instead of guild --- disnake/onboarding.py | 12 +++++++----- tests/test_onboarding.py | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index b5c26d354d..f386e190eb 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -13,6 +13,7 @@ from .abc import Snowflake from .guild import Guild, GuildChannel + from .state import ConnectionState from .types.emoji import Emoji as EmojiPayload from .types.onboarding import ( Onboarding as OnboardingPayload, @@ -54,7 +55,8 @@ def __init__(self, *, guild: Guild, data: OnboardingPayload) -> None: def _from_data(self, data: OnboardingPayload) -> None: self.prompts: List[OnboardingPrompt] = [ - OnboardingPrompt._from_dict(data=prompt, guild=self.guild) for prompt in data["prompts"] + OnboardingPrompt._from_dict(data=prompt, state=self.guild._state) + for prompt in data["prompts"] ] self.enabled: bool = data["enabled"] self.default_channel_ids: FrozenSet[int] = ( @@ -131,11 +133,11 @@ def __repr__(self) -> str: ) @classmethod - def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: + def _from_dict(cls, *, data: OnboardingPromptPayload, state: ConnectionState) -> Self: self = cls( title=data["title"], options=[ - OnboardingPromptOption._from_dict(data=option, guild=guild) + OnboardingPromptOption._from_dict(data=option, state=state) for option in data["options"] ], single_select=data["single_select"], @@ -210,9 +212,9 @@ def __repr__(self) -> str: ) @classmethod - def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: + def _from_dict(cls, *, data: OnboardingPromptOptionPayload, state: ConnectionState) -> Self: if emoji_data := data.get("emoji"): - emoji = guild._state._get_emoji_from_data(emoji_data) + emoji = state._get_emoji_from_data(emoji_data) else: emoji = None diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index eaa2fdda6c..014147133f 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -11,6 +11,7 @@ OnboardingPromptOption, OnboardingPromptType, ) +from disnake.state import ConnectionState from disnake.types import onboarding as onboarding_types onboarding_prompt_option_payload: onboarding_types.OnboardingPromptOption = { @@ -26,7 +27,7 @@ @pytest.fixture def onboarding_prompt_option() -> OnboardingPromptOption: return OnboardingPromptOption._from_dict( - guild=mock.Mock(Guild, id=123), + state=mock.Mock(ConnectionState, id=123), data=onboarding_types.OnboardingPromptOption( id="0", title="test", @@ -51,7 +52,7 @@ def onboarding_prompt() -> OnboardingPrompt: } return OnboardingPrompt._from_dict( - data=onboarding_prompt_payload, guild=mock.Mock(Guild, id=123) + data=onboarding_prompt_payload, state=mock.Mock(ConnectionState, id=123) ) From b188bd515dd8b67cd6e58830c7690a8ae38d2e44 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 15 Jul 2023 09:43:46 -0400 Subject: [PATCH 78/99] types(onboarding): use PartialEmoji instead --- disnake/types/onboarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index e419a12848..eee2749448 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -2,7 +2,7 @@ from typing import List, Literal, Optional, TypedDict -from .emoji import Emoji +from .emoji import PartialEmoji from .snowflake import Snowflake, SnowflakeList OnboardingPromptType = Literal[0, 1] @@ -13,7 +13,7 @@ class OnboardingPromptOption(TypedDict): id: Snowflake title: str description: Optional[str] - emoji: Emoji + emoji: PartialEmoji role_ids: SnowflakeList channel_ids: SnowflakeList From ac89b66bcd9a70ec16eb17d3512eb6520aaa08dc Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Sat, 15 Jul 2023 09:50:46 -0400 Subject: [PATCH 79/99] changelog: remove old and edit new --- changelog/1011.feature.rst | 10 ++++------ changelog/928.feature.rst | 6 ------ 2 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 changelog/928.feature.rst diff --git a/changelog/1011.feature.rst b/changelog/1011.feature.rst index 3c35190ae1..c023525d9b 100644 --- a/changelog/1011.feature.rst +++ b/changelog/1011.feature.rst @@ -1,6 +1,4 @@ -Implement Onboarding. - - Add :class:`Onboarding`, :class:`OnboardingPrompt` and :class:`OnboardingPromptOption`. - - Add :meth:`Guild.onboarding` and :meth:`Guild.edit_onboarding`. - - Add new enums. - - :class:`OnboardingPromptType` - - :class:`OnboardingMode` +Allow editing Onboarding. + - :class:`OnboardingPrompt` and :class:`OnboardingPromptOption` are now user-constructable. + - Add :meth:`Guild.edit_onboarding`. + - Add :class:`OnboardingMode` enum. diff --git a/changelog/928.feature.rst b/changelog/928.feature.rst deleted file mode 100644 index 3c35190ae1..0000000000 --- a/changelog/928.feature.rst +++ /dev/null @@ -1,6 +0,0 @@ -Implement Onboarding. - - Add :class:`Onboarding`, :class:`OnboardingPrompt` and :class:`OnboardingPromptOption`. - - Add :meth:`Guild.onboarding` and :meth:`Guild.edit_onboarding`. - - Add new enums. - - :class:`OnboardingPromptType` - - :class:`OnboardingMode` From fb1852c0a938d803527acc306dcaab8a7113d0dd Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:32:12 -0400 Subject: [PATCH 80/99] docs: mention parameters are optional --- disnake/guild.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/disnake/guild.py b/disnake/guild.py index d7f0f2b89f..24fc20646c 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -3561,7 +3561,7 @@ async def create_role( Creates a :class:`Role` for the guild. - All fields are optional. + All parameters are optional. You must have :attr:`~Permissions.manage_roles` permission to do this. @@ -4761,6 +4761,8 @@ async def edit_onboarding( You must have :attr:`.Permissions.manage_guild` and :attr:`.Permissions.manage_roles` permissions to do this. + All parameters are optional. + .. versionadded:: 2.10 Parameters From 6191b36921c72a8bbba1751a7bbd0363884d37a8 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:32:41 -0400 Subject: [PATCH 81/99] fix: add overload to `OnboardingPromptOption` --- disnake/onboarding.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index f386e190eb..df1b9597ac 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT from __future__ import annotations -from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Union +from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Union, overload from .emoji import Emoji, PartialEmoji from .enums import OnboardingMode, OnboardingPromptType, try_enum @@ -186,6 +186,30 @@ class OnboardingPromptOption(Hashable): __slots__ = ("id", "title", "description", "emoji", "guild", "roles", "channels") + @overload + def __init__( + self, + *, + title: str, + description: Optional[str] = None, + emoji: Optional[Union[str, PartialEmoji, Emoji]] = None, + roles: Iterable[Snowflake], + channels: Optional[Iterable[Snowflake]] = None, + ): + ... + + @overload + def __init__( + self, + *, + title: str, + description: Optional[str] = None, + emoji: Optional[Union[str, PartialEmoji, Emoji]] = None, + roles: Optional[Iterable[Snowflake]] = None, + channels: Iterable[Snowflake], + ): + ... + def __init__( self, *, From 681747cad594c04f521964d8f57c041136eee07f Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:32:55 -0400 Subject: [PATCH 82/99] tests: state doesn't have id --- tests/test_onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 014147133f..db7663f273 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -27,7 +27,7 @@ @pytest.fixture def onboarding_prompt_option() -> OnboardingPromptOption: return OnboardingPromptOption._from_dict( - state=mock.Mock(ConnectionState, id=123), + state=mock.Mock(ConnectionState), data=onboarding_types.OnboardingPromptOption( id="0", title="test", From fc419cc4acc661f0c1343255cfd565dba9d87d26 Mon Sep 17 00:00:00 2001 From: Victor <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:34:23 -0400 Subject: [PATCH 83/99] docs: update changelog Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: Victor <67214928+Victorsitou@users.noreply.github.com> --- changelog/1011.feature.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog/1011.feature.rst b/changelog/1011.feature.rst index c023525d9b..cb9ed5505e 100644 --- a/changelog/1011.feature.rst +++ b/changelog/1011.feature.rst @@ -1,4 +1,4 @@ -Allow editing Onboarding. - - :class:`OnboardingPrompt` and :class:`OnboardingPromptOption` are now user-constructable. +Allow editing Onboarding configurations. + - :class:`OnboardingPrompt` and :class:`OnboardingPromptOption` are now user-constructible. - Add :meth:`Guild.edit_onboarding`. - - Add :class:`OnboardingMode` enum. + - Add :attr:`Onboarding.mode`. From 322efc41925298a5c36770f2e5ac8a2b8ec0dcb4 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 22 Mar 2024 12:16:39 -0300 Subject: [PATCH 84/99] feat: resolve conversations --- disnake/onboarding.py | 94 ++++++++++++++++++++++++++++--------- disnake/types/emoji.py | 4 +- disnake/types/onboarding.py | 7 ++- 3 files changed, 79 insertions(+), 26 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index df1b9597ac..f2ff724f3d 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -7,12 +7,14 @@ from .enums import OnboardingMode, OnboardingPromptType, try_enum from .mixins import Hashable from .object import Object +from .utils import _get_as_snowflake if TYPE_CHECKING: from typing_extensions import Self from .abc import Snowflake from .guild import Guild, GuildChannel + from .role import Role from .state import ConnectionState from .types.emoji import Emoji as EmojiPayload from .types.onboarding import ( @@ -55,7 +57,7 @@ def __init__(self, *, guild: Guild, data: OnboardingPayload) -> None: def _from_data(self, data: OnboardingPayload) -> None: self.prompts: List[OnboardingPrompt] = [ - OnboardingPrompt._from_dict(data=prompt, state=self.guild._state) + OnboardingPrompt._from_dict(data=prompt, state=self.guild._state, guild=self.guild) for prompt in data["prompts"] ] self.enabled: bool = data["enabled"] @@ -133,11 +135,13 @@ def __repr__(self) -> str: ) @classmethod - def _from_dict(cls, *, data: OnboardingPromptPayload, state: ConnectionState) -> Self: + def _from_dict( + cls, *, data: OnboardingPromptPayload, state: ConnectionState, guild: Guild + ) -> Self: self = cls( title=data["title"], options=[ - OnboardingPromptOption._from_dict(data=option, state=state) + OnboardingPromptOption._from_dict(data=option, state=state, guild=guild) for option in data["options"] ], single_select=data["single_select"], @@ -176,15 +180,24 @@ class OnboardingPromptOption(Hashable): The prompt option's description. emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] The prompt option's emoji. - roles: Optional[FrozenSet[:class:`abc.Snowflake`]] + roles: FrozenSet[:class:`int`] The IDs of the roles that will be added to the user when they select this option. At creation, this must be set if :attr:`.channels` is not set. - channels: Optional[FrozenSet[:class:`abc.Snowflake`]] + channels: FrozenSet[:class:`int`] The IDs of the channels that the user will see when they select this option. At creation, this must be set if :attr:`.roles` is not set. """ - __slots__ = ("id", "title", "description", "emoji", "guild", "roles", "channels") + __slots__ = ( + "id", + "title", + "description", + "emoji", + "guild", + "role_ids", + "channel_ids", + "_guild", + ) @overload def __init__( @@ -218,13 +231,17 @@ def __init__( emoji: Optional[Union[str, PartialEmoji, Emoji]] = None, roles: Optional[Iterable[Snowflake]] = None, channels: Optional[Iterable[Snowflake]] = None, + _guild: Optional[Guild] = None, ) -> None: self.id: int = 0 self.title: str = title self.description: Optional[str] = description - self.roles: FrozenSet[Snowflake] = frozenset(roles) if roles else frozenset() - self.channels: FrozenSet[Snowflake] = frozenset(channels) if channels else frozenset() + self.role_ids: FrozenSet[int] = frozenset([r.id for r in roles]) if roles else frozenset() + self.channel_ids: FrozenSet[int] = ( + frozenset([c.id for c in channels]) if channels else frozenset() + ) self.emoji: Optional[Union[Emoji, PartialEmoji, str]] = emoji + self._guild = _guild def __str__(self) -> str: return self.title @@ -236,23 +253,34 @@ def __repr__(self) -> str: ) @classmethod - def _from_dict(cls, *, data: OnboardingPromptOptionPayload, state: ConnectionState) -> Self: - if emoji_data := data.get("emoji"): - emoji = state._get_emoji_from_data(emoji_data) - else: - emoji = None + def _from_dict( + cls, *, data: OnboardingPromptOptionPayload, state: ConnectionState, guild: Guild + ) -> Self: + emoji = state._get_emoji_from_fields( + name=data.get("emoji_name"), + id=_get_as_snowflake(data, "emoji_id"), + animated=data.get("emoji_animated", None), + ) - self = cls( + self = cls( # type: ignore title=data["title"], description=data.get("description"), emoji=emoji, roles=[Object(id=role_id) for role_id in data["role_ids"]], channels=[Object(id=channel_id) for channel_id in data["channel_ids"]], + _guild=guild, ) self.id = int(data["id"]) return self def to_dict(self) -> OnboardingPromptOptionPayload: + payload: OnboardingPromptOptionPayload = { + "id": self.id, + "title": self.title, + "description": self.description, + "role_ids": list(self.role_ids), + "channel_ids": list(self.channel_ids), + } emoji: EmojiPayload = {} # type: ignore if isinstance(self.emoji, (Emoji, PartialEmoji)): @@ -260,11 +288,33 @@ def to_dict(self) -> OnboardingPromptOptionPayload: elif isinstance(self.emoji, str): emoji["name"] = self.emoji - return { - "id": self.id, - "title": self.title, - "description": self.description, - "emoji": emoji, - "role_ids": [role.id for role in self.roles], - "channel_ids": [channel.id for channel in self.channels], - } + if emoji_name := emoji.get("name"): + payload["emoji_name"] = emoji_name + if emoji_id := emoji.get("id"): + payload["emoji_id"] = emoji_id + if (emoji_animated := emoji.get("animated")) is not None: + payload["emoji_animated"] = emoji_animated + + return payload + + @property + def roles(self) -> List[Role]: + """List[:class:`Role`]: A list of roles that will be added to the user when they select this option. + + Accesing this during construction will raise an :exc:`ValueError`. + """ + if not self._guild or self.id == 0: + # TODO: better message and error? + raise ValueError("You cannot access this on construction.") + return list(filter(None, map(self._guild.get_role, self.role_ids))) + + @property + def channels(self) -> List[GuildChannel]: + """List[:class:`abc.GuildChannel`]: A list of channels that the user will see when they select this option. + + Accesing this during construction will raise an :exc:`ValueError`. + """ + if not self._guild or self.id == 0: + # TODO: better message and error? + raise ValueError("You cannot access this on construction.") + return list(filter(None, map(self._guild.get_channel, self.channel_ids))) diff --git a/disnake/types/emoji.py b/disnake/types/emoji.py index 5b8bdcf756..778335458d 100644 --- a/disnake/types/emoji.py +++ b/disnake/types/emoji.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT -from typing import Optional, TypedDict +from typing import NotRequired, Optional, TypedDict from .snowflake import Snowflake, SnowflakeList from .user import User @@ -9,6 +9,7 @@ class PartialEmoji(TypedDict): id: Optional[Snowflake] name: Optional[str] + animated: NotRequired[bool] class Emoji(PartialEmoji, total=False): @@ -16,7 +17,6 @@ class Emoji(PartialEmoji, total=False): user: User require_colons: bool managed: bool - animated: bool available: bool diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index eee2749448..31e0a97213 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT -from typing import List, Literal, Optional, TypedDict +from typing import List, Literal, NotRequired, Optional, TypedDict from .emoji import PartialEmoji from .snowflake import Snowflake, SnowflakeList @@ -13,7 +13,10 @@ class OnboardingPromptOption(TypedDict): id: Snowflake title: str description: Optional[str] - emoji: PartialEmoji + emoji: NotRequired[PartialEmoji] # deprecated + emoji_id: NotRequired[Snowflake] + emoji_name: NotRequired[str] + emoji_animated: NotRequired[bool] role_ids: SnowflakeList channel_ids: SnowflakeList From 46b15dfc51060366d6b0260a50c59f3f449e3c84 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 22 Mar 2024 12:21:46 -0300 Subject: [PATCH 85/99] fix: import `NotRequired` from extensions instead --- disnake/types/onboarding.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/disnake/types/onboarding.py b/disnake/types/onboarding.py index 31e0a97213..14c06eb661 100644 --- a/disnake/types/onboarding.py +++ b/disnake/types/onboarding.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: MIT -from typing import List, Literal, NotRequired, Optional, TypedDict +from typing import List, Literal, Optional, TypedDict + +from typing_extensions import NotRequired from .emoji import PartialEmoji from .snowflake import Snowflake, SnowflakeList From 1b10d2870fcce33aa0ec722347025ad16eb59d3a Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 22 Mar 2024 12:23:10 -0300 Subject: [PATCH 86/99] fix: import `NotRequired` from extensions instead 2 --- disnake/types/emoji.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/disnake/types/emoji.py b/disnake/types/emoji.py index 778335458d..460dddc7e7 100644 --- a/disnake/types/emoji.py +++ b/disnake/types/emoji.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: MIT -from typing import NotRequired, Optional, TypedDict +from typing import Optional, TypedDict + +from typing_extensions import NotRequired from .snowflake import Snowflake, SnowflakeList from .user import User From 63ad41e9836f2a0af29f8aa4cdf975e74a34caa6 Mon Sep 17 00:00:00 2001 From: Victor <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 22 Mar 2024 12:59:08 -0300 Subject: [PATCH 87/99] feat: add API defaults Co-authored-by: shiftinv <8530778+shiftinv@users.noreply.github.com> Signed-off-by: Victor <67214928+Victorsitou@users.noreply.github.com> --- disnake/onboarding.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index f2ff724f3d..dad1f901b3 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -112,9 +112,9 @@ def __init__( title: str, options: List[OnboardingPromptOption], type: OnboardingPromptType, - single_select: bool, - required: bool, - in_onboarding: bool, + single_select: bool = False, + required: bool = False, + in_onboarding: bool = True, ) -> None: self.id: int = 0 self.title: str = title From e35721625c10a46a23033c3ceca86a5830f4c617 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:06:19 -0300 Subject: [PATCH 88/99] docs: update versionadded --- disnake/onboarding.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index f2ff724f3d..daea4428dc 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT from __future__ import annotations +import os from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Union, overload from .emoji import Emoji, PartialEmoji @@ -33,7 +34,7 @@ class Onboarding: """Represents a guild onboarding object. - .. versionadded:: 2.9 + .. versionadded:: 2.10 Attributes ---------- @@ -82,7 +83,7 @@ def default_channels(self) -> List[GuildChannel]: class OnboardingPrompt(Hashable): """Represents an onboarding prompt. - .. versionadded:: 2.9 + .. versionadded:: 2.10 Attributes ---------- @@ -116,7 +117,7 @@ def __init__( required: bool, in_onboarding: bool, ) -> None: - self.id: int = 0 + self.id: int = int.from_bytes(os.urandom(4)) self.title: str = title self.options: List[OnboardingPromptOption] = options self.single_select: bool = single_select @@ -167,7 +168,7 @@ def to_dict(self) -> OnboardingPromptPayload: class OnboardingPromptOption(Hashable): """Represents an onboarding prompt option. - .. versionadded:: 2.9 + .. versionadded:: 2.10 Attributes ---------- From 0d1f98773e23834bf5fe021c4b917665e2aad296 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:04:27 -0300 Subject: [PATCH 89/99] fix: ups --- disnake/onboarding.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 3cf296636d..dad1f901b3 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT from __future__ import annotations -import os from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Union, overload from .emoji import Emoji, PartialEmoji @@ -34,7 +33,7 @@ class Onboarding: """Represents a guild onboarding object. - .. versionadded:: 2.10 + .. versionadded:: 2.9 Attributes ---------- @@ -83,7 +82,7 @@ def default_channels(self) -> List[GuildChannel]: class OnboardingPrompt(Hashable): """Represents an onboarding prompt. - .. versionadded:: 2.10 + .. versionadded:: 2.9 Attributes ---------- @@ -117,7 +116,7 @@ def __init__( required: bool = False, in_onboarding: bool = True, ) -> None: - self.id: int = int.from_bytes(os.urandom(4)) + self.id: int = 0 self.title: str = title self.options: List[OnboardingPromptOption] = options self.single_select: bool = single_select @@ -168,7 +167,7 @@ def to_dict(self) -> OnboardingPromptPayload: class OnboardingPromptOption(Hashable): """Represents an onboarding prompt option. - .. versionadded:: 2.10 + .. versionadded:: 2.9 Attributes ---------- From 0b5ddaecb648f2acfe194622b6bebecaef2d6fae Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Mon, 20 May 2024 18:44:14 -0400 Subject: [PATCH 90/99] refactor: remove `state` from _from_dict --- disnake/onboarding.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index dad1f901b3..adffa3e089 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -15,7 +15,6 @@ from .abc import Snowflake from .guild import Guild, GuildChannel from .role import Role - from .state import ConnectionState from .types.emoji import Emoji as EmojiPayload from .types.onboarding import ( Onboarding as OnboardingPayload, @@ -57,8 +56,7 @@ def __init__(self, *, guild: Guild, data: OnboardingPayload) -> None: def _from_data(self, data: OnboardingPayload) -> None: self.prompts: List[OnboardingPrompt] = [ - OnboardingPrompt._from_dict(data=prompt, state=self.guild._state, guild=self.guild) - for prompt in data["prompts"] + OnboardingPrompt._from_dict(data=prompt, guild=self.guild) for prompt in data["prompts"] ] self.enabled: bool = data["enabled"] self.default_channel_ids: FrozenSet[int] = ( @@ -135,13 +133,11 @@ def __repr__(self) -> str: ) @classmethod - def _from_dict( - cls, *, data: OnboardingPromptPayload, state: ConnectionState, guild: Guild - ) -> Self: + def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: self = cls( title=data["title"], options=[ - OnboardingPromptOption._from_dict(data=option, state=state, guild=guild) + OnboardingPromptOption._from_dict(data=option, guild=guild) for option in data["options"] ], single_select=data["single_select"], @@ -253,10 +249,8 @@ def __repr__(self) -> str: ) @classmethod - def _from_dict( - cls, *, data: OnboardingPromptOptionPayload, state: ConnectionState, guild: Guild - ) -> Self: - emoji = state._get_emoji_from_fields( + def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: + emoji = guild._state._get_emoji_from_fields( name=data.get("emoji_name"), id=_get_as_snowflake(data, "emoji_id"), animated=data.get("emoji_animated", None), From d594d9f796afb15b1a36a789d7a74897603eae1b Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Mon, 20 May 2024 18:46:41 -0400 Subject: [PATCH 91/99] docs: add versionchanged --- disnake/onboarding.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index adffa3e089..211167690f 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -82,6 +82,10 @@ class OnboardingPrompt(Hashable): .. versionadded:: 2.9 + .. versionchanged:: 2.10 + + This is now user-constructible. + Attributes ---------- id: :class:`int` @@ -165,6 +169,10 @@ class OnboardingPromptOption(Hashable): .. versionadded:: 2.9 + .. versionchanged:: 2.10 + + This is now user-constructible. + Attributes ---------- id: :class:`int` From 2eaeceb61d776c1bf75d9e5c3ce6556e885bfdf9 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Mon, 20 May 2024 18:48:28 -0400 Subject: [PATCH 92/99] refactor: set `OnboardingPromptOption._guild` after creation --- disnake/onboarding.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 211167690f..3cc2c605a6 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -235,7 +235,6 @@ def __init__( emoji: Optional[Union[str, PartialEmoji, Emoji]] = None, roles: Optional[Iterable[Snowflake]] = None, channels: Optional[Iterable[Snowflake]] = None, - _guild: Optional[Guild] = None, ) -> None: self.id: int = 0 self.title: str = title @@ -245,7 +244,7 @@ def __init__( frozenset([c.id for c in channels]) if channels else frozenset() ) self.emoji: Optional[Union[Emoji, PartialEmoji, str]] = emoji - self._guild = _guild + self._guild: Optional[Guild] = None def __str__(self) -> str: return self.title @@ -264,14 +263,14 @@ def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Sel animated=data.get("emoji_animated", None), ) - self = cls( # type: ignore + self = cls( title=data["title"], description=data.get("description"), emoji=emoji, roles=[Object(id=role_id) for role_id in data["role_ids"]], channels=[Object(id=channel_id) for channel_id in data["channel_ids"]], - _guild=guild, ) + self._guild = guild self.id = int(data["id"]) return self From f53d6b65049576b6cc9a254cda1d331cdb96973e Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Mon, 20 May 2024 18:50:55 -0400 Subject: [PATCH 93/99] docs: revert roles and channels --- disnake/onboarding.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 3cc2c605a6..504226f15f 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -184,12 +184,10 @@ class OnboardingPromptOption(Hashable): The prompt option's description. emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] The prompt option's emoji. - roles: FrozenSet[:class:`int`] + role_ids: FrozenSet[:class:`int`] The IDs of the roles that will be added to the user when they select this option. - At creation, this must be set if :attr:`.channels` is not set. - channels: FrozenSet[:class:`int`] + channel_ids: FrozenSet[:class:`int`] The IDs of the channels that the user will see when they select this option. - At creation, this must be set if :attr:`.roles` is not set. """ __slots__ = ( From 78a5e2bf05730a7e8716a80af1a71b9edddef491 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Mon, 20 May 2024 18:55:34 -0400 Subject: [PATCH 94/99] refactor: get emoji from `emoji` field --- disnake/onboarding.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 504226f15f..ee136c36e8 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -7,7 +7,6 @@ from .enums import OnboardingMode, OnboardingPromptType, try_enum from .mixins import Hashable from .object import Object -from .utils import _get_as_snowflake if TYPE_CHECKING: from typing_extensions import Self @@ -255,11 +254,10 @@ def __repr__(self) -> str: @classmethod def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: - emoji = guild._state._get_emoji_from_fields( - name=data.get("emoji_name"), - id=_get_as_snowflake(data, "emoji_id"), - animated=data.get("emoji_animated", None), - ) + if emoji_data := data.get("emoji"): + emoji = guild._state._get_emoji_from_data(emoji_data) + else: + emoji = None self = cls( title=data["title"], From c3a7e487b8edb79ba33148c3dab82dde6d6c9b6c Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Mon, 20 May 2024 19:22:17 -0400 Subject: [PATCH 95/99] fix: set random id to `OnboardingPrompt` --- disnake/onboarding.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index ee136c36e8..15b727cb7d 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT from __future__ import annotations +import os from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Union, overload from .emoji import Emoji, PartialEmoji @@ -153,7 +154,7 @@ def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: def to_dict(self) -> OnboardingPromptPayload: return { - "id": self.id, + "id": int.from_bytes(os.urandom(4), "big"), "title": self.title, "options": [option.to_dict() for option in self.options], "single_select": self.single_select, From cf34de06b427c12f4d364d3667da3aa7e96657fd Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Mon, 20 May 2024 19:24:50 -0400 Subject: [PATCH 96/99] test: update to current behavior --- tests/test_onboarding.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index db7663f273..eaa2fdda6c 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -11,7 +11,6 @@ OnboardingPromptOption, OnboardingPromptType, ) -from disnake.state import ConnectionState from disnake.types import onboarding as onboarding_types onboarding_prompt_option_payload: onboarding_types.OnboardingPromptOption = { @@ -27,7 +26,7 @@ @pytest.fixture def onboarding_prompt_option() -> OnboardingPromptOption: return OnboardingPromptOption._from_dict( - state=mock.Mock(ConnectionState), + guild=mock.Mock(Guild, id=123), data=onboarding_types.OnboardingPromptOption( id="0", title="test", @@ -52,7 +51,7 @@ def onboarding_prompt() -> OnboardingPrompt: } return OnboardingPrompt._from_dict( - data=onboarding_prompt_payload, state=mock.Mock(ConnectionState, id=123) + data=onboarding_prompt_payload, guild=mock.Mock(Guild, id=123) ) From 7f589babdb8de45eac686f55307eb63c64871dd3 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Mon, 20 May 2024 19:29:10 -0400 Subject: [PATCH 97/99] chore: run codemod --- disnake/onboarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 15b727cb7d..2377cd69ab 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -210,7 +210,7 @@ def __init__( emoji: Optional[Union[str, PartialEmoji, Emoji]] = None, roles: Iterable[Snowflake], channels: Optional[Iterable[Snowflake]] = None, - ): + ) -> None: ... @overload @@ -222,7 +222,7 @@ def __init__( emoji: Optional[Union[str, PartialEmoji, Emoji]] = None, roles: Optional[Iterable[Snowflake]] = None, channels: Iterable[Snowflake], - ): + ) -> None: ... def __init__( From 85e3d33167d8eb66baa19b5d90f91fde83890fe0 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Wed, 22 May 2024 18:08:27 -0400 Subject: [PATCH 98/99] feat: split classes into API model and user-constructible --- disnake/onboarding.py | 167 +++++++++++++++++++++++++----------------- docs/api/guilds.rst | 16 ++++ 2 files changed, 116 insertions(+), 67 deletions(-) diff --git a/disnake/onboarding.py b/disnake/onboarding.py index 2377cd69ab..e3ce0b771f 100644 --- a/disnake/onboarding.py +++ b/disnake/onboarding.py @@ -7,11 +7,8 @@ from .emoji import Emoji, PartialEmoji from .enums import OnboardingMode, OnboardingPromptType, try_enum from .mixins import Hashable -from .object import Object if TYPE_CHECKING: - from typing_extensions import Self - from .abc import Snowflake from .guild import Guild, GuildChannel from .role import Role @@ -25,7 +22,9 @@ __all__ = ( "Onboarding", "OnboardingPrompt", + "APIOnboardingPrompt", "OnboardingPromptOption", + "APIOnboardingPromptOption", ) @@ -38,7 +37,7 @@ class Onboarding: ---------- guild: :class:`Guild` The guild this onboarding is part of. - prompts: List[:class:`OnboardingPrompt`] + prompts: List[:class:`APIOnboardingPrompt`] The prompts shown during onboarding and in community customization. enabled: :class:`bool` Whether onboarding is enabled. @@ -55,8 +54,8 @@ def __init__(self, *, guild: Guild, data: OnboardingPayload) -> None: self._from_data(data) def _from_data(self, data: OnboardingPayload) -> None: - self.prompts: List[OnboardingPrompt] = [ - OnboardingPrompt._from_dict(data=prompt, guild=self.guild) for prompt in data["prompts"] + self.prompts: List[APIOnboardingPrompt] = [ + APIOnboardingPrompt(data=prompt, guild=self.guild) for prompt in data["prompts"] ] self.enabled: bool = data["enabled"] self.default_channel_ids: FrozenSet[int] = ( @@ -86,11 +85,8 @@ class OnboardingPrompt(Hashable): This is now user-constructible. - Attributes + Parameters ---------- - id: :class:`int` - The onboarding prompt's ID. Note that if this onboarding prompt was manually constructed, - this will be ``0``. title: :class:`str` The onboarding prompt's title. options: List[:class:`OnboardingPromptOption`] @@ -106,7 +102,7 @@ class OnboardingPrompt(Hashable): If ``False``, the prompt will only appear in community customization. """ - __slots__ = ("id", "title", "options", "single_select", "required", "in_onboarding", "type") + __slots__ = ("id", "title", "options", "type", "single_select", "required", "in_onboarding") def __init__( self, @@ -121,10 +117,10 @@ def __init__( self.id: int = 0 self.title: str = title self.options: List[OnboardingPromptOption] = options + self.type: OnboardingPromptType = type self.single_select: bool = single_select self.required: bool = required self.in_onboarding: bool = in_onboarding - self.type: OnboardingPromptType = type def __str__(self) -> str: return self.title @@ -136,22 +132,6 @@ def __repr__(self) -> str: f" in_onboarding={self.in_onboarding!r} type={self.type!r}>" ) - @classmethod - def _from_dict(cls, *, data: OnboardingPromptPayload, guild: Guild) -> Self: - self = cls( - title=data["title"], - options=[ - OnboardingPromptOption._from_dict(data=option, guild=guild) - for option in data["options"] - ], - single_select=data["single_select"], - required=data["required"], - in_onboarding=data["in_onboarding"], - type=try_enum(OnboardingPromptType, data["type"]), - ) - self.id = int(data["id"]) - return self - def to_dict(self) -> OnboardingPromptPayload: return { "id": int.from_bytes(os.urandom(4), "big"), @@ -164,6 +144,49 @@ def to_dict(self) -> OnboardingPromptPayload: } +class APIOnboardingPrompt(OnboardingPrompt): + """Represents an onboarding prompt returned by the API. + + .. versionadded:: 2.10 + + Attributes + ---------- + id: :class:`int` + The onboarding prompt's ID. + title: :class:`str` + The onboarding prompt's title. + options: List[:class:`APIOnboardingPromptOption`] + The onboarding prompt's options. + type: :class:`OnboardingPromptType` + The onboarding prompt's type. + single_select: :class:`bool` + Whether users are limited to selecting one option for the prompt. + required: :class:`bool` + Whether the prompt is required before a user completes the onboarding flow. + in_onboarding: :class:`bool` + Whether the prompt is present in the onboarding flow. + If ``False``, the prompt will only appear in community customization. + """ + + def __init__(self, *, data: OnboardingPromptPayload, guild: Guild) -> None: + self.id: int = int(data["id"]) + self.title: str = data["title"] + self.options: List[APIOnboardingPromptOption] = [ + APIOnboardingPromptOption(data=o, guild=guild) for o 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 OnboardingPromptOption(Hashable): """Represents an onboarding prompt option. @@ -173,21 +196,20 @@ class OnboardingPromptOption(Hashable): This is now user-constructible. - Attributes + Parameters ---------- - id: :class:`int` - The onboarding prompt option's ID. Note that if this onboarding prompt option was manually constructed, - this will be ``0``. title: :class:`str` The prompt option's title. description: Optional[:class:`str`] The prompt option's description. emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] The prompt option's emoji. - role_ids: FrozenSet[:class:`int`] + roles: Iterable[:class:`.abc.Snowflake`] The IDs of the roles that will be added to the user when they select this option. - channel_ids: FrozenSet[:class:`int`] + This must be set if ``channels`` is not set. + channels: Iterable[:class:`.abc.Snowflake`] The IDs of the channels that the user will see when they select this option. + This must be set if ``roles`` is not set. """ __slots__ = ( @@ -242,7 +264,6 @@ def __init__( frozenset([c.id for c in channels]) if channels else frozenset() ) self.emoji: Optional[Union[Emoji, PartialEmoji, str]] = emoji - self._guild: Optional[Guild] = None def __str__(self) -> str: return self.title @@ -253,24 +274,6 @@ def __repr__(self) -> str: f"description={self.description!r} emoji={self.emoji!r}>" ) - @classmethod - def _from_dict(cls, *, data: OnboardingPromptOptionPayload, guild: Guild) -> Self: - if emoji_data := data.get("emoji"): - emoji = guild._state._get_emoji_from_data(emoji_data) - else: - emoji = None - - self = cls( - title=data["title"], - description=data.get("description"), - emoji=emoji, - roles=[Object(id=role_id) for role_id in data["role_ids"]], - channels=[Object(id=channel_id) for channel_id in data["channel_ids"]], - ) - self._guild = guild - self.id = int(data["id"]) - return self - def to_dict(self) -> OnboardingPromptOptionPayload: payload: OnboardingPromptOptionPayload = { "id": self.id, @@ -295,24 +298,54 @@ def to_dict(self) -> OnboardingPromptOptionPayload: return payload + +class APIOnboardingPromptOption(OnboardingPromptOption): + """Represents an onboarding prompt option returned by the API. + + .. versionadded:: 2.10 + + Attributes + ---------- + id: :class:`int` + The onboarding prompt option's ID. + title: :class:`str` + The prompt option's title. + description: Optional[:class:`str`] + The prompt option's description. + emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] + The prompt option's emoji. + role_ids: FrozenSet[:class:`int`] + The IDs of the roles that will be added to the user when they select this option. + channel_ids: FrozenSet[:class:`int`] + The IDs of the channels that the user will see when they select this option. + """ + + def __init__(self, *, data: OnboardingPromptOptionPayload, guild: Guild) -> None: + self.id: int = int(data["id"]) + self.title: str = data["title"] + self.description: Optional[str] = data.get("description") + self.role_ids: FrozenSet[int] = frozenset(int(r) for r in data["role_ids"]) + self.channel_ids: FrozenSet[int] = frozenset(int(c) for c in data["channel_ids"]) + self._guild: Guild = guild + + if emoji_data := data.get("emoji"): + emoji = guild._state._get_emoji_from_data(emoji_data) + else: + emoji = None + self.emoji = emoji + + def __repr__(self) -> str: + return ( + f"" + ) + @property def roles(self) -> List[Role]: - """List[:class:`Role`]: A list of roles that will be added to the user when they select this option. - - Accesing this during construction will raise an :exc:`ValueError`. - """ - if not self._guild or self.id == 0: - # TODO: better message and error? - raise ValueError("You cannot access this on construction.") + """List[:class:`Role`]: A list of roles that will be added to the user when they select this option.""" return list(filter(None, map(self._guild.get_role, self.role_ids))) @property def channels(self) -> List[GuildChannel]: - """List[:class:`abc.GuildChannel`]: A list of channels that the user will see when they select this option. - - Accesing this during construction will raise an :exc:`ValueError`. - """ - if not self._guild or self.id == 0: - # TODO: better message and error? - raise ValueError("You cannot access this on construction.") + """List[:class:`abc.GuildChannel`]: A list of channels that the user will see when they select this option.""" return list(filter(None, map(self._guild.get_channel, self.channel_ids))) diff --git a/docs/api/guilds.rst b/docs/api/guilds.rst index cdcc283db6..4018aefd26 100644 --- a/docs/api/guilds.rst +++ b/docs/api/guilds.rst @@ -105,6 +105,14 @@ OnboardingPrompt .. autoclass:: OnboardingPrompt() :members: +APIOnboardingPromptOption +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: APIOnboardingPromptOption + +.. autoclass:: APIOnboardingPromptOption() + :members: + OnboardingPromptOption ~~~~~~~~~~~~~~~~~~~~~~ @@ -113,6 +121,14 @@ OnboardingPromptOption .. autoclass:: OnboardingPromptOption() :members: +APIOnboardingPrompt +~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: APIOnboardingPrompt + +.. autoclass:: APIOnboardingPrompt() + :members: + Data Classes ------------ From a97c36f197184fcddd4fa40de5a45b296c0d4776 Mon Sep 17 00:00:00 2001 From: Victorsitou <67214928+Victorsitou@users.noreply.github.com> Date: Wed, 22 May 2024 18:09:51 -0400 Subject: [PATCH 99/99] test: update tests to the new changes --- tests/test_onboarding.py | 128 ++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 35 deletions(-) diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index eaa2fdda6c..35b9808344 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -5,7 +5,10 @@ import pytest from disnake import ( + APIOnboardingPrompt, + APIOnboardingPromptOption, Guild, + Object, Onboarding, OnboardingPrompt, OnboardingPromptOption, @@ -13,45 +16,66 @@ ) from disnake.types import onboarding as onboarding_types -onboarding_prompt_option_payload: onboarding_types.OnboardingPromptOption = { - "id": "0", - "title": "test", - "description": "test", - "emoji": {"id": "123", "name": "", "animated": False}, - "role_ids": ["456", "789"], - "channel_ids": ["123", "456"], -} - @pytest.fixture def onboarding_prompt_option() -> OnboardingPromptOption: - return OnboardingPromptOption._from_dict( + return OnboardingPromptOption( + title="title", + description="description", + emoji="🗿", + roles=[Object(id=123)], + channels=[Object(id=456)], + ) + + +@pytest.fixture +def api_onboarding_prompt_option() -> APIOnboardingPromptOption: + return APIOnboardingPromptOption( guild=mock.Mock(Guild, id=123), data=onboarding_types.OnboardingPromptOption( id="0", - title="test", - description="test", - emoji={"name": "", "id": 123, "animated": False}, - role_ids=["456", "789"], - channel_ids=["123", "456"], + title="title", + description="description", + emoji={"name": "🗿", "id": "", "animated": False}, + role_ids=["123", "456"], + channel_ids=["789", "159"], ), ) @pytest.fixture def onboarding_prompt() -> OnboardingPrompt: - onboarding_prompt_payload: onboarding_types.OnboardingPrompt = { - "id": 0, - "title": "test", - "options": [], - "single_select": True, - "required": True, - "in_onboarding": True, - "type": OnboardingPromptType.multiple_choice.value, - } + return OnboardingPrompt( + title="title", + options=[ + OnboardingPromptOption( + title="title", + description="description", + emoji="🗿", + roles=[Object(id=123)], + channels=[Object(id=456)], + ) + ], + type=OnboardingPromptType.multiple_choice, + single_select=True, + required=True, + in_onboarding=True, + ) + - return OnboardingPrompt._from_dict( - data=onboarding_prompt_payload, guild=mock.Mock(Guild, id=123) +@pytest.fixture +def api_onboarding_prompt() -> APIOnboardingPrompt: + return APIOnboardingPrompt( + guild=mock.Mock(Guild, id=123), + data=onboarding_types.OnboardingPrompt( + id="0", + title="title", + options=[], + single_select=True, + required=True, + in_onboarding=True, + type=OnboardingPromptType.multiple_choice.value, + ), ) @@ -80,26 +104,60 @@ def test_onboarding(self, onboarding: Onboarding) -> None: class TestOnboardingPrompt: - def test_onboarding_prompt(self, onboarding_prompt: OnboardingPrompt) -> None: - assert onboarding_prompt.title == "test" - assert onboarding_prompt.options == [] + def test_onboarding_prompt( + self, onboarding_prompt: OnboardingPrompt, onboarding_prompt_option: OnboardingPromptOption + ) -> None: + assert onboarding_prompt.id == 0 + assert onboarding_prompt.title == "title" + assert onboarding_prompt.options == [onboarding_prompt_option] + assert onboarding_prompt.type == OnboardingPromptType.multiple_choice assert onboarding_prompt.single_select is True assert onboarding_prompt.required is True assert onboarding_prompt.in_onboarding is True - assert onboarding_prompt.type == OnboardingPromptType.multiple_choice def test_onboarding_prompt_str(self, onboarding_prompt: OnboardingPrompt) -> None: - assert str(onboarding_prompt) == "test" + assert str(onboarding_prompt) == "title" + + +class TestAPIOnboardingPrompt: + def test_api_onboarding_prompt(self, api_onboarding_prompt: APIOnboardingPrompt) -> None: + assert api_onboarding_prompt.title == "title" + assert api_onboarding_prompt.options == [] + assert api_onboarding_prompt.single_select is True + assert api_onboarding_prompt.required is True + assert api_onboarding_prompt.in_onboarding is True + assert api_onboarding_prompt.type == OnboardingPromptType.multiple_choice + + def test_onboarding_prompt_str(self, api_onboarding_prompt: APIOnboardingPrompt) -> None: + assert str(api_onboarding_prompt) == "title" class TestOnboardingPromptOption: def test_onboarding_prompt_option( self, onboarding_prompt_option: OnboardingPromptOption ) -> None: - assert onboarding_prompt_option.title == "test" - assert onboarding_prompt_option.description == "test" + assert onboarding_prompt_option.title == "title" + assert onboarding_prompt_option.description == "description" + assert onboarding_prompt_option.emoji == "🗿" + assert onboarding_prompt_option.role_ids == frozenset([123]) + assert onboarding_prompt_option.channel_ids == frozenset([456]) def test_onboarding_prompt_option_str( - self, onboarding_prompt_option: OnboardingPromptOption + self, api_onboarding_prompt_option: APIOnboardingPromptOption + ) -> None: + assert str(api_onboarding_prompt_option) == "title" + + +class TestAPIOnboardingPromptOption: + def test_api_onboarding_prompt_option( + self, api_onboarding_prompt_option: APIOnboardingPromptOption + ) -> None: + assert api_onboarding_prompt_option.title == "title" + assert api_onboarding_prompt_option.description == "description" + assert api_onboarding_prompt_option.role_ids == frozenset([123, 456]) + assert api_onboarding_prompt_option.channel_ids == frozenset([789, 159]) + + def test_api_onboarding_prompt_option_str( + self, api_onboarding_prompt_option: APIOnboardingPromptOption ) -> None: - assert str(onboarding_prompt_option) == "test" + assert str(api_onboarding_prompt_option) == "title"