Browse Source

Expose more information from partial invites, along with counts.

This adds the following information.

* `PartialInviteGuild` to replace `Object` patching
* `PartialInviteChannel` to replace `Object` patching
* Invite.approximate_member_count and Invite.approximate_presence_count

The new partial objects provide better documentation on what is
expected when you fetch random invites.

Fixes #1830
pull/1896/head
Rapptz 6 years ago
parent
commit
5d78f43e55
  1. 2
      discord/__init__.py
  2. 14
      discord/client.py
  3. 10
      discord/guild.py
  4. 7
      discord/http.py
  5. 170
      discord/invite.py
  6. 11
      docs/api.rst

2
discord/__init__.py

@ -35,7 +35,7 @@ from .permissions import Permissions, PermissionOverwrite
from .role import Role 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 from .invite import Invite, PartialInviteChannel, PartialInviteGuild
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

14
discord/client.py

@ -882,7 +882,7 @@ class Client:
# Invite management # Invite management
async def get_invite(self, url): async def get_invite(self, url, *, with_counts=True):
"""|coro| """|coro|
Gets an :class:`Invite` from a discord.gg URL or ID. Gets an :class:`Invite` from a discord.gg URL or ID.
@ -890,13 +890,17 @@ class Client:
Note Note
------ ------
If the invite is for a guild you have not joined, the guild and channel If the invite is for a guild you have not joined, the guild and channel
attributes of the returned invite will be :class:`Object` with the names attributes of the returned :class:`Invite` will be :class:`PartialInviteGuild` and
patched in. :class:`PartialInviteChannel` respectively.
Parameters Parameters
----------- -----------
url : str url: :class:`str`
The discord invite ID or URL (must be a discord.gg URL). The discord invite ID or URL (must be a discord.gg URL).
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.
Raises Raises
------- -------
@ -912,7 +916,7 @@ class Client:
""" """
invite_id = self._resolve_invite(url) invite_id = self._resolve_invite(url)
data = await self.http.get_invite(invite_id) 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)
async def delete_invite(self, invite): async def delete_invite(self, invite):

10
discord/guild.py

@ -83,7 +83,7 @@ class Guild(Hashable):
The timeout to get sent to the AFK channel. The timeout to get sent to the AFK channel.
afk_channel: Optional[:class:`VoiceChannel`] afk_channel: Optional[:class:`VoiceChannel`]
The channel that denotes the AFK channel. None if it doesn't exist. The channel that denotes the AFK channel. None if it doesn't exist.
icon: :class:`str` icon: Optional[:class:`str`]
The guild's icon. The guild's icon.
id: :class:`int` id: :class:`int`
The guild's ID. The guild's ID.
@ -114,7 +114,7 @@ class Guild(Hashable):
- ``VERIFIED``: Guild is a "verified" server. - ``VERIFIED``: Guild is a "verified" server.
- ``MORE_EMOJI``: Guild is allowed to have more than 50 custom emoji. - ``MORE_EMOJI``: Guild is allowed to have more than 50 custom emoji.
splash: :class:`str` splash: Optional[:class:`str`]
The guild's invite splash. The guild's invite splash.
""" """
@ -601,7 +601,7 @@ class Guild(Hashable):
channel upon creation. This parameter expects a :class:`dict` of channel upon creation. This parameter expects a :class:`dict` of
overwrites with the target (either a :class:`Member` or a :class:`Role`) overwrites with the target (either a :class:`Member` or a :class:`Role`)
as the key and a :class:`PermissionOverwrite` as the value. as the key and a :class:`PermissionOverwrite` as the value.
Note Note
-------- --------
Creating a channel of a specified position will not update the position of Creating a channel of a specified position will not update the position of
@ -641,7 +641,7 @@ class Guild(Hashable):
The permissions will be automatically synced to category if no The permissions will be automatically synced to category if no
overwrites are provided. overwrites are provided.
position: :class:`int` position: :class:`int`
The position in the channel list. This is a number that starts The position in the channel list. This is a number that starts
at 0. e.g. the top channel is position 0. at 0. e.g. the top channel is position 0.
topic: Optional[:class:`str`] topic: Optional[:class:`str`]
The new channel's topic. The new channel's topic.
@ -679,7 +679,7 @@ class Guild(Hashable):
This is similar to :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead, in addition This is similar to :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead, in addition
to having the following new parameters. to having the following new parameters.
Parameters Parameters
----------- -----------
bitrate: :class:`int` bitrate: :class:`int`

