From b65986842d416ea6e703c26a4a984fb51d1d526c Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 19 Jul 2024 20:36:21 +0200 Subject: [PATCH 01/12] clans things --- discord/asset.py | 18 ++ discord/clan.py | 408 +++++++++++++++++++++++++++ discord/enums.py | 57 ++++ discord/guild.py | 95 +++++++ discord/http.py | 57 ++++ discord/member_verification.py | 313 ++++++++++++++++++++ discord/types/clan.py | 84 ++++++ discord/types/member_verification.py | 58 ++++ 8 files changed, 1090 insertions(+) create mode 100644 discord/clan.py create mode 100644 discord/member_verification.py create mode 100644 discord/types/clan.py create mode 100644 discord/types/member_verification.py diff --git a/discord/asset.py b/discord/asset.py index e3422f3110d4..84717e07e988 100644 --- a/discord/asset.py +++ b/discord/asset.py @@ -346,6 +346,24 @@ def _from_user_banner(cls, state: _State, user_id: int, banner_hash: str) -> Sel animated=animated, ) + @classmethod + def _from_clan_badge(cls, state: _State, guild_id: int, badge_hash: str) -> Self: + return cls( + state, + url=f'{cls.BASE}/clan-badges/{guild_id}/{badge_hash}.png', + key=badge_hash, + animated=False, + ) + + @classmethod + def _from_clan_banner(cls, state: _State, guild_id: int, banner_hash: str) -> Self: + return cls( + state, + url=f'{cls.BASE}/clan-banners/{guild_id}/{banner_hash}.png', + key=banner_hash, + animated=False, + ) + def __str__(self) -> str: return self._url diff --git a/discord/clan.py b/discord/clan.py new file mode 100644 index 000000000000..3790dfd2712e --- /dev/null +++ b/discord/clan.py @@ -0,0 +1,408 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, List, Optional + +from .enums import ClanBannerStyle, ClanPlayStyle, try_enum, ClanBadgeType +from .mixins import Hashable +from .state import ConnectionState +from .object import Object +from .colour import Colour +from .asset import Asset +from .member_verification import MemberVerification +from .utils import MISSING +from .abc import Snowflake + +if TYPE_CHECKING: + from typing_extensions import Self + + from .guild import Guild + + from .types.clan import ( + PartialClan as PartialClanPayload, + Clan as ClanPayload, + ClanSettings as ClanSettingsPayload, + ) + +__all__ = ('PartialClan', 'Clan') + + +class PartialClan: + """Represents a partial clan. + + Attributes + ---------- + tag: :class:`str` + The clan tag. + badge_type: Optional[:class:`ClanBadgeType`] + The clan badge type, or ``None``. + """ + + __slots__ = ( + '_state', + 'tag', + 'badge_type', + ) + + def __init__(self, *, data: PartialClanPayload, state: ConnectionState) -> None: + self._state: ConnectionState = state + self.tag: str = data['tag'] + try: + self.badge_type: Optional[ClanBadgeType] = try_enum(ClanBadgeType, data['badge']) + except KeyError: + self.badge_type = None + + +class Clan(Hashable, PartialClan): + """Represents a clan. + + Attributes + ---------- + id: :class:`int` + The guild ID. + name: :class:`str` + The guild name. + tag: :class:`str` + The clan tag. + description: :class:`str` + The clan description. + + .. note:: + + This can be different than the guild's description. + member_count: Optional[:class:`int`] + An approximate count of the total members in the guild, or ``None``. + play_style: :class:`ClanPlayStyle` + The clan play style. + badge_type: Optional[:class:`ClanBadgeType`] + The clan badge type, or ``None``. + banner_style: Optional[:class:`ClanBannerStyle`] + The clan banner type, or ``None``. + """ + + __slots__ = ( + 'id', + 'name', + 'description', + 'play_style', + 'member_count', + 'banner_style', + '_games', + '_search_terms', + '_badge_hash', + '_banner_hash', + '_badge_primary_colour', + '_badge_secondary_colour', + '_banner_primary_colour', + '_banner_secondary_colour', + '_wildcard_descriptors', + '_verification_form', + ) + + if TYPE_CHECKING: + id: int + name: str + description: str + play_style: ClanPlayStyle + member_count: Optional[int] + banner_style: Optional[ClanBannerStyle] + _games: Dict[int, Object] + _search_terms: List[str] + _badge_hash: Optional[str] + _banner_hash: Optional[str] + _badge_primary_colour: Optional[str] + _badge_secondary_colour: Optional[str] + _banner_primary_colour: Optional[str] + _banner_secondary_colour: Optional[str] + _verification_form: Optional[MemberVerification] + + def __init__(self, *, data: ClanPayload, state: ConnectionState) -> None: + super().__init__(data=data, state=state) + + self.id: int = int(data['id']) + self._update(data) + + def _update(self, data: ClanPayload) -> None: + self.name: str = data['name'] + self.description: str = data['description'] + self.member_count: Optional[int] = data.get('member_count') + self.play_style: ClanPlayStyle = try_enum(ClanPlayStyle, data.get('play_style', 0)) + + try: + self.banner_style: Optional[ClanBannerStyle] = try_enum( + ClanBannerStyle, data['banner'] + ) + except KeyError: + self.banner_style = None + + self._games: Dict[int, Object] = { + int(g): Object(int(g)) for g in data.get('game_application_ids', []) + } + self._search_terms: List[str] = data.get('search_terms', []) + self._badge_hash: Optional[str] = data.get('badge_hash') + self._banner_hash: Optional[str] = data.get('banner_hash') + self._badge_primary_colour: Optional[str] = data.get('badge_color_primary') + self._badge_secondary_colour: Optional[str] = data.get('badge_color_secondary') + self._banner_primary_colour: Optional[str] = data.get('brand_color_primary') + self._banner_secondary_colour: Optional[str] = data.get('brand_color_secondary') + self._wildcard_descriptors: List[str] = data.get('wildcard_descriptors', []) + try: + self._verification_form: Optional[MemberVerification] = MemberVerification._from_data( + data=data['verification_form'], + state=self._state, + guild=self.guild, + ) + except KeyError: + self._verification_form = None + + def _update_from_clan_settings(self, data: ClanSettingsPayload) -> None: + self.tag = data['tag'] + self._games = {int(g): Object(int(g)) for g in data.get('game_application_ids', [])} + self._search_terms = data['search_terms'] + self.play_style = try_enum(ClanPlayStyle, data['play_style']) + self.description = data['description'] + self._wildcard_descriptors = data['wildcard_descriptors'] + self.badge_type = try_enum(ClanBadgeType, data['badge']) + self.banner_style = try_enum(ClanBannerStyle, data['banner']) + self._badge_primary_colour = data['badge_color_primary'] + self._badge_secondary_colour = data['badge_color_secondary'] + self._banner_primary_colour = data['brand_color_primary'] + self._banner_secondary_colour = data['brand_color_secondary'] + self._verification_form = MemberVerification._from_data( + data=data['verification_form'], + state=self._state, + guild=self.guild, + ) + + @property + def guild(self) -> Optional[Guild]: + """Optional[:class:`Guild`]: Returns the respective guild of this clan.""" + return self._state._get_guild(self.id) + + @property + def games(self) -> List[Object]: + """List[:class:`Object`]: Returns a list of objects that represent the games + the clan plays. + """ + return list(self._games.values()) + + @property + def search_terms(self) -> List[str]: + """List[:class:`str`]: Returns a read-only list of the interests, topics, + or traits for the clan. + """ + return self._search_terms.copy() + + @property + def wildcard_descriptors(self) -> List[str]: + """List[:class:`str`]: Returns a read-only list of the terms that describe the + clan. + """ + return self._wildcard_descriptors.copy() + + @property + def badge_primary_colour(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: A property that returns the clan badge primary colour. + + There is an alias for this named :attr:`badge_primary_color`. + """ + + if self._badge_primary_colour is None: + return None + return Colour.from_str(self._badge_primary_colour) + + @property + def badge_secondary_colour(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: A property that returns the clan badge secondary colour. + + There is an alias for this named :attr:`badge_secondary_color`. + """ + + if self._badge_secondary_colour is None: + return None + return Colour.from_str(self._badge_secondary_colour) + + @property + def badge_primary_color(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: A property that returns the clan badge primary color. + + There is an alias for this named :attr:`badge_primary_colour`. + """ + return self.badge_primary_colour + + @property + def badge_secondary_color(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: A property that returns the clan badge secondary color. + + There is an alias for this named :attr:`badge_secondary_colour`. + """ + return self.badge_secondary_colour + + @property + def banner_primary_colour(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: A property that returns the clan banner primary colour. + + There is an alias for this named :attr:`banner_primary_color`. + """ + + if self._banner_primary_colour is None: + return None + return Colour.from_str(self._banner_primary_colour) + + @property + def banner_secondary_colour(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: A property that returns the clan banner secondary colour. + + There is an alias for this named :attr:`banner_secondary_color`. + """ + + if self._banner_secondary_colour is None: + return None + return Colour.from_str(self._banner_secondary_colour) + + @property + def banner_primary_color(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: A property that returns the clan banner primary color. + + There is an alias for this named :attr:`banner_primary_colour`. + """ + return self.banner_primary_colour + + @property + def banner_secondary_color(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: A property that returns the clan banner secondary color. + + There is an alias for this named :attr:`banner_secondary_colour`. + """ + return self.banner_secondary_colour + + @property + def badge(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the badge asset, or ``None``.""" + if self._badge_hash is None: + return None + return Asset._from_clan_badge(self._state, self.id, self._badge_hash) + + @property + def banner(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the banner asset, or ``None``.""" + if self._banner_hash is None: + return None + return Asset._from_clan_banner(self._state, self.id, self._banner_hash) + + @property + def verification_form(self) -> Optional[MemberVerification]: + """Optional[:class:`MemberVerification`]: The member verification shown to applicants, + or ``None``. + """ + return self._verification_form + + async def fetch_settings(self) -> Self: + """|coro| + + Fetches this clan settings. + + Raises + ------ + HTTPException + An error occurred while fetching the clan settings. + + Returns + ------- + :class:`Clan` + The updated class with the settings up-to-date. + """ + + data = await self._state.http.get_clan_settings(self.id) + self._update_from_clan_settings(data) + return self + + async def edit( + self, + *, + tag: str = MISSING, + games: List[Snowflake] = MISSING, + search_terms: List[str] = MISSING, + play_style: ClanPlayStyle = MISSING, + description: str = MISSING, + wildcard_descriptors: List[str] = MISSING, + badge_type: ClanBadgeType = MISSING, + banner_style: ClanBannerStyle = MISSING, + badge_primary_colour: Colour = MISSING, + badge_secondary_colour: Colour = MISSING, + badge_primary_color: Colour = MISSING, + badge_secondary_color: Colour = MISSING, + banner_primary_colour: Colour = MISSING, + banner_secondary_colour: Colour = MISSING, + banner_primary_color: Colour = MISSING, + banner_secondary_color: Colour = MISSING, + verification_form: MemberVerification = MISSING, + ) -> None: + """|coro| + + Edits this clan. + + Parameters + ---------- + tag: :class:`str` + The new tag of the clan. Must be between 2 to 4 characters long. + games: List[:class`abc.Snowflake`] + A list of objects that meet the :class:`abc.Snowflake` ABC representing + the games this clan plays. + search_terms: List[:class:`str`] + The interests, topics, or traits for the clan. Can have up to 30 items, and + each one can be up to 24 characters. + play_style: :class:`ClanPlayStyle` + The play style of the clan. + description: :class:`str` + The clan description. + wildcard_descriptors: List[:class:`str`] + The terms that describe the clan. Can have up to 3 items, and each one can be + up to 12 characters. + badge_type: :class:`ClanBadgeType` + The badge type shown on the clan tag. + banner_style: :class:`ClannBannerStyle` + The banner style representing the clan. + badge_primary_colour: :class:`Colour` + The primary colour of the badge. + badge_secondary_colour: :class:`Colour` + The secondary colour of the badge. + badge_primary_color: :class:`Colour` + An alias for ``badge_primary_colour``. + badge_secondary_color: :class:`Colour` + An alias for ``badge_secondary_colour``. + banner_primary_colour: :class:`Colour` + The banner primary colour. + banner_secondary_colour: :class:`Colour` + The banner secondary colour. + banner_primary_color: :class:`Colour` + An alias for ``banner_primary_colour``. + banner_secondary_color: :class:`Color` + An alias for ``banner_secondary_colour``. + verification_form: :class:`MemberVerification` + The member verification shown to applicants. + """ + + # TODO: finish this diff --git a/discord/enums.py b/discord/enums.py index eaf8aef5e058..276e35cbfb22 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -74,6 +74,10 @@ 'EntitlementType', 'EntitlementOwnerType', 'PollLayoutType', + 'ClanPlayStyle', + 'ClanBadgeType', + 'ClanBannerStyle', + 'MemberVerificationFieldType', ) @@ -835,6 +839,59 @@ class ReactionType(Enum): burst = 1 +class ClanPlayStyle(Enum): + none = 0 + social = 1 + casual = 2 + competitive = 3 + creative = 4 + very_competitive = 5 + + +class ClanBadgeType(Enum): + sword = 0 + water_drop = 1 + skull = 2 + toadstool = 3 + moon = 4 + lightning = 5 + leaf = 6 + heart = 7 + fire = 8 + compass = 9 + crosshairs = 10 + flower = 11 + force = 12 + gem = 13 + lava = 14 + psychic = 15 + smoke = 16 + snow = 17 + sound = 18 + sun = 19 + wind = 20 + + +class ClanBannerStyle(Enum): + night_sky = 0 + castle = 1 + world_map = 2 + sea_foam = 3 + warp_tunnel = 4 + house = 5 + height_map = 6 + mesh = 7 + spatter = 8 + + +class MemberVerificationFieldType(Enum): + terms = "TERMS" + text_input = "TEXT_INPUT" + paragraph = "PARAGRAPH" + multiple_choice = "MULTIPLE_CHOICE" + # verification = "VERIFICATION" (deprecated) + + def create_unknown_value(cls: Type[E], val: Any) -> E: value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below name = f'unknown_{val}' diff --git a/discord/guild.py b/discord/guild.py index f34818b63503..7ac7bcc873c6 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -62,6 +62,9 @@ from .channel import _threaded_guild_channel_factory from .enums import ( AuditLogAction, + ClanBadgeType, + ClanBannerStyle, + ClanPlayStyle, VideoQualityMode, ChannelType, EntityType, @@ -94,6 +97,8 @@ from .welcome_screen import WelcomeScreen, WelcomeChannel from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction from .partial_emoji import _EmojiTag, PartialEmoji +from .clan import Clan +from .member_verification import MemberVerification __all__ = ( @@ -4423,3 +4428,93 @@ def dms_paused(self) -> bool: return False return self.dms_paused_until > utils.utcnow() + + def has_clan(self) -> bool: + """:class:`bool`: Whether there is a clan available for this guild. + + .. versionadded:: 2.5 + """ + return 'CLAN' in self.features + + async def fetch_clan(self) -> Clan: + """|coro| + + Fetches this guild's clan. + + Raises + ------ + ClientException + Guild does not have a clan. + HTTPException + An error occurred while fetching the clan info. + + Returns + ------- + :class:`Clan` + This guild's clan. + """ + + if not self.has_clan(): + raise ClientException( + 'Guild does not have a clan' + ) + + data = await self._state.http.get_clan(self.id) + + return Clan(data=data, state=self._state) + + async def create_clan( + self, + *, + tag: str, + games: Sequence[Snowflake], + search_terms: List[str], + play_style: ClanPlayStyle, + description: str, + wildcard_descriptors: List[str], + badge_type: ClanBadgeType, + badge_primary_colour: Colour = MISSING, + badge_primary_color: Colour = MISSING, + badge_secondary_colour: Colour = MISSING, + badge_secondary_color: Colour = MISSING, + banner_style: ClanBannerStyle, + banner_primary_colour: Colour = MISSING, + banner_primary_color: Colour = MISSING, + banner_secondary_colour: Colour = MISSING, + banner_secondary_color: Colour = MISSING, + verification_form: MemberVerification, + ) -> None: + """|coro| + + Creates a new clan from this guild. + + Raises + ------ + Forbidden + You donnot have enough permissions to create a clan for this + guild. + HTTPException + An error occurred while creating the guild clan. + """ + + actual_badge_prim_col = badge_primary_colour or badge_primary_color + actual_badge_sec_col = badge_secondary_colour or badge_secondary_color + actual_banner_prim_col = banner_primary_colour or banner_primary_color + actual_banner_sec_col = banner_secondary_colour or banner_secondary_color + + await self._state.http.create_clan( + self.id, + tag=tag, + game_application_ids=[str(g.id) for g in games], + search_terms=search_terms, + description=description, + play_style=play_style.value, + wildcard_descriptors=wildcard_descriptors, + badge=badge_type.value, + banner=banner_style.value, + badge_color_primary=actual_badge_prim_col, + badge_color_secondary=actual_badge_sec_col, + brand_color_primary=actual_banner_prim_col, + brand_color_secondary=actual_banner_sec_col, + verification_form=verification_form._to_dict() + ) diff --git a/discord/http.py b/discord/http.py index 608595fe3b89..c9f51e730b16 100644 --- a/discord/http.py +++ b/discord/http.py @@ -93,6 +93,7 @@ welcome_screen, sku, poll, + clan, ) from .types.snowflake import Snowflake, SnowflakeList @@ -2503,6 +2504,62 @@ def delete_entitlement(self, application_id: Snowflake, entitlement_id: Snowflak ), ) + # Clan + + def get_clans(self, game_type: Literal['all', 'genshin', 'valorant']) -> Response[List[clan.Clan]]: + return self.request( + Route( + 'GET', + '/discovery/games/{game_type}', + game_type=game_type, + ), + ) + + def get_clan(self, guild_id: int) -> Response[clan.Clan]: + return self.request( + Route( + 'GET', + '/discovery/{guild_id}/clan', + guild_id=guild_id, + ), + ) + + def create_clan(self, guild_id: int, **params: Any) -> Response[None]: + valid_keys = ( + 'tag', + 'game_application_ids', + 'search_terms', + 'play_style', + 'description', + 'wildcard_descriptors', + 'badge', + 'badge_color_primary', + 'badge_color_secondary', + 'banner', + 'brand_color_primary', + 'brand_color_secondary', + 'verification_form', + ) + payload = {k: v for k, v in params.items() if k in valid_keys} + + return self.request( + Route( + 'POST', + '/clan/{guild_id}', + guild_id=guild_id, + ), + json=payload, + ) + + def get_clan_settings(self, guild_id: int) -> Response[clan.ClanSettings]: + return self.request( + Route( + 'GET', + '/clan/{guild_id}/settings', + guild_id=guild_id, + ), + ) + # Misc def application_info(self) -> Response[appinfo.AppInfo]: diff --git a/discord/member_verification.py b/discord/member_verification.py new file mode 100644 index 000000000000..467d206d1237 --- /dev/null +++ b/discord/member_verification.py @@ -0,0 +1,313 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING, List, Optional, Union + +from .enums import try_enum, MemberVerificationFieldType +from .state import ConnectionState +from .utils import MISSING, parse_time + +if TYPE_CHECKING: + from typing_extensions import Self + + from .guild import Guild + + from .types.member_verification import ( + MemberVerificationField as MemberVerificationFieldPayload, + MemberVerification as MemberVerificationPayload, + ) + +__all__ = ( + 'MemberVerification', + 'MemberVerificationField', + 'PartialMemberVerificationField', +) + + +class PartialMemberVerificationField: + """Represents a partial member verification form field. + + Parameters + ---------- + type: :class:`MemberVerificationFieldType` + The type of field. + label: :class:`str` + The field label. Can be up to 300 characters. + choices: List[:class:`str`] + The choices the user has available. Can have up to 8 items, and each one + can be up to 150 characters. + + Must be passed if ``type`` is :attr:`MemberVerificationFieldType.multiple_choice`. + values: Optional[List[:class:`str`]] + The rules that the user must agree to. Can have up to 16 items, and each one + can be up to 300 characters. + + Must be passed if ``type`` is :attr:`MemberverificationFieldType.terms`. + required: :class:`bool` + Whether this field is required. + description: Optional[:class:`str`] + The field description. + automations: Optional[List[:class:`str`]] + ... + placeholder: Optional[:class:`str`] + The field placeholder. + """ + + if TYPE_CHECKING: + type: MemberVerificationFieldType + label: str + required: bool + _choices: List[str] + _values: Optional[List[str]] + description: Optional[str] + automations: Optional[List[str]] + _placeholder: Optional[str] + + __slots__ = ( + 'type', + 'label', + 'required', + '_choices', + '_values', + 'description', + 'automations', + '_placeholder', + ) + + def __init__( + self, + *, + type: MemberVerificationFieldType, + label: str, + required: bool, + choices: List[str] = MISSING, + values: Optional[List[str]] = MISSING, + description: Optional[str] = None, + automations: Optional[List[str]] = None, + placeholder: Optional[str] = MISSING, + ) -> None: + self.type: MemberVerificationFieldType = type + self.label: str = label + self.required: bool = required + self._choices: List[str] = choices + self._values: Optional[List[str]] = values + self.description: Optional[str] = description + self.automations: Optional[List[str]] = automations + self._placeholder: Optional[str] = placeholder + + @property + def choices(self) -> List[str]: + """List[:class:`str`]: The choices the user has available.""" + if self._choices is MISSING: + return [] + return self._choices + + @choices.setter + def choices(self, value: List[str]) -> None: + self._choices = value + + @property + def values(self) -> Optional[List[str]]: + """Optional[List[:class:`str`]]: The rules the user must agree to, or ``None``.""" + if self._values is MISSING: + return None + return self._values + + @values.setter + def values(self, value: Optional[List[str]]) -> None: + self._values = value + + @property + def placeholder(self) -> Optional[str]: + """Optional[:class:`str`]: Returns the field placeholder, or ``None``.""" + if self._placeholder is MISSING: + return None + return self._placeholder + + @placeholder.setter + def placeholder(self, value: Optional[str]) -> None: + self._placeholder = value + + def to_dict(self) -> MemberVerificationFieldPayload: + payload: MemberVerificationFieldPayload = { + 'field_type': self.type.value, + 'label': self.label, + 'required': self.required, + 'description': self.description, + 'automations': self.automations, + } + + if self._choices is not MISSING: + payload['choices'] = self._choices + + if self._values is not MISSING: + payload['values'] = self._values + + if self._placeholder is not MISSING: + payload['placeholder'] = self._placeholder + return payload + + +class MemberVerificationField(PartialMemberVerificationField): + """Represents a member verification form field. + + .. versionadded:: 2.5 + + Attributes + ---------- + type: :class:`MemberVerificationFieldType` + The type of field. + label: :class:`str` + The field label. Can be up to 300 characters. + choices: List[:class:`str`] + The choices the user has available. + + Filled when ``type`` is :attr:`MemberVerificationFieldType.multiple_choice`. + values: Optional[List[:class:`str`]] + The rules that the user must agree to. + + Filled when ``type`` is :attr:`MemberVerificationFieldType.terms` + response: Union[:class:`str`, :class:`int`, :class:`bool`] + The user input on the field. + + If ``type`` is :attr:`MemberVerificationFieldType.terms` then this should + be ``True``. + + If ``type`` is :attr:`MemberverificationFieldType.multiple_choice` then this + represents the index of the selected choice. + required: :class:`bool` + Whether this field is required for a successful application. + description: Optional[:class:`str`] + The field description. + automations: List[:class:`str`] + ... + placeholder: Optional[:class:`str`] + The placeholder text of the field. + """ + + __slots__ = ( + '_state', + 'response', + ) + + if TYPE_CHECKING: + _state: ConnectionState + response: Optional[Union[str, int, bool]] + + def __init__(self, *, data: MemberVerificationFieldPayload, state: ConnectionState) -> None: + self._state: ConnectionState = state + + self._update(data) + + def _update(self, data: MemberVerificationFieldPayload) -> None: + super().__init__( + type=try_enum(MemberVerificationFieldType, data['field_type']), + label=data['label'], + choices=data.get('choices', MISSING), + values=data.get('values', MISSING), + required=data['required'], + description=data['description'], + automations=data['automations'], + placeholder=data.get('placeholder', MISSING), + ) + try: + self.response: Optional[Union[str, int, bool]] = data['response'] + except KeyError: + self.response = None + + +class MemberVerification: + """Represents a member verification form. + + Parameters + ---------- + fields: Sequence[:class:`PartialMemberVerificationField`] + The fields this form has. Can be up to 5 items. + description: Optional[:class:`str`] + A description of what the clan is about. Can be different + from guild description. Can be up to 300 characters. Defaults + to ``None``. + """ + + __slots__ = ( + '_guild', + '_last_modified', + 'fields', + 'description', + ) + + def __init__( + self, + *, + fields: List[PartialMemberVerificationField], + description: Optional[str] = None, + ) -> None: + self.fields: List[PartialMemberVerificationField] = fields + self.description: Optional[str] = description + + self._guild: Optional[Guild] = None + self._last_modified: Optional[datetime] = None + + @classmethod + def _from_data(cls, *, data: MemberVerificationPayload, state: ConnectionState, guild: Optional[Guild]) -> Self: + self = cls( + fields=[MemberVerificationField(data=f, state=state) for f in data['form_fields']], + description=data.get('description'), + ) + if guild: + self._guild = guild + else: + # If guild is misteriously None then we use the guild preview + # the data offers us. + guild_data = data.get('guild') + + if guild_data is not None: + from .guild import Guild # circular import + self._guild = Guild(data=guild_data, state=state) # type: ignore + + self._last_modified = parse_time(data.get('version')) + + return self + + @property + def guild(self) -> Optional[Guild]: + """Optional[:class:`Guild`]: The guild this member verification is for. + """ + return self._guild + + @property + def last_modified_at(self) -> Optional[datetime]: + """Optional[:class:`datetime.datetime`]: The timestamp at which the verification + has been latest modified, or ``None``. + """ + return self._last_modified + + def to_dict(self) -> MemberVerificationFieldPayload: + return { + 'form_fields': [f.to_dict() for f in self.fields], # type:ignore + 'description': self.description, + } diff --git a/discord/types/clan.py b/discord/types/clan.py new file mode 100644 index 000000000000..b8d63a58a101 --- /dev/null +++ b/discord/types/clan.py @@ -0,0 +1,84 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Literal, Optional, TypedDict + +from .snowflake import Snowflake, SnowflakeList +from .member_verification import MemberVerification + +if TYPE_CHECKING: + from typing_extensions import NotRequired + +ClanBadge = Literal[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +] +ClanBanner = Literal[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, +] +ClanPlayStyle = Literal[ + 0, 1, 2, 3, 4, 5, +] + + +class PartialClan(TypedDict): + tag: str + badge: ClanBadge + + +class ClanSettings(PartialClan): + game_application_ids: SnowflakeList + search_terms: List[str] + play_style: ClanPlayStyle + description: str + wildcard_descriptors: List[str] + badge_color_primary: str + badge_color_secondary: str + banner: ClanBanner + brand_color_primary: str + brand_color_secondary: str + verification_form: MemberVerification + + +# We override almost everything because some may be missing on +# full clan objects. +class Clan(ClanSettings): + id: Snowflake + name: str + icon_hash: Optional[str] + member_count: int + description: str + play_style: NotRequired[ClanPlayStyle] + search_terms: NotRequired[List[str]] + game_application_ids: NotRequired[SnowflakeList] + badge_hash: NotRequired[str] + badge_color_primary: NotRequired[str] + badge_color_secondary: NotRequired[str] + banner: NotRequired[ClanBanner] + banner_hash: NotRequired[str] + brand_color_primary: NotRequired[str] + brand_color_secondary: NotRequired[str] + wildcard_descriptors: NotRequired[List[str]] + verification_form: NotRequired[MemberVerification] diff --git a/discord/types/member_verification.py b/discord/types/member_verification.py new file mode 100644 index 000000000000..b62c8444bc52 --- /dev/null +++ b/discord/types/member_verification.py @@ -0,0 +1,58 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Literal, Optional, TypedDict, Union + +from .guild import GuildPreview + +if TYPE_CHECKING: + from typing_extensions import NotRequired + +MemberVerificationFieldType = Literal[ + 'TERMS', + 'MULTIPLE_CHOICE', + 'TEXT_INPUT', + 'PARAGRAPH', + # 'VERIFICATION', +] + + +class MemberVerificationField(TypedDict): + field_type: [MemberVerificationFieldType] + label: str + choices: NotRequired[List[str]] + values: NotRequired[Optional[List[str]]] + response: NotRequired[Union[str, int, bool]] + required: bool + description: Optional[str] + automations: Optional[List[str]] + placeholder: NotRequired[Optional[str]] + + +class MemberVerification(TypedDict): + version: str + form_fields: List[MemberVerificationField] + description: Optional[str] + guild: Optional[GuildPreview] From 062f906a92aa51082a1dda269cf90d5e77989e23 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Mon, 22 Jul 2024 22:18:27 +0200 Subject: [PATCH 02/12] Add UserClan type --- discord/types/clan.py | 11 +++++++++++ discord/types/user.py | 2 ++ 2 files changed, 13 insertions(+) diff --git a/discord/types/clan.py b/discord/types/clan.py index b8d63a58a101..5becb690d646 100644 --- a/discord/types/clan.py +++ b/discord/types/clan.py @@ -82,3 +82,14 @@ class Clan(ClanSettings): brand_color_secondary: NotRequired[str] wildcard_descriptors: NotRequired[List[str]] verification_form: NotRequired[MemberVerification] + + +# This is not the same as a partial clan as +# the badge the PartialClan provides is one +# of the enum members, but here is the hash +# pretty weird though lol +class UserClan(TypedDict): + tag: str + badge: str + identity_guild_id: Snowflake + identity_enabled: bool diff --git a/discord/types/user.py b/discord/types/user.py index 1f027ce9d9ac..dd3f2ff71c0f 100644 --- a/discord/types/user.py +++ b/discord/types/user.py @@ -23,6 +23,7 @@ """ from .snowflake import Snowflake +from .clan import UserClan from typing import Literal, Optional, TypedDict from typing_extensions import NotRequired @@ -54,3 +55,4 @@ class User(PartialUser, total=False): flags: int premium_type: PremiumType public_flags: int + clan: UserClan From 8ac930286fe637734f5eb4ec8b40b97694ac1903 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Mon, 22 Jul 2024 22:18:43 +0200 Subject: [PATCH 03/12] Add UserClan --- discord/clan.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/discord/clan.py b/discord/clan.py index 3790dfd2712e..163e20670b09 100644 --- a/discord/clan.py +++ b/discord/clan.py @@ -44,14 +44,77 @@ PartialClan as PartialClanPayload, Clan as ClanPayload, ClanSettings as ClanSettingsPayload, + UserClan as UserClanPayload, ) __all__ = ('PartialClan', 'Clan') +class UserClan: + """Represents a partial clan accessible via a user. + + .. container:: operations + + .. describe:: x == y + + Checks if two user clans are equal. + + .. describe:: x != y + + Checks if two user clans are not equal. + + .. versionadded:: 2.5 + + Attributes + ---------- + guild_id: :class:`int` + The guild ID the clan is from. + enabled: :class:`bool` + Whether the user is displaying their clan tag. + tag: :class:`str` + The clan tag. + """ + + __slots__ = ( + '_state', + 'guild_id', + 'enabled', + 'tag', + '_badge_hash', + ) + + def __init__(self, *, data: UserClanPayload, state: ConnectionState) -> None: + self._state: ConnectionState = state + self.guild_id: int = int(data['identity_guild_id']) + self.enabled: bool = data['identity_enabled'] + self.tag: str = data['tag'] + self._badge_hash: str = data['badge'] + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) and self.tag == other.tag and self.guild_id == other.guild_id + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def __repr__(self) -> str: + return f'' + + @property + def guild(self) -> Optional[Guild]: + """Optional[:class:`Guild`]: Returns the cached guild this clan is from.""" + return self._state._get_guild(self.guild_id) + + @property + def badge(self) -> Asset: + """:class:`Asset`: Returns the clan badge asset.""" + return Asset._from_clan_badge(self._state, self.guild_id, self._badge_hash) + + class PartialClan: """Represents a partial clan. + .. versionadded:: 2.5 + Attributes ---------- tag: :class:`str` @@ -78,6 +141,26 @@ def __init__(self, *, data: PartialClanPayload, state: ConnectionState) -> None: class Clan(Hashable, PartialClan): """Represents a clan. + .. container:: operations + + .. describe:: x == y + + Checks if two clans are equal. + + .. describe:: x != y + + Checks if two clans are not equal. + + .. describe:: hash(x) + + Returns the clan's hash. + + .. describe:: str(x) + + Returns the clan's name. + + .. versionadded:: 2.5 + Attributes ---------- id: :class:`int` @@ -196,6 +279,18 @@ def _update_from_clan_settings(self, data: ClanSettingsPayload) -> None: guild=self.guild, ) + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) and self.id == other.id + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def __str__(self) -> str: + return self.name + + def __repr__(self) -> str: + return f'' + @property def guild(self) -> Optional[Guild]: """Optional[:class:`Guild`]: Returns the respective guild of this clan.""" From 4a7bb5e53404a12c9f6f3d441b97c3839709f3b7 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Mon, 22 Jul 2024 22:19:24 +0200 Subject: [PATCH 04/12] Add UserClan to __all__ --- discord/clan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/clan.py b/discord/clan.py index 163e20670b09..f926b43a64dc 100644 --- a/discord/clan.py +++ b/discord/clan.py @@ -47,7 +47,7 @@ UserClan as UserClanPayload, ) -__all__ = ('PartialClan', 'Clan') +__all__ = ('UserClan', 'PartialClan', 'Clan') class UserClan: From 888b68e572afd5fb3fb0439c52058fb6b0d7d1e9 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Mon, 22 Jul 2024 23:24:35 +0200 Subject: [PATCH 05/12] fix type & and add User.clan --- discord/types/user.py | 2 +- discord/user.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/discord/types/user.py b/discord/types/user.py index dd3f2ff71c0f..afe96d558a94 100644 --- a/discord/types/user.py +++ b/discord/types/user.py @@ -55,4 +55,4 @@ class User(PartialUser, total=False): flags: int premium_type: PremiumType public_flags: int - clan: UserClan + clan: NotRequired[Optional[UserClan]] diff --git a/discord/user.py b/discord/user.py index 5151957dc998..fef37b1c4027 100644 --- a/discord/user.py +++ b/discord/user.py @@ -32,6 +32,7 @@ from .enums import DefaultAvatar from .flags import PublicUserFlags from .utils import snowflake_time, _bytes_to_base64_data, MISSING, _get_as_snowflake +from .clan import UserClan if TYPE_CHECKING: from typing_extensions import Self @@ -71,6 +72,7 @@ class BaseUser(_UserTag): '_public_flags', '_state', '_avatar_decoration_data', + 'clan', ) if TYPE_CHECKING: @@ -86,6 +88,7 @@ class BaseUser(_UserTag): _accent_colour: Optional[int] _public_flags: int _avatar_decoration_data: Optional[AvatarDecorationData] + clan: Optional[UserClan] def __init__(self, *, state: ConnectionState, data: Union[UserPayload, PartialUserPayload]) -> None: self._state = state @@ -124,6 +127,12 @@ def _update(self, data: Union[UserPayload, PartialUserPayload]) -> None: self.system = data.get('system', False) self._avatar_decoration_data = data.get('avatar_decoration_data') + clan = data.get('clan') + self.clan = None + + if clan: + self.clan = UserClan(data=clan, state=self._state) + @classmethod def _copy(cls, user: Self) -> Self: self = cls.__new__(cls) # bypass __init__ From fd87981604a8231cc299184a1f021e28a3b2a103 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:41:31 +0100 Subject: [PATCH 06/12] Black, finish some things, apply comments --- discord/__init__.py | 2 + discord/clan.py | 173 +++++---------------------- discord/guild.py | 84 +++++-------- discord/http.py | 58 ++------- discord/member_verification.py | 13 +- discord/types/clan.py | 67 +++++++---- discord/types/member_verification.py | 4 +- docs/api.rst | 44 +++++++ 8 files changed, 169 insertions(+), 276 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 765719b68731..2c3244ddfefe 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -70,6 +70,8 @@ from .threads import * from .automod import * from .poll import * +from .clan import * +from .member_verification import * class VersionInfo(NamedTuple): diff --git a/discord/clan.py b/discord/clan.py index f926b43a64dc..964f661414a8 100644 --- a/discord/clan.py +++ b/discord/clan.py @@ -25,25 +25,24 @@ from typing import TYPE_CHECKING, Dict, List, Optional +from . import utils from .enums import ClanBannerStyle, ClanPlayStyle, try_enum, ClanBadgeType from .mixins import Hashable from .state import ConnectionState from .object import Object from .colour import Colour from .asset import Asset -from .member_verification import MemberVerification +from .member_verification import MemberVerificationForm from .utils import MISSING from .abc import Snowflake if TYPE_CHECKING: - from typing_extensions import Self from .guild import Guild from .types.clan import ( PartialClan as PartialClanPayload, Clan as ClanPayload, - ClanSettings as ClanSettingsPayload, UserClan as UserClanPayload, ) @@ -53,6 +52,8 @@ class UserClan: """Represents a partial clan accessible via a user. + .. versionadded:: 2.5 + .. container:: operations .. describe:: x == y @@ -67,12 +68,20 @@ class UserClan: Attributes ---------- - guild_id: :class:`int` - The guild ID the clan is from. enabled: :class:`bool` Whether the user is displaying their clan tag. - tag: :class:`str` + guild_id: Optional[:class:`int`] + The guild ID the clan is from. + + .. note:: + + This will be ``None`` if :attr:`.enabled` is ``False``. + tag: Optional[:class:`str`] The clan tag. + + .. note:: + + This will be ``None`` if :attr:`.enabled` is ``False``. """ __slots__ = ( @@ -85,10 +94,10 @@ class UserClan: def __init__(self, *, data: UserClanPayload, state: ConnectionState) -> None: self._state: ConnectionState = state - self.guild_id: int = int(data['identity_guild_id']) + self.guild_id: Optional[int] = utils._get_as_snowflake(data, 'identity_guild_id') self.enabled: bool = data['identity_enabled'] - self.tag: str = data['tag'] - self._badge_hash: str = data['badge'] + self.tag: Optional[str] = data.get('tag') + self._badge_hash: Optional[str] = data.get('badge') def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) and self.tag == other.tag and self.guild_id == other.guild_id @@ -97,7 +106,7 @@ def __ne__(self, other: object) -> bool: return not self.__eq__(other) def __repr__(self) -> str: - return f'' + return f'' @property def guild(self) -> Optional[Guild]: @@ -105,8 +114,15 @@ def guild(self) -> Optional[Guild]: return self._state._get_guild(self.guild_id) @property - def badge(self) -> Asset: - """:class:`Asset`: Returns the clan badge asset.""" + def badge(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the clan badge asset. + + .. note:: + + This will be ``None`` if :attr:`.enabled` is ``False``. + """ + if self._badge_hash is None or self.guild_id is None: + return None return Asset._from_clan_badge(self._state, self.guild_id, self._badge_hash) @@ -219,10 +235,9 @@ class Clan(Hashable, PartialClan): _badge_secondary_colour: Optional[str] _banner_primary_colour: Optional[str] _banner_secondary_colour: Optional[str] - _verification_form: Optional[MemberVerification] def __init__(self, *, data: ClanPayload, state: ConnectionState) -> None: - super().__init__(data=data, state=state) + super().__init__(data=data, state=state) # type: ignore self.id: int = int(data['id']) self._update(data) @@ -234,15 +249,11 @@ def _update(self, data: ClanPayload) -> None: self.play_style: ClanPlayStyle = try_enum(ClanPlayStyle, data.get('play_style', 0)) try: - self.banner_style: Optional[ClanBannerStyle] = try_enum( - ClanBannerStyle, data['banner'] - ) + self.banner_style: Optional[ClanBannerStyle] = try_enum(ClanBannerStyle, data['banner']) except KeyError: self.banner_style = None - self._games: Dict[int, Object] = { - int(g): Object(int(g)) for g in data.get('game_application_ids', []) - } + self._games: Dict[int, Object] = {int(g): Object(int(g)) for g in data.get('game_application_ids', [])} self._search_terms: List[str] = data.get('search_terms', []) self._badge_hash: Optional[str] = data.get('badge_hash') self._banner_hash: Optional[str] = data.get('banner_hash') @@ -251,33 +262,6 @@ def _update(self, data: ClanPayload) -> None: self._banner_primary_colour: Optional[str] = data.get('brand_color_primary') self._banner_secondary_colour: Optional[str] = data.get('brand_color_secondary') self._wildcard_descriptors: List[str] = data.get('wildcard_descriptors', []) - try: - self._verification_form: Optional[MemberVerification] = MemberVerification._from_data( - data=data['verification_form'], - state=self._state, - guild=self.guild, - ) - except KeyError: - self._verification_form = None - - def _update_from_clan_settings(self, data: ClanSettingsPayload) -> None: - self.tag = data['tag'] - self._games = {int(g): Object(int(g)) for g in data.get('game_application_ids', [])} - self._search_terms = data['search_terms'] - self.play_style = try_enum(ClanPlayStyle, data['play_style']) - self.description = data['description'] - self._wildcard_descriptors = data['wildcard_descriptors'] - self.badge_type = try_enum(ClanBadgeType, data['badge']) - self.banner_style = try_enum(ClanBannerStyle, data['banner']) - self._badge_primary_colour = data['badge_color_primary'] - self._badge_secondary_colour = data['badge_color_secondary'] - self._banner_primary_colour = data['brand_color_primary'] - self._banner_secondary_colour = data['brand_color_secondary'] - self._verification_form = MemberVerification._from_data( - data=data['verification_form'], - state=self._state, - guild=self.guild, - ) def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) and self.id == other.id @@ -406,98 +390,3 @@ def banner(self) -> Optional[Asset]: if self._banner_hash is None: return None return Asset._from_clan_banner(self._state, self.id, self._banner_hash) - - @property - def verification_form(self) -> Optional[MemberVerification]: - """Optional[:class:`MemberVerification`]: The member verification shown to applicants, - or ``None``. - """ - return self._verification_form - - async def fetch_settings(self) -> Self: - """|coro| - - Fetches this clan settings. - - Raises - ------ - HTTPException - An error occurred while fetching the clan settings. - - Returns - ------- - :class:`Clan` - The updated class with the settings up-to-date. - """ - - data = await self._state.http.get_clan_settings(self.id) - self._update_from_clan_settings(data) - return self - - async def edit( - self, - *, - tag: str = MISSING, - games: List[Snowflake] = MISSING, - search_terms: List[str] = MISSING, - play_style: ClanPlayStyle = MISSING, - description: str = MISSING, - wildcard_descriptors: List[str] = MISSING, - badge_type: ClanBadgeType = MISSING, - banner_style: ClanBannerStyle = MISSING, - badge_primary_colour: Colour = MISSING, - badge_secondary_colour: Colour = MISSING, - badge_primary_color: Colour = MISSING, - badge_secondary_color: Colour = MISSING, - banner_primary_colour: Colour = MISSING, - banner_secondary_colour: Colour = MISSING, - banner_primary_color: Colour = MISSING, - banner_secondary_color: Colour = MISSING, - verification_form: MemberVerification = MISSING, - ) -> None: - """|coro| - - Edits this clan. - - Parameters - ---------- - tag: :class:`str` - The new tag of the clan. Must be between 2 to 4 characters long. - games: List[:class`abc.Snowflake`] - A list of objects that meet the :class:`abc.Snowflake` ABC representing - the games this clan plays. - search_terms: List[:class:`str`] - The interests, topics, or traits for the clan. Can have up to 30 items, and - each one can be up to 24 characters. - play_style: :class:`ClanPlayStyle` - The play style of the clan. - description: :class:`str` - The clan description. - wildcard_descriptors: List[:class:`str`] - The terms that describe the clan. Can have up to 3 items, and each one can be - up to 12 characters. - badge_type: :class:`ClanBadgeType` - The badge type shown on the clan tag. - banner_style: :class:`ClannBannerStyle` - The banner style representing the clan. - badge_primary_colour: :class:`Colour` - The primary colour of the badge. - badge_secondary_colour: :class:`Colour` - The secondary colour of the badge. - badge_primary_color: :class:`Colour` - An alias for ``badge_primary_colour``. - badge_secondary_color: :class:`Colour` - An alias for ``badge_secondary_colour``. - banner_primary_colour: :class:`Colour` - The banner primary colour. - banner_secondary_colour: :class:`Colour` - The banner secondary colour. - banner_primary_color: :class:`Colour` - An alias for ``banner_primary_colour``. - banner_secondary_color: :class:`Color` - An alias for ``banner_secondary_colour``. - verification_form: :class:`MemberVerification` - The member verification shown to applicants. - """ - - # TODO: finish this diff --git a/discord/guild.py b/discord/guild.py index 7ac7bcc873c6..13b90ffdaa09 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -62,9 +62,6 @@ from .channel import _threaded_guild_channel_factory from .enums import ( AuditLogAction, - ClanBadgeType, - ClanBannerStyle, - ClanPlayStyle, VideoQualityMode, ChannelType, EntityType, @@ -98,7 +95,7 @@ from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction from .partial_emoji import _EmojiTag, PartialEmoji from .clan import Clan -from .member_verification import MemberVerification +from .member_verification import MemberVerificationForm __all__ = ( @@ -4441,6 +4438,8 @@ async def fetch_clan(self) -> Clan: Fetches this guild's clan. + .. versionadded:: 2.5 + Raises ------ ClientException @@ -4455,66 +4454,43 @@ async def fetch_clan(self) -> Clan: """ if not self.has_clan(): - raise ClientException( - 'Guild does not have a clan' - ) + raise ClientException('Guild does not have a clan') data = await self._state.http.get_clan(self.id) return Clan(data=data, state=self._state) - async def create_clan( - self, - *, - tag: str, - games: Sequence[Snowflake], - search_terms: List[str], - play_style: ClanPlayStyle, - description: str, - wildcard_descriptors: List[str], - badge_type: ClanBadgeType, - badge_primary_colour: Colour = MISSING, - badge_primary_color: Colour = MISSING, - badge_secondary_colour: Colour = MISSING, - badge_secondary_color: Colour = MISSING, - banner_style: ClanBannerStyle, - banner_primary_colour: Colour = MISSING, - banner_primary_color: Colour = MISSING, - banner_secondary_colour: Colour = MISSING, - banner_secondary_color: Colour = MISSING, - verification_form: MemberVerification, - ) -> None: + async def fetch_member_verification_form( + self, *, with_guild: bool = MISSING, invite: Union[str, Invite] = MISSING + ) -> Optional[MemberVerificationForm]: """|coro| - - Creates a new clan from this guild. + + Fetches the current guild member verification form. + + .. versionadded:: 2.5 + + Parameters + ---------- + with_guild: :class:`bool` + Whether to include a guild snapshot on the member verification form. + invite: Union[:class:`str`, :class:`Invite`] + The invite code the member verification form is fetched from. Raises ------ - Forbidden - You donnot have enough permissions to create a clan for this - guild. HTTPException - An error occurred while creating the guild clan. + Fetching the member verification form failed. + + Returns + ------- + Optional[:class:`MemberVerificationForm`] + The member verification form, or ``None``. """ - actual_badge_prim_col = badge_primary_colour or badge_primary_color - actual_badge_sec_col = badge_secondary_colour or badge_secondary_color - actual_banner_prim_col = banner_primary_colour or banner_primary_color - actual_banner_sec_col = banner_secondary_colour or banner_secondary_color + invite_code = MISSING - await self._state.http.create_clan( - self.id, - tag=tag, - game_application_ids=[str(g.id) for g in games], - search_terms=search_terms, - description=description, - play_style=play_style.value, - wildcard_descriptors=wildcard_descriptors, - badge=badge_type.value, - banner=banner_style.value, - badge_color_primary=actual_badge_prim_col, - badge_color_secondary=actual_badge_sec_col, - brand_color_primary=actual_banner_prim_col, - brand_color_secondary=actual_banner_sec_col, - verification_form=verification_form._to_dict() - ) + if invite is not MISSING: + invite_code = invite.code if isinstance(invite, Invite) else invite + + form = await self._state.http.get_guild_member_verification(self.id, with_guild=with_guild, invite_code=invite_code) + return MemberVerificationForm._from_data(data=form, state=self._state, guild=self) diff --git a/discord/http.py b/discord/http.py index c9f51e730b16..bd8291879d70 100644 --- a/discord/http.py +++ b/discord/http.py @@ -94,6 +94,7 @@ sku, poll, clan, + member_verification, ) from .types.snowflake import Snowflake, SnowflakeList @@ -1793,6 +1794,16 @@ def edit_widget( def edit_incident_actions(self, guild_id: Snowflake, payload: guild.IncidentData) -> Response[guild.IncidentData]: return self.request(Route('PUT', '/guilds/{guild_id}/incident-actions', guild_id=guild_id), json=payload) + def get_guild_member_verification(self, guild_id: Snowflake, *, with_guild: bool = MISSING, invite_code: str = MISSING) -> Response[member_verification.MemberVerificationForm]: + params = {} + + if with_guild is not MISSING: + params['with_guild'] = with_guild + if invite_code is not MISSING: + params['invite_code'] = invite_code + + return self.request(Route('GET', '/guilds/{guild_id}/member-verification', guild_id=guild_id), params=params) + # Invite management def create_invite( @@ -2504,16 +2515,7 @@ def delete_entitlement(self, application_id: Snowflake, entitlement_id: Snowflak ), ) - # Clan - - def get_clans(self, game_type: Literal['all', 'genshin', 'valorant']) -> Response[List[clan.Clan]]: - return self.request( - Route( - 'GET', - '/discovery/games/{game_type}', - game_type=game_type, - ), - ) + # Clans def get_clan(self, guild_id: int) -> Response[clan.Clan]: return self.request( @@ -2524,42 +2526,6 @@ def get_clan(self, guild_id: int) -> Response[clan.Clan]: ), ) - def create_clan(self, guild_id: int, **params: Any) -> Response[None]: - valid_keys = ( - 'tag', - 'game_application_ids', - 'search_terms', - 'play_style', - 'description', - 'wildcard_descriptors', - 'badge', - 'badge_color_primary', - 'badge_color_secondary', - 'banner', - 'brand_color_primary', - 'brand_color_secondary', - 'verification_form', - ) - payload = {k: v for k, v in params.items() if k in valid_keys} - - return self.request( - Route( - 'POST', - '/clan/{guild_id}', - guild_id=guild_id, - ), - json=payload, - ) - - def get_clan_settings(self, guild_id: int) -> Response[clan.ClanSettings]: - return self.request( - Route( - 'GET', - '/clan/{guild_id}/settings', - guild_id=guild_id, - ), - ) - # Misc def application_info(self) -> Response[appinfo.AppInfo]: diff --git a/discord/member_verification.py b/discord/member_verification.py index 467d206d1237..ceaf84ab1979 100644 --- a/discord/member_verification.py +++ b/discord/member_verification.py @@ -38,11 +38,11 @@ from .types.member_verification import ( MemberVerificationField as MemberVerificationFieldPayload, - MemberVerification as MemberVerificationPayload, + MemberVerificationForm as MemberVerificationFormPayload, ) __all__ = ( - 'MemberVerification', + 'MemberVerificationForm', 'MemberVerificationField', 'PartialMemberVerificationField', ) @@ -220,7 +220,6 @@ class MemberVerificationField(PartialMemberVerificationField): def __init__(self, *, data: MemberVerificationFieldPayload, state: ConnectionState) -> None: self._state: ConnectionState = state - self._update(data) def _update(self, data: MemberVerificationFieldPayload) -> None: @@ -240,7 +239,7 @@ def _update(self, data: MemberVerificationFieldPayload) -> None: self.response = None -class MemberVerification: +class MemberVerificationForm: """Represents a member verification form. Parameters @@ -273,7 +272,7 @@ def __init__( self._last_modified: Optional[datetime] = None @classmethod - def _from_data(cls, *, data: MemberVerificationPayload, state: ConnectionState, guild: Optional[Guild]) -> Self: + def _from_data(cls, *, data: MemberVerificationFormPayload, state: ConnectionState, guild: Optional[Guild]) -> Self: self = cls( fields=[MemberVerificationField(data=f, state=state) for f in data['form_fields']], description=data.get('description'), @@ -287,6 +286,7 @@ def _from_data(cls, *, data: MemberVerificationPayload, state: ConnectionState, if guild_data is not None: from .guild import Guild # circular import + self._guild = Guild(data=guild_data, state=state) # type: ignore self._last_modified = parse_time(data.get('version')) @@ -295,8 +295,7 @@ def _from_data(cls, *, data: MemberVerificationPayload, state: ConnectionState, @property def guild(self) -> Optional[Guild]: - """Optional[:class:`Guild`]: The guild this member verification is for. - """ + """Optional[:class:`Guild`]: The guild this member verification is for.""" return self._guild @property diff --git a/discord/types/clan.py b/discord/types/clan.py index 5becb690d646..99a91fea0a2d 100644 --- a/discord/types/clan.py +++ b/discord/types/clan.py @@ -26,20 +26,51 @@ from typing import TYPE_CHECKING, List, Literal, Optional, TypedDict from .snowflake import Snowflake, SnowflakeList -from .member_verification import MemberVerification if TYPE_CHECKING: from typing_extensions import NotRequired ClanBadge = Literal[ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, ] ClanBanner = Literal[ - 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, ] ClanPlayStyle = Literal[ - 0, 1, 2, 3, 4, 5, + 0, + 1, + 2, + 3, + 4, + 5, ] @@ -48,23 +79,9 @@ class PartialClan(TypedDict): badge: ClanBadge -class ClanSettings(PartialClan): - game_application_ids: SnowflakeList - search_terms: List[str] - play_style: ClanPlayStyle - description: str - wildcard_descriptors: List[str] - badge_color_primary: str - badge_color_secondary: str - banner: ClanBanner - brand_color_primary: str - brand_color_secondary: str - verification_form: MemberVerification - - # We override almost everything because some may be missing on # full clan objects. -class Clan(ClanSettings): +class Clan(PartialClan): id: Snowflake name: str icon_hash: Optional[str] @@ -73,15 +90,15 @@ class Clan(ClanSettings): play_style: NotRequired[ClanPlayStyle] search_terms: NotRequired[List[str]] game_application_ids: NotRequired[SnowflakeList] + badge: NotRequired[ClanBadge] badge_hash: NotRequired[str] badge_color_primary: NotRequired[str] badge_color_secondary: NotRequired[str] banner: NotRequired[ClanBanner] banner_hash: NotRequired[str] brand_color_primary: NotRequired[str] - brand_color_secondary: NotRequired[str] + brand_color_seconday: NotRequired[str] wildcard_descriptors: NotRequired[List[str]] - verification_form: NotRequired[MemberVerification] # This is not the same as a partial clan as @@ -89,7 +106,7 @@ class Clan(ClanSettings): # of the enum members, but here is the hash # pretty weird though lol class UserClan(TypedDict): - tag: str - badge: str - identity_guild_id: Snowflake + tag: Optional[str] + badge: Optional[str] + identity_guild_id: Optional[Snowflake] identity_enabled: bool diff --git a/discord/types/member_verification.py b/discord/types/member_verification.py index b62c8444bc52..7253265f1ee0 100644 --- a/discord/types/member_verification.py +++ b/discord/types/member_verification.py @@ -40,7 +40,7 @@ class MemberVerificationField(TypedDict): - field_type: [MemberVerificationFieldType] + field_type: MemberVerificationFieldType label: str choices: NotRequired[List[str]] values: NotRequired[Optional[List[str]]] @@ -51,7 +51,7 @@ class MemberVerificationField(TypedDict): placeholder: NotRequired[Optional[str]] -class MemberVerification(TypedDict): +class MemberVerificationForm(TypedDict): version: str form_fields: List[MemberVerificationField] description: Optional[str] diff --git a/docs/api.rst b/docs/api.rst index 41cf6549d169..698052033c58 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4688,6 +4688,24 @@ Member .. automethod:: typing :async-with: +MemberVerificationField +~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: MemberVerificationField + +.. autoclass:: MemberVerificationField() + :members: + :inherited-members: + + +MemberVerificationForm +~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: MemberVerificationForm + +.. autoclass:: MemberVerificationForm() + :members: + Spotify ~~~~~~~~ @@ -4826,6 +4844,15 @@ CategoryChannel :members: :inherited-members: +Clan +~~~~ + +.. attributetable:: Clan + +.. autoclass:: Clan() + :members: + :inherited-members: + DMChannel ~~~~~~~~~ @@ -4852,6 +4879,14 @@ GroupChannel .. automethod:: typing :async-with: +PartialClan +~~~~~~~~~~~ + +.. attributetable:: PartialClan + +.. autoclass:: PartialClan() + :members: + PartialInviteGuild ~~~~~~~~~~~~~~~~~~~ @@ -5407,6 +5442,15 @@ PollMedia :members: +PartialMemberVerificationField +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: PartialMemberVerificationField + +.. autoclass:: PartialMemberVerificationField + :members: + + Exceptions ------------ From 54dda43796e42d67409bc0d33e346416f81d1246 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:47:23 +0100 Subject: [PATCH 07/12] Try to fix circular imports --- discord/clan.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/discord/clan.py b/discord/clan.py index 964f661414a8..14c1f9a7699a 100644 --- a/discord/clan.py +++ b/discord/clan.py @@ -28,17 +28,14 @@ from . import utils from .enums import ClanBannerStyle, ClanPlayStyle, try_enum, ClanBadgeType from .mixins import Hashable -from .state import ConnectionState from .object import Object from .colour import Colour from .asset import Asset -from .member_verification import MemberVerificationForm -from .utils import MISSING -from .abc import Snowflake if TYPE_CHECKING: from .guild import Guild + from .state import ConnectionState from .types.clan import ( PartialClan as PartialClanPayload, From a39da411a480e4e9f03e572564085b380eac425d Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:49:57 +0100 Subject: [PATCH 08/12] Try to fix circular imports again --- discord/member_verification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/member_verification.py b/discord/member_verification.py index ceaf84ab1979..0bcd6edc10da 100644 --- a/discord/member_verification.py +++ b/discord/member_verification.py @@ -28,13 +28,13 @@ from typing import TYPE_CHECKING, List, Optional, Union from .enums import try_enum, MemberVerificationFieldType -from .state import ConnectionState from .utils import MISSING, parse_time if TYPE_CHECKING: from typing_extensions import Self from .guild import Guild + from .state import ConnectionState from .types.member_verification import ( MemberVerificationField as MemberVerificationFieldPayload, From fcbef0b55dd162e47d5f5ba74fe492f2e31ce37e Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:52:08 +0100 Subject: [PATCH 09/12] Black --- discord/http.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/http.py b/discord/http.py index ea643d3f328b..a51c57c27851 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1814,7 +1814,9 @@ def edit_widget( def edit_incident_actions(self, guild_id: Snowflake, payload: guild.IncidentData) -> Response[guild.IncidentData]: return self.request(Route('PUT', '/guilds/{guild_id}/incident-actions', guild_id=guild_id), json=payload) - def get_guild_member_verification(self, guild_id: Snowflake, *, with_guild: bool = MISSING, invite_code: str = MISSING) -> Response[member_verification.MemberVerificationForm]: + def get_guild_member_verification( + self, guild_id: Snowflake, *, with_guild: bool = MISSING, invite_code: str = MISSING + ) -> Response[member_verification.MemberVerificationForm]: params = {} if with_guild is not MISSING: From b3e54676697db73dd50cb8f39ec4417f82734d36 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Tue, 5 Nov 2024 18:12:26 +0100 Subject: [PATCH 10/12] Added Enums to docs --- docs/api.rst | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index d015adbbba54..6e9aac521497 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3828,6 +3828,194 @@ of :class:`enum.Enum`. An alias for :attr:`.reply`. + +.. class:: ClanPlayStyle + + Represents a clan play style. + + .. versionadded:: 2.5 + + .. attribute:: none + + The clan has no play style. + + .. attribute:: social + + A social clan. Also described as "Very Casual" on the UI. + + .. attribute:: casual + + A casual clan. + + .. attribute:: competitive + + A competitive clan. + + .. attribute:: creative + + A creative clan. + + .. attribute:: very_competitive + + A very competitive clan. + + +.. class:: ClanBadgeType + + Represents a clan badge type. + + .. attribute:: sword + + A sword icon badge. + + .. attribute:: water_drop + + A water drop icon badge. + + .. attribute:: skull + + A skull icon badge. + + .. attribute:: toadstool + + A toadstool icon badge. + + .. attribute:: moon + + A moon icon badge. + + .. attribute:: lightning + + A lightning icon badge. + + .. attribute:: leaf + + A leaf icon badge. + + .. attribute:: heart + + A heart icon badge. + + .. attribute:: fire + + A fire icon badge. + + .. attribute:: compass + + A compass icon badge. + + .. attribute:: crosshairs + + A crosshair icon badge. + + .. attribute:: flower + + A flower icon badge. + + .. attribute:: force + + A force icon badge. + + .. attribute:: gem + + A gem icon badge. + + .. attribute:: lava + + A lava icon badge. + + .. attribute:: psychic + + A psychic icon badge. + + .. attribute:: smoke + + A smoke icon badge. + + .. attribute:: snow + + A snow icon badge. + + .. attribute:: sound + + A sound icon badge. + + .. attribute:: sun + + A sun icon badge. + + .. attribute:: wind + + A wind icon badge. + + +.. class:: ClanBannerStyle + + Represents a clan banner style. + + .. attribute:: night_sky + + A night sky icon banner. + + .. attribute:: castle + + A castle icon banner. + + .. attribute:: world_map + + A world map icon banner. + + .. attribute:: sea_foam + + A sea foam icon banner. + + .. attribute:: warp_tunnel + + A warp tunnel icon banner. + + .. attribute:: house + + A house icon banner. + + .. attribute:: height_map + + A height map icon banner. + + .. attribute:: mesh + + A mesh icon banner. + + .. attribute:: spatter + + A spatter icon banner. + + +.. class:: MemberVerificationFieldType + + Represents a member verification field type. + + .. attribute:: terms + + The field has a set of guidelines the user must agree to. Fields with this type must have + :attr:`PartialMemberVerificationField.values` filled. This field's :attr:`MemberVerificationField.response` + will be a :class:`bool` representing whether the user has accepted or not to the terms. + + .. attribute:: text_input + + The field asks for a short response from the user. This field's :attr:`MemberVerificationField.response` + will be a :class:`str` representing the user input value. + + .. attribute:: paragraph + + The field asks for a long response from the user. This field's :attr:`MemberVerificationField.response` + will be a :class:`str` representing the user input value. + + .. attribute:: multiple_choice + + The user must choose an option from a list. Fields with this type must have + :attr:`PartialMemberVerificationField.choices` filled. This field's :attr:`MemberVerificationField.response` + will be a :class:`int` representing the index of the selected option. + .. _discord-api-audit-logs: Audit Log Data From d86193a3aeb6f264454baaafbea37dd32c52160c Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:14:04 +0100 Subject: [PATCH 11/12] Fix docs and docstrings --- discord/member_verification.py | 14 ++------------ discord/user.py | 2 ++ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/discord/member_verification.py b/discord/member_verification.py index 0bcd6edc10da..543256bd5db9 100644 --- a/discord/member_verification.py +++ b/discord/member_verification.py @@ -66,7 +66,7 @@ class PartialMemberVerificationField: The rules that the user must agree to. Can have up to 16 items, and each one can be up to 300 characters. - Must be passed if ``type`` is :attr:`MemberverificationFieldType.terms`. + Must be passed if ``type`` is :attr:`MemberVerificationFieldType.terms`. required: :class:`bool` Whether this field is required. description: Optional[:class:`str`] @@ -183,21 +183,13 @@ class MemberVerificationField(PartialMemberVerificationField): The type of field. label: :class:`str` The field label. Can be up to 300 characters. - choices: List[:class:`str`] - The choices the user has available. - - Filled when ``type`` is :attr:`MemberVerificationFieldType.multiple_choice`. - values: Optional[List[:class:`str`]] - The rules that the user must agree to. - - Filled when ``type`` is :attr:`MemberVerificationFieldType.terms` response: Union[:class:`str`, :class:`int`, :class:`bool`] The user input on the field. If ``type`` is :attr:`MemberVerificationFieldType.terms` then this should be ``True``. - If ``type`` is :attr:`MemberverificationFieldType.multiple_choice` then this + If ``type`` is :attr:`MemberVerificationFieldType.multiple_choice` then this represents the index of the selected choice. required: :class:`bool` Whether this field is required for a successful application. @@ -205,8 +197,6 @@ class MemberVerificationField(PartialMemberVerificationField): The field description. automations: List[:class:`str`] ... - placeholder: Optional[:class:`str`] - The placeholder text of the field. """ __slots__ = ( diff --git a/discord/user.py b/discord/user.py index 00df93fe8de3..ea9801ba2a70 100644 --- a/discord/user.py +++ b/discord/user.py @@ -525,6 +525,8 @@ class User(BaseUser, discord.abc.Messageable): Specifies if the user is a bot account. system: :class:`bool` Specifies if the user is a system user (i.e. represents Discord officially). + clan: Optional[:class:`UserClan`] + The clan the user belongs to. """ __slots__ = ('__weakref__',) From f0c385fb9e715099c090706ec118ec1ccd6a79a7 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:17:59 +0100 Subject: [PATCH 12/12] Added UserClan to docs --- docs/api.rst | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 6e9aac521497..df9f5a4259df 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -5592,16 +5592,24 @@ PollAnswer .. autoclass:: PollAnswer() :members: -.. _discord_api_data: - MessageSnapshot ~~~~~~~~~~~~~~~~~ .. attributetable:: MessageSnapshot -.. autoclass:: MessageSnapshot +.. autoclass:: MessageSnapshot() :members: +UserClan +~~~~~~~~ + +.. attributetable:: UserClan + +.. autoclass:: UserClan() + :members: + +.. _discord_api_data: + Data Classes --------------