Browse Source

Add support for guild widget

pull/2014/head
NCPlayz 6 years ago
committed by Rapptz
parent
commit
8a30a4cac0
  1. 1
      discord/__init__.py
  2. 47
      discord/client.py
  3. 26
      discord/guild.py
  4. 3
      discord/http.py
  5. 26
      discord/utils.py
  6. 244
      discord/widget.py

1
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

47
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|

26
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)

3
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):

26
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

244
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)
Loading…
Cancel
Save