aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNCPlayz <[email protected]>2019-03-15 19:39:15 +0000
committerRapptz <[email protected]>2019-03-19 09:22:48 -0400
commit8a30a4cac0651df5a24cb744fc104ced90a4cf3a (patch)
treef211cab296d0aa43a6df3aa7f55c3771d34909c9
parentExpose Metadata (diff)
downloaddiscord.py-8a30a4cac0651df5a24cb744fc104ced90a4cf3a.tar.xz
discord.py-8a30a4cac0651df5a24cb744fc104ced90a4cf3a.zip
Add support for guild widget
-rw-r--r--discord/__init__.py1
-rw-r--r--discord/client.py47
-rw-r--r--discord/guild.py26
-rw-r--r--discord/http.py3
-rw-r--r--discord/utils.py26
-rw-r--r--discord/widget.py244
6 files changed, 333 insertions, 14 deletions
diff --git a/discord/__init__.py b/discord/__init__.py
index b4e749c2..dfde548f 100644
--- a/discord/__init__.py
+++ b/discord/__init__.py
@@ -36,6 +36,7 @@ from .role import Role
from .file import File
from .colour import Color, Colour
from .invite import Invite, PartialInviteChannel, PartialInviteGuild
+from .widget import Widget, WidgetMember, WidgetChannel
from .object import Object
from .reaction import Reaction
from . import utils, opus, abc
diff --git a/discord/client.py b/discord/client.py
index 14b3e338..f8ac3b84 100644
--- a/discord/client.py
+++ b/discord/client.py
@@ -27,7 +27,6 @@ DEALINGS IN THE SOFTWARE.
import asyncio
from collections import namedtuple
import logging
-import re
import signal
import sys
import traceback
@@ -37,7 +36,7 @@ import websockets
from .user import User, Profile
from .invite import Invite
-from .object import Object
+from .widget import Widget
from .guild import Guild
from .member import Member
from .errors import *
@@ -170,16 +169,6 @@ class Client:
def _handle_ready(self):
self._ready.set()
- def _resolve_invite(self, invite):
- if isinstance(invite, Invite) or isinstance(invite, Object):
- return invite.id
- else:
- rx = r'(?:https?\:\/\/)?discord(?:\.gg|app\.com\/invite)\/(.+)'
- m = re.match(rx, invite)
- if m:
- return m.group(1)
- return invite
-
@property
def latency(self):
""":class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
@@ -991,7 +980,7 @@ class Client:
The invite from the URL/ID.
"""
- invite_id = self._resolve_invite(url)
+ invite_id = utils.resolve_invite(url)
data = await self.http.get_invite(invite_id, with_counts=with_counts)
return Invite.from_incomplete(state=self._connection, data=data)
@@ -1018,11 +1007,41 @@ class Client:
Revoking the invite failed.
"""
- invite_id = self._resolve_invite(invite)
+ invite_id = utils.resolve_invite(invite)
await self.http.delete_invite(invite_id)
# Miscellaneous stuff
+ async def fetch_widget(self, guild_id):
+ """|coro|
+
+ Gets a :class:`Widget` from a guild ID.
+
+ .. note::
+
+ The guild must have the widget enabled to get this information.
+
+ Parameters
+ -----------
+ guild_id: :class:`int`
+ The ID of the guild.
+
+ Raises
+ -------
+ Forbidden
+ The widget for this guild is disabled.
+ HTTPException
+ Retrieving the widget failed.
+
+ Returns
+ --------
+ :class:`Widget`
+ The guild's widget.
+ """
+ data = await self.http.get_widget(guild_id)
+
+ return Widget(state=self._connection, data=data)
+
async def application_info(self):
"""|coro|
diff --git a/discord/guild.py b/discord/guild.py
index e2d704b3..9e7f1796 100644
--- a/discord/guild.py
+++ b/discord/guild.py
@@ -42,6 +42,7 @@ from .user import User
from .invite import Invite
from .iterators import AuditLogIterator
from .webhook import Webhook
+from .widget import Widget
VALID_ICON_FORMATS = {"jpeg", "jpg", "webp", "png"}
@@ -1475,3 +1476,28 @@ class Guild(Hashable):
return AuditLogIterator(self, before=before, after=after, limit=limit,
reverse=reverse, user_id=user, action_type=action)
+
+ async def widget(self):
+ """|coro|
+
+ Returns the widget of the guild.
+
+ .. note::
+
+ The guild must have the widget enabled to get this information.
+
+ Raises
+ -------
+ Forbidden
+ The widget for this guild is disabled.
+ HTTPException
+ Retrieving the widget failed.
+
+ Returns
+ --------
+ :class:`Widget`
+ The guild's widget.
+ """
+ data = await self._state.http.get_widget(self.id)
+
+ return Widget(state=self._state, data=data)
diff --git a/discord/http.py b/discord/http.py
index 72e9f8ec..44b007bb 100644
--- a/discord/http.py
+++ b/discord/http.py
@@ -659,6 +659,9 @@ class HTTPClient:
r = Route('GET', '/guilds/{guild_id}/audit-logs', guild_id=guild_id)
return self.request(r, params=params)
+ def get_widget(self, guild_id):
+ return self.request(Route('GET', '/guilds/{guild_id}/widget.json', guild_id=guild_id))
+
# Invite management
def create_invite(self, channel_id, *, reason=None, **options):
diff --git a/discord/utils.py b/discord/utils.py
index dc63f1d3..96a26918 100644
--- a/discord/utils.py
+++ b/discord/utils.py
@@ -38,6 +38,7 @@ import re
import warnings
from .errors import InvalidArgument
+from .object import Object
DISCORD_EPOCH = 1420070400000
@@ -340,3 +341,28 @@ def _string_width(string, *, _IS_ASCII=_IS_ASCII):
for char in string:
width += 2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1
return width
+
+def resolve_invite(invite):
+ """
+ Resolves an invite from a :class:`Invite`, URL or ID
+
+ Parameters
+ -----------
+ invite: Union[:class:`Invite`, :class:`Object`, :class:`str`]
+ The invite.
+
+ Returns
+ --------
+ :class:`str`
+ The invite code.
+ """
+ from .invite import Invite # circular import
+ if isinstance(invite, Invite) or isinstance(invite, Object):
+ return invite.id
+ else:
+ rx = r'(?:https?\:\/\/)?discord(?:\.gg|app\.com\/invite)\/(.+)'
+ m = re.match(rx, invite)
+ if m:
+ return m.group(1)
+ return invite
+
diff --git a/discord/widget.py b/discord/widget.py
new file mode 100644
index 00000000..7bb4f0a2
--- /dev/null
+++ b/discord/widget.py
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+
+"""
+The MIT License (MIT)
+
+Copyright (c) 2015-2019 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 snowflake_time, _get_as_snowflake, resolve_invite
+from .user import BaseUser
+from .activity import Activity
+from .invite import Invite
+from .enums import Status, try_enum
+from collections import namedtuple
+
+VALID_ICON_FORMATS = {"jpeg", "jpg", "webp", "png"}
+
+class WidgetChannel(namedtuple('WidgetChannel', 'id name position')):
+ """Represents a "partial" widget channel.
+
+ .. container:: operations
+
+ .. describe:: x == y
+
+ Checks if two partial channels are the same.
+
+ .. describe:: x != y
+
+ Checks if two partial channels are not the same.
+
+ .. describe:: hash(x)
+
+ Return the partial channel's hash.
+
+ .. describe:: str(x)
+
+ Returns the partial channel's name.
+
+ Attributes
+ -----------
+ id: :class:`int`
+ The channel's ID.
+ name: :class:`str`
+ The channel's name.
+ position: :class:`int`
+ The channel's position
+ """
+ __slots__ = ()
+
+ def __str__(self):
+ return self.name
+
+ @property
+ def mention(self):
+ """:class:`str`: The string that allows you to mention the channel."""
+ return '<#%s>' % self.id
+
+ @property
+ def created_at(self):
+ """Returns the channel's creation time in UTC."""
+ return snowflake_time(self.id)
+
+class WidgetMember(BaseUser):
+ """Represents a "partial" member of the widget's guild.
+
+ .. container:: operations
+
+ .. describe:: x == y
+
+ Checks if two widget members are the same.
+
+ .. describe:: x != y
+
+ Checks if two widget members are not the same.
+
+ .. describe:: hash(x)
+
+ Return the widget member's hash.
+
+ .. describe:: str(x)
+
+ Returns the widget member's `name#discriminator`.
+
+ Attributes
+ -----------
+ id: :class:`int`
+ The member's ID.
+ name: :class:`str`
+ The member's username.
+ discriminator: :class:`str`
+ The member's discriminator.
+ bot: :class:`bool`
+ Whether the member is a bot.
+ status: :class:`Status`
+ The member's status.
+ nick: Optional[:class:`str`]
+ The member's nickname.
+ avatar: Optional[:class:`str`]
+ The member's avatar hash.
+ activity: Optional[:class:`Activity`]
+ The member's activity.
+ deafened: Optional[:class:`bool`]
+ Whether the member is currently deafened.
+ muted: Optional[:class:`bool`]
+ Whether the member is currently muted.
+ suppress: Optional[:class:`bool`]
+ Whether the member is currently being suppressed.
+ connected_channel: Optional[:class:`VoiceChannel`]
+ Which channel the member is connected to.
+ """
+ __slots__ = ('name', 'status', 'nick', 'avatar', 'discriminator',
+ 'id', 'bot', 'activity', 'deafened', 'suppress', 'muted',
+ 'connected_channel')
+
+ def __init__(self, *, state, data, connected_channel=None):
+ super().__init__(state=state, data=data)
+ self.nick = data.get('nick')
+ self.status = try_enum(Status, data.get('status'))
+ self.deafened = data.get('deaf', False) or data.get('self_deaf', False)
+ self.muted = data.get('mute', False) or data.get('self_mute', False)
+ self.suppress = data.get('suppress', False)
+
+ game = data.get('game')
+ if game:
+ self.activity = Activity(**game)
+
+ self.connected_channel = connected_channel
+
+ @property
+ def display_name(self):
+ """:class:`str`: Returns the member's display name."""
+ return self.nick if self.nick else self.name
+
+class Widget:
+ """Represents a :class:`Guild` widget.
+
+ .. container:: operations
+
+ .. describe:: x == y
+
+ Checks if two widgets are the same.
+
+ .. describe:: x != y
+
+ Checks if two widgets are not the same.
+
+ .. describe:: str(x)
+
+ Returns the widget's JSON URL.
+
+ Attributes
+ -----------
+ id: :class:`int`
+ The guild's ID.
+ name: :class:`str`
+ The guild's name.
+ channels: Optional[List[:class:`WidgetChannel`]]
+ The accessible voice channels in the guild.
+ members: Optional[List[:class:`Member`]]
+ The online members in the server. Offline members
+ do not appear in the widget.
+ """
+ __slots__ = ('_state', 'channels', '_invite', 'id', 'members', 'name')
+
+ def __init__(self, *, state, data):
+ self._state = state
+ self._invite = data['instant_invite']
+ self.name = data['name']
+ self.id = int(data['id'])
+
+ self.channels = []
+ for channel in data.get('channels', []):
+ _id = int(channel['id'])
+ self.channels.append(WidgetChannel(id=_id, name=channel['name'], position=channel['position']))
+
+ self.members = []
+ channels = {channel.id: channel for channel in self.channels}
+ for member in data.get('members', []):
+ connected_channel = _get_as_snowflake(member, 'channel_id')
+ if connected_channel:
+ connected_channel = channels[connected_channel]
+
+ self.members.append(WidgetMember(state=self._state, data=member, connected_channel=connected_channel))
+
+ def __str__(self):
+ return self.json_url
+
+ def __eq__(self, other):
+ return self.id == other.id
+
+ def __repr__(self):
+ return '<Widget id={0.id} name={0.name!r} invite={0.invite!r}>'.format(self)
+
+ @property
+ def created_at(self):
+ """Returns the member's creation time in UTC."""
+ return snowflake_time(self.id)
+
+ @property
+ def json_url(self):
+ """The JSON URL of the widget."""
+ return "https://discordapp.com/api/guilds/{0.id}/widget.json".format(self)
+
+ async def fetch_invite(self, *, with_counts=True):
+ """|coro|
+
+ Retrieves an :class:`Invite` from a invite URL or ID.
+ This is the same as :meth:`Client.get_invite`; the invite
+ code is abstracted away.
+
+ Parameters
+ -----------
+ with_counts: :class:`bool`
+ Whether to include count information in the invite. This fills the
+ :attr:`Invite.approximate_member_count` and :attr:`Invite.approximate_presence_count`
+ fields.
+
+ Returns
+ --------
+ :class:`Invite`
+ The invite from the URL/ID.
+ """
+ if self._invite:
+ invite_id = resolve_invite(self._invite)
+ data = await self._state.http.get_invite(invite_id, with_counts=with_counts)
+ return Invite.from_incomplete(state=self._state, data=data)