From 47e42d164839a9bfff76091fe9604399875a8e01 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 2 Sep 2021 22:40:11 +0200 Subject: [PATCH] Merge pull request #42 * implement WelcomeScreen * copy over the kwargs issue. * readable variable names * modernise code * modernise pt2 * Update discord/welcome_screen.py * make pylance not cry from my onions * type http.py * remove extraneous import --- discord/__init__.py | 1 + discord/guild.py | 76 ++++++++++++++ discord/http.py | 15 +++ discord/welcome_screen.py | 216 ++++++++++++++++++++++++++++++++++++++ docs/api.rst | 16 +++ 5 files changed, 324 insertions(+) create mode 100644 discord/welcome_screen.py diff --git a/discord/__init__.py b/discord/__init__.py index 1e74cf91..288da41b 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -40,6 +40,7 @@ from .colour import * from .integrations import * from .invite import * from .template import * +from .welcome_screen import * from .widget import * from .object import * from .reaction import * diff --git a/discord/guild.py b/discord/guild.py index c9132f5a..cb53f44c 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -76,6 +76,7 @@ from .stage_instance import StageInstance from .threads import Thread, ThreadMember from .sticker import GuildSticker from .file import File +from .welcome_screen import WelcomeScreen, WelcomeChannel __all__ = ( @@ -2604,6 +2605,81 @@ class Guild(Hashable): return roles + async def welcome_screen(self) -> WelcomeScreen: + """|coro| + + Returns the guild's welcome screen. + + The guild must have ``COMMUNITY`` in :attr:`~Guild.features`. + + You must have the :attr:`~Permissions.manage_guild` permission to use + this as well. + + .. versionadded:: 2.0 + + Raises + ------- + Forbidden + You do not have the proper permissions to get this. + HTTPException + Retrieving the welcome screen failed. + + Returns + -------- + :class:`WelcomeScreen` + The welcome screen. + """ + data = await self._state.http.get_welcome_screen(self.id) + return WelcomeScreen(data=data, guild=self) + + @overload + async def edit_welcome_screen( + self, + *, + description: Optional[str] = ..., + welcome_channels: Optional[List[WelcomeChannel]] = ..., + enabled: Optional[bool] = ..., + ) -> WelcomeScreen: + ... + + @overload + async def edit_welcome_screen(self) -> None: + ... + + async def edit_welcome_screen(self, **kwargs): + """|coro| + + A shorthand method of :attr:`WelcomeScreen.edit` without needing + to fetch the welcome screen beforehand. + + The guild must have ``COMMUNITY`` in :attr:`~Guild.features`. + + You must have the :attr:`~Permissions.manage_guild` permission to use + this as well. + + .. versionadded:: 2.0 + + Returns + -------- + :class:`WelcomeScreen` + The edited welcome screen. + """ + try: + welcome_channels = kwargs['welcome_channels'] + except KeyError: + pass + else: + welcome_channels_serialised = [] + for wc in welcome_channels: + if not isinstance(wc, WelcomeChannel): + raise InvalidArgument('welcome_channels parameter must be a list of WelcomeChannel') + welcome_channels_serialised.append(wc.to_dict()) + kwargs['welcome_channels'] = welcome_channels_serialised + + if kwargs: + data = await self._state.http.edit_welcome_screen(self.id, kwargs) + return WelcomeScreen(data=data, guild=self) + async def kick(self, user: Snowflake, *, reason: Optional[str] = None) -> None: """|coro| diff --git a/discord/http.py b/discord/http.py index 7a4c2adc..4f86fc87 100644 --- a/discord/http.py +++ b/discord/http.py @@ -84,6 +84,7 @@ if TYPE_CHECKING: threads, voice, sticker, + welcome_screen, ) from .types.snowflake import Snowflake, SnowflakeList @@ -1116,6 +1117,20 @@ class HTTPClient: payload['icon'] = icon return self.request(Route('POST', '/guilds/templates/{code}', code=code), json=payload) + def get_welcome_screen(self, guild_id: Snowflake) -> Response[welcome_screen.WelcomeScreen]: + return self.request(Route('GET', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id)) + + def edit_welcome_screen(self, guild_id: Snowflake, payload: Any) -> Response[welcome_screen.WelcomeScreen]: + valid_keys = ( + 'description', + 'welcome_channels', + 'enabled', + ) + payload = { + k: v for k, v in payload.items() if k in valid_keys + } + return self.request(Route('PATCH', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id), json=payload) + def get_bans(self, guild_id: Snowflake) -> Response[List[guild.Ban]]: return self.request(Route('GET', '/guilds/{guild_id}/bans', guild_id=guild_id)) diff --git a/discord/welcome_screen.py b/discord/welcome_screen.py new file mode 100644 index 00000000..de8c3c27 --- /dev/null +++ b/discord/welcome_screen.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- + +""" +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 Dict, List, Optional, TYPE_CHECKING, Union, overload +from .utils import _get_as_snowflake, get +from .errors import InvalidArgument +from .partial_emoji import _EmojiTag + +__all__ = ( + 'WelcomeChannel', + 'WelcomeScreen', +) + +if TYPE_CHECKING: + from .types.welcome_screen import ( + WelcomeScreen as WelcomeScreenPayload, + WelcomeScreenChannel as WelcomeScreenChannelPayload, + ) + from .abc import Snowflake + from .guild import Guild + from .partial_emoji import PartialEmoji + from .emoji import Emoji + + +class WelcomeChannel: + """Represents a :class:`WelcomeScreen` welcome channel. + + .. versionadded:: 2.0 + + Attributes + ----------- + channel: :class:`abc.Snowflake` + The guild channel that is being referenced. + description: :class:`str` + The description shown of the channel. + emoji: Optional[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`] + The emoji used beside the channel description. + """ + + def __init__(self, *, channel: Snowflake, description: str, emoji: Union[PartialEmoji, Emoji, str] = None): + self.channel = channel + self.description = description + self.emoji = emoji + + def __repr__(self) -> str: + return f'' + + @classmethod + def _from_dict(cls, *, data: WelcomeScreenChannelPayload, guild: Guild) -> WelcomeChannel: + channel_id = _get_as_snowflake(data, 'channel_id') + channel = guild.get_channel(channel_id) + description = data['description'] + _emoji_id = _get_as_snowflake(data, 'emoji_id') + _emoji_name = data['emoji_name'] + + if _emoji_id: + # custom + emoji = get(guild.emojis, id=_emoji_id) + else: + # unicode or None + emoji = _emoji_name + + return cls(channel=channel, description=description, emoji=emoji) # type: ignore + + def to_dict(self) -> WelcomeScreenChannelPayload: + ret: WelcomeScreenChannelPayload = { + 'channel_id': self.channel.id, + 'description': self.description, + 'emoji_id': None, + 'emoji_name': None, + } + + if isinstance(self.emoji, _EmojiTag): + ret['emoji_id'] = self.emoji.id # type: ignore + ret['emoji_name'] = self.emoji.name # type: ignore + else: + # unicode or None + ret['emoji_name'] = self.emoji + + return ret + + +class WelcomeScreen: + """Represents a :class:`Guild` welcome screen. + + .. versionadded:: 2.0 + + Attributes + ----------- + description: :class:`str` + The description shown on the welcome screen. + welcome_channels: List[:class:`WelcomeChannel`] + The channels shown on the welcome screen. + """ + + def __init__(self, *, data: WelcomeScreenPayload, guild: Guild): + self._state = guild._state + self._guild = guild + self._store(data) + + def _store(self, data: WelcomeScreenPayload) -> None: + self.description = data['description'] + welcome_channels = data.get('welcome_channels', []) + self.welcome_channels = [WelcomeChannel._from_dict(data=wc, guild=self._guild) for wc in welcome_channels] + + def __repr__(self) -> str: + return f'' + + @property + def enabled(self) -> bool: + """:class:`bool`: Whether the welcome screen is displayed. + + This is equivalent to checking if ``WELCOME_SCREEN_ENABLED`` + is present in :attr:`Guild.features`. + """ + return 'WELCOME_SCREEN_ENABLED' in self._guild.features + + @overload + async def edit( + self, + *, + description: Optional[str] = ..., + welcome_channels: Optional[List[WelcomeChannel]] = ..., + enabled: Optional[bool] = ..., + ) -> None: + ... + + @overload + async def edit(self) -> None: + ... + + async def edit(self, **kwargs): + """|coro| + + Edit the welcome screen. + + You must have the :attr:`~Permissions.manage_guild` permission in the + guild to do this. + + Usage: :: + + rules_channel = guild.get_channel(12345678) + announcements_channel = guild.get_channel(87654321) + + custom_emoji = utils.get(guild.emojis, name='loudspeaker') + + await welcome_screen.edit( + description='This is a very cool community server!', + welcome_channels=[ + WelcomeChannel(channel=rules_channel, description='Read the rules!', emoji='👨‍🏫'), + WelcomeChannel(channel=announcements_channel, description='Watch out for announcements!', emoji=custom_emoji), + ] + ) + + .. note:: + + Welcome channels can only accept custom emojis if :attr:`~Guild.premium_tier` is level 2 or above. + + Parameters + ------------ + description: Optional[:class:`str`] + The template's description. + welcome_channels: Optional[List[:class:`WelcomeChannel`]] + The welcome channels, in their respective order. + enabled: Optional[:class:`bool`] + Whether the welcome screen should be displayed. + + Raises + ------- + HTTPException + Editing the welcome screen failed failed. + Forbidden + You don't have permissions to edit the welcome screen. + NotFound + This welcome screen does not exist. + """ + try: + welcome_channels = kwargs['welcome_channels'] + except KeyError: + pass + else: + welcome_channels_serialised = [] + for wc in welcome_channels: + if not isinstance(wc, WelcomeChannel): + raise InvalidArgument('welcome_channels parameter must be a list of WelcomeChannel') + welcome_channels_serialised.append(wc.to_dict()) + kwargs['welcome_channels'] = welcome_channels_serialised + + if kwargs: + data = await self._state.http.edit_welcome_screen(self._guild.id, kwargs) + self._store(data) diff --git a/docs/api.rst b/docs/api.rst index 5fc56af1..069dd1d4 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3792,6 +3792,22 @@ Template .. autoclass:: Template() :members: +WelcomeScreen +~~~~~~~~~~~~~~~ + +.. attributetable:: WelcomeScreen + +.. autoclass:: WelcomeScreen() + :members: + +WelcomeChannel +~~~~~~~~~~~~~~~ + +.. attributetable:: WelcomeChannel + +.. autoclass:: WelcomeChannel() + :members: + WidgetChannel ~~~~~~~~~~~~~~~