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 .file import File
|
||||||
from .colour import Color, Colour
|
from .colour import Color, Colour
|
||||||
from .invite import Invite, PartialInviteChannel, PartialInviteGuild
|
from .invite import Invite, PartialInviteChannel, PartialInviteGuild
|
||||||
|
from .widget import Widget, WidgetMember, WidgetChannel
|
||||||
from .object import Object
|
from .object import Object
|
||||||
from .reaction import Reaction
|
from .reaction import Reaction
|
||||||
from . import utils, opus, abc
|
from . import utils, opus, abc
|
||||||
|
@ -27,7 +27,6 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
import asyncio
|
import asyncio
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
@ -37,7 +36,7 @@ import websockets
|
|||||||
|
|
||||||
from .user import User, Profile
|
from .user import User, Profile
|
||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
from .object import Object
|
from .widget import Widget
|
||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
from .member import Member
|
from .member import Member
|
||||||
from .errors import *
|
from .errors import *
|
||||||
@ -170,16 +169,6 @@ class Client:
|
|||||||
def _handle_ready(self):
|
def _handle_ready(self):
|
||||||
self._ready.set()
|
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
|
@property
|
||||||
def latency(self):
|
def latency(self):
|
||||||
""":class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
|
""":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.
|
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)
|
data = await self.http.get_invite(invite_id, with_counts=with_counts)
|
||||||
return Invite.from_incomplete(state=self._connection, data=data)
|
return Invite.from_incomplete(state=self._connection, data=data)
|
||||||
|
|
||||||
@ -1018,11 +1007,41 @@ class Client:
|
|||||||
Revoking the invite failed.
|
Revoking the invite failed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
invite_id = self._resolve_invite(invite)
|
invite_id = utils.resolve_invite(invite)
|
||||||
await self.http.delete_invite(invite_id)
|
await self.http.delete_invite(invite_id)
|
||||||
|
|
||||||
# Miscellaneous stuff
|
# 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):
|
async def application_info(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ from .user import User
|
|||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
from .iterators import AuditLogIterator
|
from .iterators import AuditLogIterator
|
||||||
from .webhook import Webhook
|
from .webhook import Webhook
|
||||||
|
from .widget import Widget
|
||||||
|
|
||||||
VALID_ICON_FORMATS = {"jpeg", "jpg", "webp", "png"}
|
VALID_ICON_FORMATS = {"jpeg", "jpg", "webp", "png"}
|
||||||
|
|
||||||
@ -1475,3 +1476,28 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
return AuditLogIterator(self, before=before, after=after, limit=limit,
|
return AuditLogIterator(self, before=before, after=after, limit=limit,
|
||||||
reverse=reverse, user_id=user, action_type=action)
|
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)
|
r = Route('GET', '/guilds/{guild_id}/audit-logs', guild_id=guild_id)
|
||||||
return self.request(r, params=params)
|
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
|
# Invite management
|
||||||
|
|
||||||
def create_invite(self, channel_id, *, reason=None, **options):
|
def create_invite(self, channel_id, *, reason=None, **options):
|
||||||
|
@ -38,6 +38,7 @@ import re
|
|||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from .errors import InvalidArgument
|
from .errors import InvalidArgument
|
||||||
|
from .object import Object
|
||||||
|
|
||||||
DISCORD_EPOCH = 1420070400000
|
DISCORD_EPOCH = 1420070400000
|
||||||
|
|
||||||
@ -340,3 +341,28 @@ def _string_width(string, *, _IS_ASCII=_IS_ASCII):
|
|||||||
for char in string:
|
for char in string:
|
||||||
width += 2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1
|
width += 2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1
|
||||||
return width
|
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