From 27ed72369a74e6a986de30fd5b6ed9244736cc1c Mon Sep 17 00:00:00 2001 From: NCPlayz Date: Wed, 24 Feb 2021 22:25:32 +0000 Subject: [PATCH 1/9] implement WelcomeScreen --- discord/__init__.py | 1 + discord/guild.py | 61 +++++++++++++ discord/http.py | 14 +++ discord/welcome_screen.py | 181 ++++++++++++++++++++++++++++++++++++++ docs/api.rst | 16 ++++ 5 files changed, 273 insertions(+) create mode 100644 discord/welcome_screen.py diff --git a/discord/__init__.py b/discord/__init__.py index 1e74cf91..7711a5af 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -44,6 +44,7 @@ from .widget import * from .object import * from .reaction import * from . import utils, opus, abc, ui +from .welcome_screen import WelcomeScreen, WelcomeChannel from .enums import * from .embeds import * from .mentions import * diff --git a/discord/guild.py b/discord/guild.py index 4ed89821..84b98faa 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__ = ( @@ -2574,6 +2575,66 @@ class Guild(Hashable): return roles + async def welcome_screen(self): + """|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:: 1.7 + + 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) + + 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:: 1.7 + + 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()) + + 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..30a806be 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1116,6 +1116,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): + return self.request(Route('GET', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id)) + + def edit_welcome_screen(self, guild_id, payload): + 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..12c936dc --- /dev/null +++ b/discord/welcome_screen.py @@ -0,0 +1,181 @@ +# -*- 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 .utils import _get_as_snowflake, get +from .errors import InvalidArgument +from .partial_emoji import _EmojiTag + + +class WelcomeChannel: + """Represents a :class:`WelcomeScreen` welcome channel. + + .. versionadded:: 1.7 + + 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, description, emoji=None): + self.channel = channel + self.description = description + self.emoji = emoji + + def __repr__(self): + return ''.format(self) + + @classmethod + def _from_dict(cls, *, data, guild): + 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) + + def to_dict(self): + ret = { + '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 + ret['emoji_name'] = self.emoji.name + else: + # unicode or None + ret['emoji_name'] = self.emoji + + return ret + +class WelcomeScreen: + """Represents a :class:`Guild` welcome screen. + + .. versionadded:: 1.7 + + 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, guild): + self._state = guild._state + self._guild = guild + self._store(data) + + def _store(self, data): + 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): + return ''.format(self) + + @property + def enabled(self): + """: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 + + 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: :: + + channel_1 = guild.get_channel(12345678) + channel_2 = 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=channel_one, description='Read the rules!', emoji='👨‍🏫'), + WelcomeChannel(channel=channel_two, 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 0a9ba5cc..c6c91720 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3781,6 +3781,22 @@ Template .. autoclass:: Template() :members: +WelcomeScreen +~~~~~~~~~~~~~~~ + +.. attributetable:: WelcomeScreen + +.. autoclass:: WelcomeScreen() + :members: + +WelcomeChannel +~~~~~~~~~~~~~~~ + +.. attributetable:: WelcomeChannel + +.. autoclass:: WelcomeChannel() + :members: + WidgetChannel ~~~~~~~~~~~~~~~ -- 2.47.2 From 71b8f315070c9d03f1d63555c04aad0f26ec16fc Mon Sep 17 00:00:00 2001 From: NCPlayz Date: Wed, 24 Feb 2021 22:30:12 +0000 Subject: [PATCH 2/9] copy over the kwargs issue. --- discord/guild.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/guild.py b/discord/guild.py index 84b98faa..c2484784 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -2630,6 +2630,7 @@ class Guild(Hashable): 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) -- 2.47.2 From c3fb10348a17f6d4c94847be7eef3b1fc2aa2469 Mon Sep 17 00:00:00 2001 From: NCPlayz Date: Wed, 24 Feb 2021 22:39:27 +0000 Subject: [PATCH 3/9] readable variable names --- discord/welcome_screen.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/welcome_screen.py b/discord/welcome_screen.py index 12c936dc..c387be44 100644 --- a/discord/welcome_screen.py +++ b/discord/welcome_screen.py @@ -129,16 +129,16 @@ class WelcomeScreen: Usage: :: - channel_1 = guild.get_channel(12345678) - channel_2 = guild.get_channel(87654321) + 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=channel_one, description='Read the rules!', emoji='👨‍🏫'), - WelcomeChannel(channel=channel_two, description='Watch out for announcements!', emoji=custom_emoji), + WelcomeChannel(channel=rules_channel, description='Read the rules!', emoji='👨‍🏫'), + WelcomeChannel(channel=announcements_channel, description='Watch out for announcements!', emoji=custom_emoji), ] ) -- 2.47.2 From b654530821e0d75b30d4db9fb7b5a6bf6f1b875e Mon Sep 17 00:00:00 2001 From: NCPlayz Date: Sun, 18 Apr 2021 22:37:42 +0100 Subject: [PATCH 4/9] modernise code --- discord/__init__.py | 1 + discord/welcome_screen.py | 83 ++++++++++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 7711a5af..2095ac36 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/welcome_screen.py b/discord/welcome_screen.py index c387be44..fd0288b8 100644 --- a/discord/welcome_screen.py +++ b/discord/welcome_screen.py @@ -24,15 +24,33 @@ 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:: 1.7 + .. versionadded:: 2.0 Attributes ----------- @@ -43,20 +61,21 @@ class WelcomeChannel: emoji: Optional[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`] The emoji used beside the channel description. """ - def __init__(self, *, channel, description, emoji=None): + + 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): - return ''.format(self) + + def __repr__(self) -> str: + return f'' @classmethod - def _from_dict(cls, *, data, guild): + 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_id = _get_as_snowflake(data, 'emoji_id') _emoji_name = data['emoji_name'] if _emoji_id: @@ -66,9 +85,9 @@ class WelcomeChannel: # unicode or None emoji = _emoji_name - return cls(channel=channel, description=description, emoji=emoji) + return cls(channel=channel, description=description, emoji=emoji) # type: ignore - def to_dict(self): + def to_dict(self) -> Dict[str, str]: ret = { 'channel_id': self.channel.id, 'description': self.description, @@ -77,18 +96,19 @@ class WelcomeChannel: } if isinstance(self.emoji, _EmojiTag): - ret['emoji_id'] = self.emoji.id - ret['emoji_name'] = self.emoji.name + 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:: 1.7 + .. versionadded:: 2.0 Attributes ----------- @@ -97,33 +117,48 @@ class WelcomeScreen: welcome_channels: List[:class:`WelcomeChannel`] The channels shown on the welcome screen. """ - def __init__(self, *, data, guild): + + def __init__(self, *, data: WelcomeScreenPayload, guild: Guild): self._state = guild._state self._guild = guild self._store(data) - - def _store(self, 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): - return ''.format(self) + def __repr__(self) -> str: + return f'' @property - def enabled(self): + 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. @@ -138,14 +173,14 @@ class WelcomeScreen: 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), + 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`] -- 2.47.2 From 8be28fdb964a5ced0af307a30e9cf1015ed33884 Mon Sep 17 00:00:00 2001 From: NCPlayz Date: Sun, 18 Apr 2021 22:43:53 +0100 Subject: [PATCH 5/9] modernise pt2 --- discord/guild.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index c2484784..3fbf718c 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -2575,7 +2575,7 @@ class Guild(Hashable): return roles - async def welcome_screen(self): + async def welcome_screen(self) -> WelcomeScreen: """|coro| Returns the guild's welcome screen. @@ -2585,7 +2585,7 @@ class Guild(Hashable): You must have the :attr:`~Permissions.manage_guild` permission to use this as well. - .. versionadded:: 1.7 + .. versionadded:: 2.0 Raises ------- @@ -2602,6 +2602,20 @@ class Guild(Hashable): 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| @@ -2613,7 +2627,7 @@ class Guild(Hashable): You must have the :attr:`~Permissions.manage_guild` permission to use this as well. - .. versionadded:: 1.7 + .. versionadded:: 2.0 Returns -------- -- 2.47.2 From bf43cff621244d8ca98ec0653ebb5a1719b50395 Mon Sep 17 00:00:00 2001 From: Nadir Chowdhury Date: Mon, 19 Apr 2021 01:56:57 +0100 Subject: [PATCH 6/9] Update discord/welcome_screen.py --- discord/welcome_screen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/welcome_screen.py b/discord/welcome_screen.py index fd0288b8..0a7e3cd2 100644 --- a/discord/welcome_screen.py +++ b/discord/welcome_screen.py @@ -87,7 +87,7 @@ class WelcomeChannel: return cls(channel=channel, description=description, emoji=emoji) # type: ignore - def to_dict(self) -> Dict[str, str]: + def to_dict(self) -> WelcomeScreenChannelPayload: ret = { 'channel_id': self.channel.id, 'description': self.description, -- 2.47.2 From 24855484e1b25b33e214c71609b46ab9a4a9c4da Mon Sep 17 00:00:00 2001 From: NCPlayz Date: Mon, 19 Apr 2021 16:34:52 +0100 Subject: [PATCH 7/9] make pylance not cry from my onions --- discord/welcome_screen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/welcome_screen.py b/discord/welcome_screen.py index 0a7e3cd2..de8c3c27 100644 --- a/discord/welcome_screen.py +++ b/discord/welcome_screen.py @@ -88,7 +88,7 @@ class WelcomeChannel: return cls(channel=channel, description=description, emoji=emoji) # type: ignore def to_dict(self) -> WelcomeScreenChannelPayload: - ret = { + ret: WelcomeScreenChannelPayload = { 'channel_id': self.channel.id, 'description': self.description, 'emoji_id': None, -- 2.47.2 From 45083c6ad54a04ea987bb97c14becf47b2552009 Mon Sep 17 00:00:00 2001 From: NCPlayz Date: Thu, 17 Jun 2021 00:02:55 +0100 Subject: [PATCH 8/9] type http.py --- discord/http.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/http.py b/discord/http.py index 30a806be..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,10 +1117,10 @@ class HTTPClient: payload['icon'] = icon return self.request(Route('POST', '/guilds/templates/{code}', code=code), json=payload) - def get_welcome_screen(self, guild_id): + 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, payload): + def edit_welcome_screen(self, guild_id: Snowflake, payload: Any) -> Response[welcome_screen.WelcomeScreen]: valid_keys = ( 'description', 'welcome_channels', -- 2.47.2 From 8d756df65120a9c608c5f53bc91adcb766dcad4a Mon Sep 17 00:00:00 2001 From: NCPlayz Date: Tue, 10 Aug 2021 13:59:50 +0100 Subject: [PATCH 9/9] remove extraneous import --- discord/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/discord/__init__.py b/discord/__init__.py index 2095ac36..288da41b 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -45,7 +45,6 @@ from .widget import * from .object import * from .reaction import * from . import utils, opus, abc, ui -from .welcome_screen import WelcomeScreen, WelcomeChannel from .enums import * from .embeds import * from .mentions import * -- 2.47.2