Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature/add guild onboarding #2005

Draft
wants to merge 30 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0390aea
Added onboarding routes
syncblaze Aug 6, 2024
e2b91e9
Added onboarding rest-routes
syncblaze Aug 6, 2024
a49fc22
Added onboarding classes
syncblaze Aug 6, 2024
36fa885
Added onboarding serialization/deserialization
syncblaze Aug 6, 2024
11699bb
Add changelog
syncblaze Aug 6, 2024
7450196
Merge branch 'hikari-py:master' into feature/add-guild-onboarding
syncblaze Aug 6, 2024
9b6fb28
Switch attrs.define hash parameter to unsafe_hash
syncblaze Aug 6, 2024
b284f35
Update hikari/guilds.py
syncblaze Aug 6, 2024
1d44ea2
Update hikari/guilds.py
syncblaze Aug 6, 2024
4f57a41
Update hikari/impl/entity_factory.py
syncblaze Aug 6, 2024
09f46ac
Update hikari/impl/entity_factory.py
syncblaze Aug 6, 2024
ca0b5cc
Update hikari/api/entity_factory.py
syncblaze Aug 6, 2024
f830f5b
Update hikari/guilds.py
syncblaze Aug 6, 2024
9d52ce7
Update hikari/guilds.py
syncblaze Aug 6, 2024
be1abea
Update hikari/impl/rest.py
syncblaze Aug 6, 2024
77b4232
Update hikari/impl/entity_factory.py
syncblaze Aug 6, 2024
c845a08
Update hikari/impl/entity_factory.py
syncblaze Aug 6, 2024
53bd006
change to list comprehensions
syncblaze Aug 6, 2024
72a65d2
remove unnecessary check
syncblaze Aug 6, 2024
8b10f6c
rename deserialize_onboarding
syncblaze Aug 6, 2024
b3dd3d8
Update hikari/impl/entity_factory.py
syncblaze Aug 6, 2024
932d612
send ids as int
syncblaze Aug 6, 2024
10013f6
forgot to run nox pipeline
syncblaze Aug 6, 2024
5784ac4
Changes repr, added docstrings
syncblaze Aug 6, 2024
c016826
Adding app field for usage in get_ and fetch_ utility functions
syncblaze Aug 6, 2024
958da95
Screw utility functions
syncblaze Aug 6, 2024
bc44482
Added audit log support for onboarding.
syncblaze Aug 6, 2024
3ad44b3
Forgot to run nox pipeline
syncblaze Aug 6, 2024
f251876
change types
syncblaze Aug 17, 2024
c151882
adding tests
syncblaze Aug 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/2005.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add guild onboarding functionality
79 changes: 79 additions & 0 deletions hikari/api/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,85 @@ def deserialize_guild_widget(self, payload: data_binding.JSONObject) -> guild_mo
The deserialized guild widget object.
"""

@abc.abstractmethod
def deserialize_guild_onboarding(self, payload: data_binding.JSONObject) -> guild_models.GuildOnboarding:
syncblaze marked this conversation as resolved.
Show resolved Hide resolved
"""Parse a raw payload from Discord into a guild onboarding object.

Parameters
----------
payload
The JSON payload to deserialize.

Returns
-------
hikari.guilds.GuildOnboarding
The deserialized guild onboarding object.
"""

@abc.abstractmethod
def deserialize_onboarding_prompt(self, payload: data_binding.JSONObject) -> guild_models.OnboardingPrompt:
"""Parse a raw payload from Discord into an onboarding prompt object.

Parameters
----------
payload
The JSON payload to deserialize.

Returns
-------
hikari.guilds.OnboardingPrompt
The deserialized onboarding prompt object.
"""

@abc.abstractmethod
def deserialize_onboarding_prompt_option(
self, payload: data_binding.JSONObject
) -> guild_models.OnboardingPromptOption:
"""Parse a raw payload from Discord into an onboarding prompt option object.

Parameters
----------
payload
The JSON payload to deserialize.

