aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRapptz <[email protected]>2017-01-19 19:37:11 -0500
committerRapptz <[email protected]>2017-01-19 19:37:11 -0500
commitfa384f21148487bc188c83e829f1964b6d6e1b06 (patch)
tree12da19daeccf9f7871500e5cc3cbcb0a805c6904
parentRemove email/password based login. (diff)
downloaddiscord.py-fa384f21148487bc188c83e829f1964b6d6e1b06.tar.xz
discord.py-fa384f21148487bc188c83e829f1964b6d6e1b06.zip
Make ClientUser separate from a regular User.
This removes Client.edit_profile in favour of ClientUser.edit.
-rw-r--r--discord/__init__.py2
-rw-r--r--discord/client.py80
-rw-r--r--discord/member.py4
-rw-r--r--discord/state.py6
-rw-r--r--discord/user.py275
-rw-r--r--docs/api.rst7
6 files changed, 221 insertions, 153 deletions
diff --git a/discord/__init__.py b/discord/__init__.py
index 10bbec8c..f8a4eac2 100644
--- a/discord/__init__.py
+++ b/discord/__init__.py
@@ -18,7 +18,7 @@ __copyright__ = 'Copyright 2015-2016 Rapptz'
__version__ = '1.0.0a0'
from .client import Client, AppInfo, ChannelPermissions
-from .user import User
+from .user import User, ClientUser
from .game import Game
from .emoji import Emoji, PartialEmoji
from .channel import *
diff --git a/discord/client.py b/discord/client.py
index b967a56d..725c0e77 100644
--- a/discord/client.py
+++ b/discord/client.py
@@ -99,7 +99,7 @@ class Client:
Attributes
-----------
- user : Optional[:class:`User`]
+ user : Optional[:class:`ClientUser`]
Represents the connected client. None if not logged in.
voice_clients: List[:class:`VoiceClient`]
Represents a list of voice connections. To connect to voice use
@@ -815,84 +815,6 @@ class Client:
yield from self.ws.send_as_json(payload)
@asyncio.coroutine
- def edit_profile(self, password=None, **fields):
- """|coro|
-
- Edits the current profile of the client.
-
- If a bot account is used then the password field is optional,
- otherwise it is required.
-
- The :attr:`Client.user` object is not modified directly afterwards until the
- corresponding WebSocket event is received.
-
- Note
- -----
- To upload an avatar, a *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 *bytes-like object* is given through the use of ``fp.read()``.
-
- The only image formats supported for uploading is JPEG and PNG.
-
- Parameters
- -----------
- password : str
- The current password for the client's account. Not used
- for bot accounts.
- new_password : str
- The new password you wish to change to.
- email : str
- The new email you wish to change to.
- username :str
- The new username you wish to change to.
- avatar : bytes
- A *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``.
- ClientException
- Password is required for non-bot accounts.
- """
-
- try:
- avatar_bytes = fields['avatar']
- except KeyError:
- avatar = self.user.avatar
- else:
- if avatar_bytes is not None:
- avatar = utils._bytes_to_base64_data(avatar_bytes)
- else:
- avatar = None
-
- not_bot_account = not self.user.bot
- if not_bot_account and password is None:
- raise ClientException('Password is required for non-bot accounts.')
-
- args = {
- 'password': password,
- 'username': fields.get('username', self.user.name),
- 'avatar': avatar
- }
-
- if not_bot_account:
- args['email'] = fields.get('email', self.email)
-
- if 'new_password' in fields:
- args['new_password'] = fields['new_password']
-
- data = yield from self.http.edit_profile(**args)
- if not_bot_account:
- self.email = data['email']
- if 'token' in data:
- self.http._token(data['token'], bot=False)
-
- @asyncio.coroutine
def change_presence(self, *, game=None, status=None, afk=False):
"""|coro|
diff --git a/discord/member.py b/discord/member.py
index 97c9c254..2cc5a92d 100644
--- a/discord/member.py
+++ b/discord/member.py
@@ -29,7 +29,7 @@ import asyncio
import discord.abc
from . import utils
-from .user import User
+from .user import BaseUser
from .game import Game
from .permissions import Permissions
from .enums import Status, ChannelType, try_enum
@@ -74,7 +74,7 @@ class VoiceState:
return '<VoiceState self_mute={0.self_mute} self_deaf={0.self_deaf} channel={0.channel!r}>'.format(self)
def flatten_user(cls):
- for attr, value in User.__dict__.items():
+ for attr, value in BaseUser.__dict__.items():
# ignore private/special methods
if attr.startswith('_'):
continue
diff --git a/discord/state.py b/discord/state.py
index 7f0c4eb9..f68656cc 100644
--- a/discord/state.py
+++ b/discord/state.py
@@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
"""
from .guild import Guild
-from .user import User
+from .user import User, ClientUser
from .game import Game
from .emoji import Emoji, PartialEmoji
from .reaction import Reaction
@@ -239,7 +239,7 @@ class ConnectionState:
def parse_ready(self, data):
self._ready_state = ReadyState(launch=asyncio.Event(), guilds=[])
- self.user = self.store_user(data['user'])
+ self.user = ClientUser(state=self, data=data['user'])
guilds = self._ready_state.guilds
for guild_data in data['guilds']:
@@ -339,7 +339,7 @@ class ConnectionState:
self.dispatch('member_update', old_member, member)
def parse_user_update(self, data):
- self.user = User(state=self, data=data)
+ self.user = ClientUser(state=self, data=data)
def parse_channel_delete(self, data):
guild = self._get_guild(utils._get_as_snowflake(data, 'guild_id'))
diff --git a/discord/user.py b/discord/user.py
index c08a3fb7..7755c7c7 100644
--- a/discord/user.py
+++ b/discord/user.py
@@ -24,44 +24,15 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
-from .utils import snowflake_time
+from .utils import snowflake_time, _bytes_to_base64_data
from .enums import DefaultAvatar
+from .errors import ClientException
import discord.abc
import asyncio
-class User(discord.abc.Messageable):
- """Represents a Discord user.
-
- Supported Operations:
-
- +-----------+---------------------------------------------+
- | Operation | Description |
- +===========+=============================================+
- | x == y | Checks if two users are equal. |
- +-----------+---------------------------------------------+
- | x != y | Checks if two users are not equal. |
- +-----------+---------------------------------------------+
- | hash(x) | Return the user's hash. |
- +-----------+---------------------------------------------+
- | str(x) | Returns the user's name with discriminator. |
- +-----------+---------------------------------------------+
-
- Attributes
- -----------
- name: str
- The user's username.
- id: int
- The user's unique ID.
- discriminator: str
- The user's discriminator. This is given when the username has conflicts.
- avatar: str
- The avatar hash the user has. Could be None.
- bot: bool
- Specifies if the user is a bot account.
- """
-
- __slots__ = ('name', 'id', 'discriminator', 'avatar', 'bot', '_state', '__weakref__')
+class BaseUser:
+ __slots__ = ('name', 'id', 'discriminator', 'avatar', 'bot', '_state')
def __init__(self, *, state, data):
self._state = state
@@ -75,7 +46,7 @@ class User(discord.abc.Messageable):
return '{0.name}#{0.discriminator}'.format(self)
def __eq__(self, other):
- return isinstance(other, User) and other.id == self.id
+ return isinstance(other, BaseUser) and other.id == self.id
def __ne__(self, other):
return not self.__eq__(other)
@@ -83,38 +54,6 @@ class User(discord.abc.Messageable):
def __hash__(self):
return self.id >> 22
- def __repr__(self):
- return '<User id={0.id} name={0.name!r} discriminator={0.discriminator!r} bot={0.bot}>'.format(self)
-
- @asyncio.coroutine
- def _get_channel(self):
- ch = yield from self.create_dm()
- return ch
-
- @property
- def dm_channel(self):
- """Returns the :class:`DMChannel` 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)
-
- @asyncio.coroutine
- def create_dm(self):
- """Creates a :class:`DMChannel` with this user.
-
- This should be rarely called, as this is done transparently for most
- people.
- """
- found = self.dm_channel
- if found is not None:
- return found
-
- state = self._state
- data = yield from state.http.start_private_message(self.id)
- return state.add_dm_channel(data)
-
@property
def avatar_url(self):
"""Returns a friendly URL version of the avatar the user has.
@@ -191,7 +130,207 @@ class User(discord.abc.Messageable):
if message.mention_everyone:
return True
- if self in message.mentions:
- return True
+ for user in message.mentions:
+ if user.id == self.id:
+ return True
return False
+
+class ClientUser(BaseUser):
+ """Represents your Discord user.
+
+ Supported Operations:
+
+ +-----------+---------------------------------------------+
+ | Operation | Description |
+ +===========+=============================================+
+ | x == y | Checks if two users are equal. |
+ +-----------+---------------------------------------------+
+ | x != y | Checks if two users are not equal. |
+ +-----------+---------------------------------------------+
+ | hash(x) | Return the user's hash. |
+ +-----------+---------------------------------------------+
+ | str(x) | Returns the user's name with discriminator. |
+ +-----------+---------------------------------------------+
+
+ Attributes
+ -----------
+ name: str
+ The user's username.
+ id: int
+ The user's unique ID.
+ discriminator: str
+ The user's discriminator. This is given when the username has conflicts.
+ avatar: str
+ The avatar hash the user has. Could be None.
+ bot: bool
+ Specifies if the user is a bot account.
+ verified: bool
+ Specifies if the user is a verified account.
+ email: Optional[str]
+ The email the user used when registering.
+ mfa_enabled: bool
+ Specifies if the user has MFA turned on and working.
+ """
+ __slots__ = ('email', 'verified', 'mfa_enabled')
+
+ def __init__(self, *, state, data):
+ super().__init__(state=state, data=data)
+ self.verified = data.get('verified', False)
+ self.email = data.get('email')
+ self.mfa_enabled = data.get('mfa_enabled', False)
+
+ def __repr__(self):
+ return '<ClientUser id={0.id} name={0.name!r} discriminator={0.discriminator!r}' \
+ ' bot={0.bot} verified={0.verified} mfa_enabled={0.mfa_enabled}>'.format(self)
+
+
+ @asyncio.coroutine
+ def edit(self, **fields):
+ """|coro|
+
+ Edits the current profile of the client.
+
+ If a bot account is used then a password field is optional,
+ otherwise it is required.
+
+ Note
+ -----
+ To upload an avatar, a *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 *bytes-like object* is given through the use of ``fp.read()``.
+
+ The only image formats supported for uploading is JPEG and PNG.
+
+ Parameters
+ -----------
+ password : str
+ The current password for the client's account.
+ Only applicable to user accounts.
+ new_password: str
+ The new password you wish to change to.
+ Only applicable to user accounts.
+ email: str
+ The new email you wish to change to.
+ Only applicable to user accounts.
+ username :str
+ The new username you wish to change to.
+ avatar: bytes
+ A *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``.
+ ClientException
+ Password is required for non-bot accounts.
+ """
+
+ try:
+ avatar_bytes = fields['avatar']
+ except KeyError:
+ avatar = self.avatar
+ else:
+ if avatar_bytes is not None:
+ avatar = _bytes_to_base64_data(avatar_bytes)
+ else:
+ avatar = None
+
+ not_bot_account = not self.bot
+ password = fields.get('password')
+ if not_bot_account and password is None:
+ raise ClientException('Password is required for non-bot accounts.')
+
+ args = {
+ 'password': password,
+ 'username': fields.get('username', self.name),
+ 'avatar': avatar
+ }
+
+ if not_bot_account:
+ args['email'] = fields.get('email', self.email)
+
+ if 'new_password' in fields:
+ args['new_password'] = fields['new_password']
+
+ http = self._state.http
+
+ data = yield from http.edit_profile(**args)
+ if not_bot_account:
+ self.email = data['email']
+ try:
+ http._token(data['token'], bot=False)
+ except KeyError:
+ pass
+
+ # manually update data by calling __init__ explicitly.
+ self.__init__(state=self._state, data=data)
+
+class User(BaseUser, discord.abc.Messageable):
+ """Represents a Discord user.
+
+ Supported Operations:
+
+ +-----------+---------------------------------------------+
+ | Operation | Description |
+ +===========+=============================================+
+ | x == y | Checks if two users are equal. |
+ +-----------+---------------------------------------------+
+ | x != y | Checks if two users are not equal. |
+ +-----------+---------------------------------------------+
+ | hash(x) | Return the user's hash. |
+ +-----------+---------------------------------------------+
+ | str(x) | Returns the user's name with discriminator. |
+ +-----------+---------------------------------------------+
+
+ Attributes
+ -----------
+ name: str
+ The user's username.
+ id: int
+ The user's unique ID.
+ discriminator: str
+ The user's discriminator. This is given when the username has conflicts.
+ avatar: str
+ The avatar hash the user has. Could be None.
+ bot: bool
+ Specifies if the user is a bot account.
+ """
+
+ __slots__ = ('__weakref__')
+
+ def __repr__(self):
+ return '<User id={0.id} name={0.name!r} discriminator={0.discriminator!r} bot={0.bot}>'.format(self)
+
+ @asyncio.coroutine
+ def _get_channel(self):
+ ch = yield from self.create_dm()
+ return ch
+
+ @property
+ def dm_channel(self):
+ """Returns the :class:`DMChannel` 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)
+
+ @asyncio.coroutine
+ def create_dm(self):
+ """Creates a :class:`DMChannel` with this user.
+
+ This should be rarely called, as this is done transparently for most
+ people.
+ """
+ found = self.dm_channel
+ if found is not None:
+ return found
+
+ state = self._state
+ data = yield from state.http.start_private_message(self.id)
+ return state.add_dm_channel(data)
diff --git a/docs/api.rst b/docs/api.rst
index 985afc3b..7ab4c0b5 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -645,6 +645,13 @@ Object
.. autoclass:: Object
:members:
+ClientUser
+~~~~~~~~~~~~
+
+.. autoclass:: ClientUser
+ :members:
+ :inherited-members:
+
User
~~~~~