7
discord/http.py

@ -647,8 +647,11 @@ class HTTPClient:
return self.request(r, reason=reason, json=payload) return self.request(r, reason=reason, json=payload)
def get_invite(self, invite_id): def get_invite(self, invite_id, *, with_counts=True):
return self.request(Route('GET', '/invite/{invite_id}', invite_id=invite_id)) params = {
'with_counts': int(with_counts)
}
return self.request(Route('GET', '/invite/{invite_id}', invite_id=invite_id), params=params)
def invites_from(self, guild_id): def invites_from(self, guild_id):
return self.request(Route('GET', '/guilds/{guild_id}/invites', guild_id=guild_id)) return self.request(Route('GET', '/guilds/{guild_id}/invites', guild_id=guild_id))

170
discord/invite.py

@ -27,6 +27,141 @@ DEALINGS IN THE SOFTWARE.
from .utils import parse_time from .utils import parse_time
from .mixins import Hashable from .mixins import Hashable
from .object import Object from .object import Object
from .enums import ChannelType, VerificationLevel, try_enum
from collections import namedtuple
class PartialInviteChannel(namedtuple('PartialInviteChannel', 'id name type')):
"""Represents a "partial" invite channel.
This model will be given when the user is not part of the
guild the :class:`Invite` resolves to.
.. 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
-----------
name: :class:`str`
The partial channel's name.
id: :class:`int`
The partial channel's ID.
type: :class:`ChannelType`
The partial channel's type.
"""
__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 utils.snowflake_time(self.id)
class PartialInviteGuild(namedtuple('PartialInviteGuild', 'features icon id name splash verification_level')):
"""Represents a "partial" invite guild.
This model will be given when the user is not part of the
guild the :class:`Invite` resolves to.
.. container:: operations
.. describe:: x == y
Checks if two partial guilds are the same.
.. describe:: x != y
Checks if two partial guilds are not the same.
.. describe:: hash(x)
Return the partial guild's hash.
.. describe:: str(x)
Returns the partial guild's name.
Attributes
-----------
name: :class:`str`
The partial guild's name.
id: :class:`int`
The partial guild's ID.
verification_level: :class:`VerificationLevel`
The partial guild's verification level.
features: List[:class:`str`]
A list of features the guild has. See :attr:`Guild.features` for more information.
icon: Optional[:class:`str`]
The partial guild's icon.
splash: Optional[:class:`str`]
The partial guild's invite splash.
"""
__slots__ = ()
def __str__(self):
return self.name
@property
def created_at(self):
"""Returns the guild's creation time in UTC."""
return utils.snowflake_time(self.id)
@property
def icon_url(self):
"""Returns the URL version of the guild's icon. Returns an empty string if it has no icon."""
return self.icon_url_as()
def icon_url_as(self, *, format='webp', size=1024):
""":class:`str`: The same operation as :meth:`Guild.icon_url_as`."""
if not valid_icon_size(size):
raise InvalidArgument("size must be a power of 2 between 16 and 2048")
if format not in VALID_ICON_FORMATS:
raise InvalidArgument("format must be one of {}".format(VALID_ICON_FORMATS))
if self.icon is None:
return ''
return 'https://cdn.discordapp.com/icons/{0.id}/{0.icon}.{1}?size={2}'.format(self, format, size)
@property
def splash_url(self):
"""Returns the URL version of the guild's invite splash. Returns an empty string if it has no splash."""
return self.splash_url_as()
def splash_url_as(self, *, format='webp', size=2048):
""":class:`str`: The same operation as :meth:`Guild.splash_url_as`."""
if not valid_icon_size(size):
raise InvalidArgument("size must be a power of 2 between 16 and 2048")
if format not in VALID_ICON_FORMATS:
raise InvalidArgument("format must be one of {}".format(VALID_ICON_FORMATS))
if self.splash is None:
return ''
return 'https://cdn.discordapp.com/splashes/{0.id}/{0.splash}.{1}?size={2}'.format(self, format, size)
class Invite(Hashable): class Invite(Hashable):
"""Represents a Discord :class:`Guild` or :class:`abc.GuildChannel` invite. """Represents a Discord :class:`Guild` or :class:`abc.GuildChannel` invite.
@ -58,7 +193,7 @@ class Invite(Hashable):
How long the before the invite expires in seconds. A value of 0 indicates that it doesn't expire. How long the before the invite expires in seconds. A value of 0 indicates that it doesn't expire.
code: :class:`str` code: :class:`str`
The URL fragment used for the invite. The URL fragment used for the invite.
guild: :class:`Guild` guild: Union[:class:`Guild`, :class:`PartialInviteGuild`]
The guild the invite is for. The guild the invite is for.
revoked: :class:`bool` revoked: :class:`bool`
Indicates if the invite has been revoked. Indicates if the invite has been revoked.
@ -73,13 +208,19 @@ class Invite(Hashable):
How many times the invite can be used. How many times the invite can be used.
inviter: :class:`User` inviter: :class:`User`
The user who created the invite. The user who created the invite.
channel: :class:`abc.GuildChannel` approximate_member_count: Optional[:class:`int`]
The approximate number of members in the guild.
approximate_presence_count: Optional[:class:`int`]
The approximate number of members currently active in the guild.
This includes idle, dnd, online, and invisible members. Offline members are excluded.
channel: Union[:class:`abc.GuildChannel`, :class:`PartialInviteChannel`]
The channel the invite is for. The channel the invite is for.
""" """
__slots__ = ('max_age', 'code', 'guild', 'revoked', 'created_at', 'uses', __slots__ = ('max_age', 'code', 'guild', 'revoked', 'created_at', 'uses',
'temporary', 'max_uses', 'inviter', 'channel', '_state') 'temporary', 'max_uses', 'inviter', 'channel', '_state',
'approximate_member_count', 'approximate_presence_count' )
def __init__(self, *, state, data): def __init__(self, *, state, data):
self._state = state self._state = state
@ -91,6 +232,8 @@ class Invite(Hashable):
self.temporary = data.get('temporary') self.temporary = data.get('temporary')
self.uses = data.get('uses') self.uses = data.get('uses')
self.max_uses = data.get('max_uses') self.max_uses = data.get('max_uses')
self.approximate_presence_count = data.get('approximate_presence_count')
self.approximate_member_count = data.get('approximate_member_count')
inviter_data = data.get('inviter') inviter_data = data.get('inviter')
self.inviter = None if inviter_data is None else self._state.store_user(inviter_data) self.inviter = None if inviter_data is None else self._state.store_user(inviter_data)
@ -104,17 +247,16 @@ class Invite(Hashable):
if guild is not None: if guild is not None:
channel = guild.get_channel(channel_id) channel = guild.get_channel(channel_id)
else: else:
guild = Object(id=guild_id) channel_data = data['channel']
channel = Object(id=channel_id) guild_data = data['guild']
guild.name = data['guild']['name'] channel_type = try_enum(ChannelType, channel_data['type'])
channel = PartialInviteChannel(id=channel_id, name=channel_data['name'], type=channel_type)
guild.splash = data['guild']['splash'] guild = PartialInviteGuild(id=guild_id,
guild.splash_url = '' name=guild_data['name'],
if guild.splash: features=guild_data.get('features', []),
guild.splash_url = 'https://cdn.discordapp.com/splashes/{0.id}/{0.splash}.jpg?size=2048'.format(guild) icon=guild_data.get('icon'),
splash=guild_data.get('splash'),
channel.name = data['channel']['name'] verification_level=try_enum(VerificationLevel, guild_data.get('verification_level')))
data['guild'] = guild data['guild'] = guild
data['channel'] = channel data['channel'] = channel
return cls(state=state, data=data) return cls(state=state, data=data)

11
docs/api.rst

@ -2035,6 +2035,17 @@ GroupChannel
.. autocomethod:: typing .. autocomethod:: typing
:async-with: :async-with:
PartialInviteGuild
~~~~~~~~~~~~~~~~~~~
.. autoclass:: PartialInviteGuild()
:members:
PartialInviteChannel
~~~~~~~~~~~~~~~~~~~~~
.. autoclass:: PartialInviteChannel()
:members:
Invite Invite
~~~~~~~ ~~~~~~~

Loading…
Cancel
Save