Add support for guild widget
This commit is contained in:
parent
f507f508a2
commit
8a30a4cac0
@ -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
|
||||
|
@ -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|
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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
Normal file
244
discord/widget.py
Normal file
@ -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…
x
Reference in New Issue
Block a user