mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-19 07:26:17 +00:00
Profiling showed that WeakValueDictionary caused rather significant and noticeable slowdowns during startup. Since the only thing it was used for was to automatically remove the key from the mapping when the reference count reaches zero, the same could theoretically be accomplished by using the __del__ special method. There is a chance that this could lead to a memory leak since the __del__ method is not always called, but the only instances of this happening are during interpreter shutdown to my knowledge and at that point the mapping is the least of my concern.
392 lines
12 KiB
Python
392 lines
12 KiB
Python
"""
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2015-present 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 typing import Any, Dict, Optional, TYPE_CHECKING
|
|
import discord.abc
|
|
from .flags import PublicUserFlags
|
|
from .utils import snowflake_time, _bytes_to_base64_data, MISSING
|
|
from .enums import DefaultAvatar
|
|
from .colour import Colour
|
|
from .asset import Asset
|
|
|
|
__all__ = (
|
|
'User',
|
|
'ClientUser',
|
|
)
|
|
|
|
_BaseUser = discord.abc.User
|
|
|
|
|
|
class BaseUser(_BaseUser):
|
|
__slots__ = ('name', 'id', 'discriminator', '_avatar', 'bot', 'system', '_public_flags', '_state')
|
|
|
|
if TYPE_CHECKING:
|
|
name: str
|
|
id: int
|
|
discriminator: str
|
|
bot: bool
|
|
system: bool
|
|
|
|
def __init__(self, *, state, data):
|
|
self._state = state
|
|
self._update(data)
|
|
|
|
def __repr__(self):
|
|
return (
|
|
f"<BaseUser id={self.id} name={self.name!r} discriminator={self.discriminator!r}"
|
|
f" bot={self.bot} system={self.system}>"
|
|
)
|
|
|
|
def __str__(self):
|
|
return f'{self.name}#{self.discriminator}'
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, _BaseUser) and other.id == self.id
|
|
|
|
def __ne__(self, other):
|
|
return not self.__eq__(other)
|
|
|
|
def __hash__(self):
|
|
return self.id >> 22
|
|
|
|
def _update(self, data):
|
|
self.name = data['username']
|
|
self.id = int(data['id'])
|
|
self.discriminator = data['discriminator']
|
|
self._avatar = data['avatar']
|
|
self._public_flags = data.get('public_flags', 0)
|
|
self.bot = data.get('bot', False)
|
|
self.system = data.get('system', False)
|
|
|
|
@classmethod
|
|
def _copy(cls, user):
|
|
self = cls.__new__(cls) # bypass __init__
|
|
|
|
self.name = user.name
|
|
self.id = user.id
|
|
self.discriminator = user.discriminator
|
|
self._avatar = user._avatar
|
|
self.bot = user.bot
|
|
self._state = user._state
|
|
self._public_flags = user._public_flags
|
|
|
|
return self
|
|
|
|
def _to_minimal_user_json(self):
|
|
return {
|
|
'username': self.name,
|
|
'id': self.id,
|
|
'avatar': self._avatar,
|
|
'discriminator': self.discriminator,
|
|
'bot': self.bot,
|
|
}
|
|
|
|
@property
|
|
def public_flags(self):
|
|
""":class:`PublicUserFlags`: The publicly available flags the user has."""
|
|
return PublicUserFlags._from_value(self._public_flags)
|
|
|
|
@property
|
|
def avatar(self):
|
|
""":class:`Asset`: Returns an :class:`Asset` for the avatar the user has.
|
|
|
|
If the user does not have a traditional avatar, an asset for
|
|
the default avatar is returned instead.
|
|
"""
|
|
if self._avatar is None:
|
|
return Asset._from_default_avatar(self._state, int(self.discriminator) % len(DefaultAvatar))
|
|
else:
|
|
return Asset._from_avatar(self._state, self.id, self._avatar)
|
|
@property
|
|
def default_avatar(self):
|
|
""":class:`Asset`: Returns the default avatar for a given user. This is calculated by the user's discriminator."""
|
|
return Asset._from_default_avatar(self._state, int(self.discriminator) % len(DefaultAvatar))
|
|
|
|
@property
|
|
def colour(self):
|
|
""":class:`Colour`: A property that returns a colour denoting the rendered colour
|
|
for the user. This always returns :meth:`Colour.default`.
|
|
|
|
There is an alias for this named :attr:`color`.
|
|
"""
|
|
return Colour.default()
|
|
|
|
@property
|
|
def color(self):
|
|
""":class:`Colour`: A property that returns a color denoting the rendered color
|
|
for the user. This always returns :meth:`Colour.default`.
|
|
|
|
There is an alias for this named :attr:`colour`.
|
|
"""
|
|
return self.colour
|
|
|
|
@property
|
|
def mention(self):
|
|
""":class:`str`: Returns a string that allows you to mention the given user."""
|
|
return f'<@{self.id}>'
|
|
|
|
@property
|
|
def created_at(self):
|
|
""":class:`datetime.datetime`: Returns the user's creation time in UTC.
|
|
|
|
This is when the user's Discord account was created.
|
|
"""
|
|
return snowflake_time(self.id)
|
|
|
|
@property
|
|
def display_name(self):
|
|
""":class:`str`: Returns the user's display name.
|
|
|
|
For regular users this is just their username, but
|
|
if they have a guild specific nickname then that
|
|
is returned instead.
|
|
"""
|
|
return self.name
|
|
|
|
def mentioned_in(self, message):
|
|
"""Checks if the user is mentioned in the specified message.
|
|
|
|
Parameters
|
|
-----------
|
|
message: :class:`Message`
|
|
The message to check if you're mentioned in.
|
|
|
|
Returns
|
|
-------
|
|
:class:`bool`
|
|
Indicates if the user is mentioned in the message.
|
|
"""
|
|
|
|
if message.mention_everyone:
|
|
return True
|
|
|
|
return any(user.id == self.id for user in message.mentions)
|
|
|
|
|
|
class ClientUser(BaseUser):
|
|
"""Represents your Discord user.
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: x == y
|
|
|
|
Checks if two users are equal.
|
|
|
|
.. describe:: x != y
|
|
|
|
Checks if two users are not equal.
|
|
|
|
.. describe:: hash(x)
|
|
|
|
Return the user's hash.
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the user's name with discriminator.
|
|
|
|
Attributes
|
|
-----------
|
|
name: :class:`str`
|
|
The user's username.
|
|
id: :class:`int`
|
|
The user's unique ID.
|
|
discriminator: :class:`str`
|
|
The user's discriminator. This is given when the username has conflicts.
|
|
bot: :class:`bool`
|
|
Specifies if the user is a bot account.
|
|
system: :class:`bool`
|
|
Specifies if the user is a system user (i.e. represents Discord officially).
|
|
|
|
.. versionadded:: 1.3
|
|
|
|
verified: :class:`bool`
|
|
Specifies if the user's email is verified.
|
|
locale: Optional[:class:`str`]
|
|
The IETF language tag used to identify the language the user is using.
|
|
mfa_enabled: :class:`bool`
|
|
Specifies if the user has MFA turned on and working.
|
|
"""
|
|
|
|
__slots__ = ('locale', '_flags', 'verified', 'mfa_enabled', '__weakref__')
|
|
|
|
def __init__(self, *, state, data):
|
|
super().__init__(state=state, data=data)
|
|
|
|
def __repr__(self):
|
|
return (
|
|
f'<ClientUser id={self.id} name={self.name!r} discriminator={self.discriminator!r}'
|
|
f' bot={self.bot} verified={self.verified} mfa_enabled={self.mfa_enabled}>'
|
|
)
|
|
|
|
def _update(self, data):
|
|
super()._update(data)
|
|
# There's actually an Optional[str] phone field as well but I won't use it
|
|
self.verified = data.get('verified', False)
|
|
self.locale = data.get('locale')
|
|
self._flags = data.get('flags', 0)
|
|
self.mfa_enabled = data.get('mfa_enabled', False)
|
|
|
|
async def edit(self, *, username: str = MISSING, avatar: bytes = MISSING) -> None:
|
|
"""|coro|
|
|
|
|
Edits the current profile of the client.
|
|
|
|
.. note::
|
|
|
|
To upload an avatar, a :term:`py:bytes-like object` must be passed in that
|
|
represents the image being uploaded. If this is done through a file
|
|
then the file must be opened via ``open('some_filename', 'rb')`` and
|
|
the :term:`py:bytes-like object` is given through the use of ``fp.read()``.
|
|
|
|
The only image formats supported for uploading is JPEG and PNG.
|
|
|
|
Parameters
|
|
-----------
|
|
username: :class:`str`
|
|
The new username you wish to change to.
|
|
avatar: :class:`bytes`
|
|
A :term:`py:bytes-like object` representing the image to upload.
|
|
Could be ``None`` to denote no avatar.
|
|
|
|
Raises
|
|
------
|
|
HTTPException
|
|
Editing your profile failed.
|
|
InvalidArgument
|
|
Wrong image format passed for ``avatar``.
|
|
"""
|
|
payload: Dict[str, Any] = {}
|
|
if username is not MISSING:
|
|
payload['username'] = username
|
|
|
|
if avatar is not MISSING:
|
|
payload['avatar'] = _bytes_to_base64_data(avatar)
|
|
|
|
data = await self._state.http.edit_profile(payload)
|
|
self._update(data)
|
|
|
|
|
|
class User(BaseUser, discord.abc.Messageable):
|
|
"""Represents a Discord user.
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: x == y
|
|
|
|
Checks if two users are equal.
|
|
|
|
.. describe:: x != y
|
|
|
|
Checks if two users are not equal.
|
|
|
|
.. describe:: hash(x)
|
|
|
|
Return the user's hash.
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the user's name with discriminator.
|
|
|
|
Attributes
|
|
-----------
|
|
name: :class:`str`
|
|
The user's username.
|
|
id: :class:`int`
|
|
The user's unique ID.
|
|
discriminator: :class:`str`
|
|
The user's discriminator. This is given when the username has conflicts.
|
|
bot: :class:`bool`
|
|
Specifies if the user is a bot account.
|
|
system: :class:`bool`
|
|
Specifies if the user is a system user (i.e. represents Discord officially).
|
|
"""
|
|
|
|
__slots__ = ('_stored',)
|
|
|
|
def __init__(self, *, state, data):
|
|
super().__init__(state=state, data=data)
|
|
self._stored = False
|
|
|
|
def __repr__(self):
|
|
return f'<User id={self.id} name={self.name!r} discriminator={self.discriminator!r} bot={self.bot}>'
|
|
|
|
def __del__(self) -> None:
|
|
try:
|
|
if self._stored:
|
|
self._state.deref_user(self.id)
|
|
except Exception:
|
|
pass
|
|
|
|
@classmethod
|
|
def _copy(cls, user):
|
|
self = super()._copy(user)
|
|
self._stored = getattr(user, '_stored', False)
|
|
return self
|
|
|
|
async def _get_channel(self):
|
|
ch = await self.create_dm()
|
|
return ch
|
|
|
|
@property
|
|
def dm_channel(self):
|
|
"""Optional[:class:`DMChannel`]: Returns the channel associated with this user if it exists.
|
|
|
|
If this returns ``None``, you can create a DM channel by calling the
|
|
:meth:`create_dm` coroutine function.
|
|
"""
|
|
return self._state._get_private_channel_by_user(self.id)
|
|
|
|
@property
|
|
def mutual_guilds(self):
|
|
"""List[:class:`Guild`]: The guilds that the user shares with the client.
|
|
|
|
.. note::
|
|
|
|
This will only return mutual guilds within the client's internal cache.
|
|
|
|
.. versionadded:: 1.7
|
|
"""
|
|
return [guild for guild in self._state._guilds.values() if guild.get_member(self.id)]
|
|
|
|
async def create_dm(self):
|
|
"""|coro|
|
|
|
|
Creates a :class:`DMChannel` with this user.
|
|
|
|
This should be rarely called, as this is done transparently for most
|
|
people.
|
|
|
|
Returns
|
|
-------
|
|
:class:`.DMChannel`
|
|
The channel that was created.
|
|
"""
|
|
found = self.dm_channel
|
|
if found is not None:
|
|
return found
|
|
|
|
state = self._state
|
|
data = await state.http.start_private_message(self.id)
|
|
return state.add_dm_channel(data)
|