Returns
-------
hikari.guilds.OnboardingPromptOption
The deserialized onboarding prompt option object.
"""

@abc.abstractmethod
def serialize_onboarding_prompt_option(
self, option: guild_models.OnboardingPromptOption
) -> data_binding.JSONObject:
"""Serialize an onboarding prompt option object to a json serializable dict.

Parameters
----------
option
The onboarding prompt option object to serialize.

Returns
-------
hikari.internal.data_binding.JSONObject
The serialized representation of the onboarding prompt option.
"""

@abc.abstractmethod
def serialize_onboarding_prompt(self, prompt: guild_models.OnboardingPrompt) -> data_binding.JSONObject:
"""Serialize an onboarding prompt object to a json serializable dict.

Parameters
----------
prompt
The onboarding prompt object to serialize.

Returns
-------
hikari.internal.data_binding.JSONObject
The serialized representation of the onboarding prompt.
"""

@abc.abstractmethod
def deserialize_welcome_screen(self, payload: data_binding.JSONObject) -> guild_models.WelcomeScreen:
"""Parse a raw payload from Discord into a guild welcome screen object.
Expand Down
83 changes: 83 additions & 0 deletions hikari/api/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6452,6 +6452,89 @@ async def edit_welcome_screen(
If an internal error occurs on Discord while handling the request.
"""

@abc.abstractmethod
async def fetch_guild_onboarding(
self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild]
) -> guilds.GuildOnboarding:
"""Fetch a guild's onboarding.

Parameters
----------
guild
Object or ID of the guild to fetch the welcome screen for.

Returns
-------
hikari.guilds.GuildOnboarding
The requested onboarding object.

Raises
------
hikari.errors.NotFoundError
If the guild is not found or the welcome screen has never been set
for this guild (if the welcome screen has been set for a guild
before and then disabled you should still be able to fetch it).
hikari.errors.UnauthorizedError
If you are unauthorized to make the request (invalid/missing token).
hikari.errors.RateLimitTooLongError
Raised in the event that a rate limit occurs that is
longer than `max_rate_limit` when making a request.
hikari.errors.InternalServerError
If an internal error occurs on Discord while handling the request.
"""

@abc.abstractmethod
async def edit_guild_onboarding(
self,
guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
*,
enabled: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
default_channels: undefined.UndefinedNoneOr[
typing.Sequence[snowflakes.SnowflakeishOr[channels_.GuildChannel]]
] = undefined.UNDEFINED,
mode: undefined.UndefinedOr[guilds.OnboardingMode] = undefined.UNDEFINED,
prompts: undefined.UndefinedNoneOr[typing.Sequence[guilds.OnboardingPrompt]] = undefined.UNDEFINED,
reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
) -> guilds.GuildOnboarding:
"""Edit the onboarding of a community guild.

Parameters
----------
guild
ID or object of the guild to edit the onboarding for.
enabled
If provided, Whether the guild's onboarding should be enabled.
default_channels
If provided, channel IDs that members get opted into automatically.
mode
If provided, the onboarding mode to set for the guild.
prompts
If provided, prompts shown during onboarding and in customize community.

Returns
-------
hikari.guilds.GuildOnboarding
The edited onboarding object.

Raises
------
hikari.errors.BadRequestError
# TODO: fix docs
hikari.errors.ForbiddenError
If you are missing the [`hikari.permissions.Permissions.MANAGE_GUILD`][] permission, are not part of
the guild or the guild doesn't have access to the community welcome
screen feature.
hikari.errors.NotFoundError
If the guild is not found.
hikari.errors.UnauthorizedError
If you are unauthorized to make the request (invalid/missing token).
hikari.errors.RateLimitTooLongError
Raised in the event that a rate limit occurs that is
longer than `max_rate_limit` when making a request.
hikari.errors.InternalServerError
If an internal error occurs on Discord while handling the request.
"""

@abc.abstractmethod
async def fetch_vanity_url(self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild]) -> invites.VanityURL:
"""Fetch a guild's vanity url.
Expand Down
91 changes: 91 additions & 0 deletions hikari/guilds.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,97 @@ class WelcomeChannel:
"""ID of the emoji shown in the welcome screen channel if it's set to a custom emoji."""


@typing.final
class OnboardingPromptType(int, enums.Enum):
"""The type of onboarding prompt."""

MULTIPLE_CHOICE = 0
"""A multiple choice prompt."""

DROPDOWN = 1
"""A dropdown prompt."""


@typing.final
class OnboardingMode(int, enums.Enum):
"""The mode of onboarding."""

ONBOARDING_DEFAULT = 0
"""Counts only Default Channels towards constraints."""

ONBOARDING_ADVANCED = 1
"""Counts Default Channels and Questions towards constraints."""


@attrs_extensions.with_copy
@attrs.define(unsafe_hash=False, kw_only=True, weakref_slot=False)
syncblaze marked this conversation as resolved.
Show resolved Hide resolved
davfsa marked this conversation as resolved.
Show resolved Hide resolved
class GuildOnboarding:
"""Used to represent guild onboarding settings on Discord."""

guild_id: snowflakes.Snowflake = attrs.field(repr=True)
"""ID of the guild this onboarding is part of."""

prompts: typing.List[OnboardingPrompt] = attrs.field(repr=True)
"""Prompts shown during onboarding and in customize community."""

default_channel_ids: typing.List[snowflakes.Snowflake] = attrs.field(repr=True)
"""Channel IDs that members get opted into automatically."""
syncblaze marked this conversation as resolved.
Show resolved Hide resolved

enabled: bool = attrs.field(repr=True)
"""Whether the guild onboarding is enabled."""

mode: OnboardingMode = attrs.field(repr=True)
"""The mode of onboarding."""


@attrs_extensions.with_copy
@attrs.define(unsafe_hash=False, kw_only=True, weakref_slot=False)
syncblaze marked this conversation as resolved.
Show resolved Hide resolved
class OnboardingPrompt:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dito above

"""Used to represent an onboarding prompt."""

id: snowflakes.Snowflake = attrs.field(repr=True)
"""The ID of the onboarding prompt."""

type: OnboardingPromptType = attrs.field(repr=True)
"""The type of the onboarding prompt."""

options: typing.List[OnboardingPromptOption] = attrs.field(repr=True)
"""Options available within the prompt."""

title: str = attrs.field(repr=True)
"""The title of the onboarding prompt."""

single_select: bool = attrs.field(repr=True)
"""Indicates whether users are limited to selecting one option for the prompt."""

required: bool = attrs.field(repr=True)
"""Indicates whether the prompt is required before a user completes the onboarding flow."""

in_onboarding: bool = attrs.field(repr=True)
"""Indicates whether the prompt is present in the onboarding flow.

If `false`, the prompt will only appear in the Channels & Roles tab.
syncblaze marked this conversation as resolved.
Show resolved Hide resolved
"""


@attrs_extensions.with_copy
@attrs.define(unsafe_hash=False, kw_only=True, weakref_slot=False)
class OnboardingPromptOption:
"""Used to represent an onboarding prompt option."""

id: snowflakes.Snowflake = attrs.field(repr=True)

channel_ids: typing.Sequence[snowflakes.Snowflake] = attrs.field(repr=True)

role_ids: typing.Sequence[snowflakes.Snowflake] = attrs.field(repr=True)

emoji: typing.Optional[emojis_.Emoji] = attrs.field(repr=True)

title: str = attrs.field(repr=True)

description: typing.Optional[str] = attrs.field(repr=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing docstrings



@attrs_extensions.with_copy
@attrs.define(kw_only=True, weakref_slot=False)
class WelcomeScreen:
Expand Down
95 changes: 95 additions & 0 deletions hikari/impl/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1776,6 +1776,101 @@ def deserialize_guild_widget(self, payload: data_binding.JSONObject) -> guild_mo

return guild_models.GuildWidget(app=self._app, channel_id=channel_id, is_enabled=payload["enabled"])

def deserialize_guild_onboarding(self, payload: data_binding.JSONObject) -> guild_models.GuildOnboarding:
default_channel_ids: typing.List[snowflakes.Snowflake] = []
for raw_channel_id in payload["default_channel_ids"]:
default_channel_ids.append(snowflakes.Snowflake(raw_channel_id))

prompts: typing.List[guild_models.OnboardingPrompt] = []
for prompt_payload in payload["prompts"]:
prompts.append(self.deserialize_onboarding_prompt(prompt_payload))

mode: guild_models.OnboardingMode = guild_models.OnboardingMode(payload["mode"])

return guild_models.GuildOnboarding(
guild_id=snowflakes.Snowflake(payload["guild_id"]),
prompts=prompts,
default_channel_ids=default_channel_ids,
mode=mode,
enabled=bool(payload.get("enabled")),
)

def deserialize_onboarding_prompt(self, payload: data_binding.JSONObject) -> guild_models.OnboardingPrompt:
options: typing.List[guild_models.OnboardingPromptOption] = []
for option_payload in payload["options"]:
options.append(self.deserialize_onboarding_prompt_option(option_payload))

return guild_models.OnboardingPrompt(
id=snowflakes.Snowflake(payload["id"]),
type=guild_models.OnboardingPromptType(payload["type"]),
options=options,
title=payload["title"],
single_select=bool(payload.get("single_select")),
required=bool(payload.get("required")),
in_onboarding=bool(payload.get("in_onboarding")),
)

def deserialize_onboarding_prompt_option(
self, payload: data_binding.JSONObject
) -> guild_models.OnboardingPromptOption:
channel_ids: typing.List[snowflakes.Snowflake] = []
for raw_channel_id in payload["channel_ids"]:
channel_ids.append(snowflakes.Snowflake(raw_channel_id))

role_ids: typing.List[snowflakes.Snowflake] = []
for raw_role_id in payload["role_ids"]:
role_ids.append(snowflakes.Snowflake(raw_role_id))
syncblaze marked this conversation as resolved.
Show resolved Hide resolved

description = payload.get("description", None)
syncblaze marked this conversation as resolved.
Show resolved Hide resolved

emoji: typing.Optional[emoji_models.Emoji] = None
if raw_emoji := payload.get("emoji"):
emoji = self.deserialize_emoji(raw_emoji)

return guild_models.OnboardingPromptOption(
id=snowflakes.Snowflake(payload["id"]),
channel_ids=channel_ids,
role_ids=role_ids,
emoji=emoji,
title=payload["title"],
description=description,
)

def serialize_onboarding_prompt(self, prompt: guild_models.OnboardingPrompt) -> data_binding.JSONObject:
payload: typing.Dict[str, typing.Any] = {
"id": str(prompt.id),
"type": prompt.type.value,
"title": prompt.title,
"options": [self.serialize_onboarding_prompt_option(option) for option in prompt.options],
"single_select": prompt.single_select,
"required": prompt.required,
"in_onboarding": prompt.in_onboarding,
}

return payload
syncblaze marked this conversation as resolved.
Show resolved Hide resolved

def serialize_onboarding_prompt_option(
self, option: guild_models.OnboardingPromptOption
) -> data_binding.JSONObject:
payload: typing.Dict[str, typing.Any] = {
"id": str(option.id),
syncblaze marked this conversation as resolved.
Show resolved Hide resolved
"channel_ids": [str(channel_id) for channel_id in option.channel_ids],
"role_ids": [str(role_id) for role_id in option.role_ids],
syncblaze marked this conversation as resolved.
Show resolved Hide resolved
"title": option.title,
}

if option.description is not None:
syncblaze marked this conversation as resolved.
Show resolved Hide resolved
payload["description"] = option.description

if isinstance(option.emoji, emoji_models.UnicodeEmoji):
payload["emoji_name"] = option.emoji.name
elif isinstance(option.emoji, emoji_models.CustomEmoji):
payload["emoji_id"] = str(option.emoji.id)
syncblaze marked this conversation as resolved.
Show resolved Hide resolved
payload["emoji_name"] = option.emoji.name
payload["emoji_animated"] = option.emoji.is_animated

return payload

def deserialize_welcome_screen(self, payload: data_binding.JSONObject) -> guild_models.WelcomeScreen:
channels: typing.List[guild_models.WelcomeChannel] = []

Expand Down
Loading
Loading