From 2b3c6e0d4776fae080191e605c2caf89fa132e1b Mon Sep 17 00:00:00 2001 From: Nadir Chowdhury Date: Sun, 28 Jun 2020 19:48:04 +0100 Subject: [PATCH] Add support for Discord templates --- discord/__init__.py | 1 + discord/client.py | 38 +++++++++++- discord/http.py | 11 ++++ discord/template.py | 140 ++++++++++++++++++++++++++++++++++++++++++++ discord/utils.py | 11 ++++ docs/api.rst | 6 ++ 6 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 discord/template.py diff --git a/discord/__init__.py b/discord/__init__.py index f4846d455..589a1629b 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -40,6 +40,7 @@ from .role import Role from .file import File from .colour import Color, Colour from .invite import Invite, PartialInviteChannel, PartialInviteGuild +from .template import Template from .widget import Widget, WidgetMember, WidgetChannel from .object import Object from .reaction import Reaction diff --git a/discord/client.py b/discord/client.py index 88cd09a1e..b5ab12a95 100644 --- a/discord/client.py +++ b/discord/client.py @@ -37,6 +37,7 @@ import websockets from .user import User, Profile from .asset import Asset from .invite import Invite +from .template import Template from .widget import Widget from .guild import Guild from .channel import _channel_factory @@ -1019,6 +1020,32 @@ class Client: """ return GuildIterator(self, limit=limit, before=before, after=after) + async def fetch_template(self, code): + """|coro| + + Gets a :class:`.Template` from a discord.new URL or code. + + Parameters + ----------- + code: :class:`str` + The Discord Template Code or URL (must be a discord.new URL). + + Raises + ------- + :exc:`.NotFound` + The template is invalid. + :exc:`.HTTPException` + Getting the template failed. + + Returns + -------- + :class:`.Template` + The template from the URL/code. + """ + code = utils.resolve_template(code) + data = await self.http.get_template(code) + return Template(data=data, state=self._connection) + async def fetch_guild(self, guild_id): """|coro| @@ -1053,7 +1080,7 @@ class Client: data = await self.http.get_guild(guild_id) return Guild(data=data, state=self._connection) - async def create_guild(self, name, region=None, icon=None): + async def create_guild(self, name, region=None, icon=None, *, code=None): """|coro| Creates a :class:`.Guild`. @@ -1070,6 +1097,10 @@ class Client: icon: :class:`bytes` The :term:`py:bytes-like object` representing the icon. See :meth:`.ClientUser.edit` for more details on what is expected. + code: Optional[:class:`str`] + The code for a template to create the guild with. + + .. versionadded:: 1.4 Raises ------ @@ -1092,7 +1123,10 @@ class Client: else: region = region.value - data = await self.http.create_guild(name, region, icon) + if code: + data = await self.http.create_from_template(code, name, region, icon) + else: + data = await self.http.create_guild(name, region, icon) return Guild(data=data, state=self._connection) # Invite management diff --git a/discord/http.py b/discord/http.py index dba174b61..c5650c24d 100644 --- a/discord/http.py +++ b/discord/http.py @@ -630,6 +630,17 @@ class HTTPClient: return self.request(Route('PATCH', '/guilds/{guild_id}', guild_id=guild_id), json=payload, reason=reason) + def get_template(self, code): + return self.request(Route('GET', '/guilds/templates/{code}', code=code)) + + def create_from_template(self, code, name, region, icon): + payload = { + 'name': name, + 'icon': icon, + 'region': region + } + return self.request(Route('POST', '/guilds/templates/{code}', code=code), json=payload) + def get_bans(self, guild_id): return self.request(Route('GET', '/guilds/{guild_id}/bans', guild_id=guild_id)) diff --git a/discord/template.py b/discord/template.py new file mode 100644 index 000000000..905f5515e --- /dev/null +++ b/discord/template.py @@ -0,0 +1,140 @@ +from .utils import parse_time, _get_as_snowflake +from .enums import VoiceRegion +from .guild import Guild + +__all__ = ( + 'Template' +) + +class _FriendlyHttpAttributeErrorHelper: + __slots__ = () + + def __getattr__(self, attr): + raise AttributeError('PartialTemplateState does not support http methods.') + +class _PartialTemplateState: + def __init__(self, *, state): + self.__state = state + self.http = _FriendlyHttpAttributeErrorHelper() + + @property + def is_bot(self): + return self.__state.is_bot + + @property + def shard_count(self): + return self.__state.shard_count + + @property + def user(self): + return self.__state.user + + @property + def self_id(self): + return self.__state.user.id + + def store_emoji(self, guild, packet): + return None + + def _get_voice_client(self, id): + return None + + def _get_message(self, id): + return None + + async def query_members(self, **kwargs): + return [] + + def __getattr__(self, attr): + raise AttributeError('PartialTemplateState does not support {0!r}.'.format(attr)) + +class Template: + """Represents a Discord template. + + .. versionadded:: 1.4 + + Attributes + ----------- + code: :code:`str` + The template code. + uses: :class:`int` + How many time the template has been used. + name: :class:`str` + The name of the template. + description: :class:`str` + The description of the template. + creator: :class:`User` + The creator of the template. + created_at: :class:`datetime.datetime` + When the template was created. + updated_at: :class:`datetime.datetime` + When the template was last updated (referred to as "last synced" in the client). + source_guild: :class:`TemplateGuild` + The source guild. + """ + + def __init__(self, *, state, data): + self._state = state + + self.code = data['code'] + self.uses = data['usage_count'] + self.name = data['name'] + self.description = data['description'] + creator_data = data.get('creator') + self.creator = None if creator_data is None else self._state.store_user(creator_data) + + self.created_at = parse_time(data.get('created_at')) + self.updated_at = parse_time(data.get('updated_at')) + + id = _get_as_snowflake(data, 'source_guild_id') + source_serialised = data['serialized_source_guild'] + source_serialised['id'] = id + state = _PartialTemplateState(state=self._state) + + self.source_guild = Guild(data=source_serialised, state=state) + + def __repr__(self): + return '