diff --git a/discord/client.py b/discord/client.py index 0b0d9d5a2..87cee11ac 100644 --- a/discord/client.py +++ b/discord/client.py @@ -37,7 +37,7 @@ from .errors import * from .state import ConnectionState from .permissions import Permissions, PermissionOverwrite from . import utils, compat -from .enums import ChannelType, ServerRegion, VerificationLevel +from .enums import ChannelType, ServerRegion, VerificationLevel, Status from .voice_client import VoiceClient from .iterators import LogsFromIterator from .gateway import * @@ -1526,6 +1526,7 @@ class Client: self._update_cache(self.email, password) @asyncio.coroutine + @utils.deprecated('change_presence') def change_status(self, game=None, idle=False): """|coro| @@ -1537,7 +1538,8 @@ class Client: The idle parameter is a boolean parameter that indicates whether the client should go idle or not. - .. _game_list: https://gist.github.com/Rapptz/a82b82381b70a60c281b + .. deprecated:: v0.13.0 + Use :meth:`change_status` instead. Parameters ---------- @@ -1553,6 +1555,42 @@ class Client: """ yield from self.ws.change_presence(game=game, idle=idle) + @asyncio.coroutine + def change_presence(self, *, game=None, status=None, afk=False): + """|coro| + + Changes the client's presence. + + The game parameter is a Game object (not a string) that represents + a game being played currently. + + Parameters + ---------- + game: Optional[:class:`Game`] + The game being played. None if no game is being played. + status: Optional[:class:`Status`] + Indicates what status to change to. If None, then + :attr:`Status.online` is used. + afk: bool + Indicates if you are going AFK. This allows the discord + client to know how to handle push notifications better + for you in case you are actually idle and not lying. + + Raises + ------ + InvalidArgument + If the ``game`` parameter is not :class:`Game` or None. + """ + + if status is None: + status = 'online' + elif status is Status.offline: + status = 'invisible' + else: + status = str(status) + + yield from self.ws.change_presence(game=game, status=status, afk=afk) + @asyncio.coroutine def change_nickname(self, member, nickname): """|coro| diff --git a/discord/enums.py b/discord/enums.py index 191728ee6..37195cbb5 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -78,6 +78,9 @@ class Status(Enum): online = 'online' offline = 'offline' idle = 'idle' + dnd = 'dnd' + do_not_disturb = 'dnd' + invisible = 'invisible' def __str__(self): return self.value diff --git a/discord/gateway.py b/discord/gateway.py index ff6bc6a84..402a174b0 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -29,7 +29,7 @@ import websockets import asyncio import aiohttp from . import utils, compat -from .enums import Status +from .enums import Status, try_enum from .game import Game from .errors import GatewayNotFound, ConnectionClosed, InvalidArgument import logging @@ -406,18 +406,25 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol): raise ConnectionClosed(e) from e @asyncio.coroutine - def change_presence(self, *, game=None, idle=None): + def change_presence(self, *, game=None, status=None, afk=False, since=0.0, idle=None): if game is not None and not isinstance(game, Game): raise InvalidArgument('game must be of Game or None') - idle_since = None if idle == False else int(time.time() * 1000) + if idle: + status = 'idle' + + if status == 'idle': + since = int(time.time() * 1000) + sent_game = dict(game) if game else None payload = { 'op': self.PRESENCE, 'd': { 'game': sent_game, - 'idle_since': idle_since + 'afk': afk, + 'since': since, + 'status': status } } @@ -425,14 +432,17 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol): log.debug('Sending "{}" to change status'.format(sent)) yield from self.send(sent) + status_enum = try_enum(Status, status) + if status_enum is Status.invisible: + status_enum = Status.offline + for server in self._connection.servers: me = server.me if me is None: continue me.game = game - status = Status.idle if idle_since else Status.online - me.status = status + me.status = status_enum @asyncio.coroutine def request_sync(self, guild_ids): diff --git a/discord/utils.py b/discord/utils.py index 55ee30a8a..831296541 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -30,6 +30,7 @@ import datetime from base64 import b64encode import asyncio import json +import warnings, functools DISCORD_EPOCH = 1420070400000 @@ -74,6 +75,21 @@ def parse_time(timestamp): return datetime.datetime(*map(int, re_split(r'[^\d]', timestamp.replace('+00:00', '')))) return None +def deprecated(instead=None): + def actual_decorator(func): + @functools.wraps(func) + def decorated(*args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) # turn off filter + if instead: + fmt = "{0.__name__} is deprecated, use {1} instead." + else: + fmt = '{0.__name__} is deprecated.' + + warnings.warn(fmt.format(func, instead), stacklevel=3, category=DeprecationWarning) + warnings.simplefilter('default', DeprecationWarning) # reset filter + return func(*args, **kwargs) + return decorated + return actual_decorator def oauth_url(client_id, permissions=None, server=None, redirect_uri=None): """A helper function that returns the OAuth2 URL for inviting the bot diff --git a/docs/api.rst b/docs/api.rst index 02572dd2f..641331c45 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -549,6 +549,17 @@ All enumerations are subclasses of `enum`_. .. attribute:: idle The member is idle. + .. attribute:: dnd + + The member is "Do Not Disturb". + .. attribute:: do_not_disturb + + An alias for :attr:`dnd`. + .. attribute:: invisible + + The member is "invisible". In reality, this is only used in sending + a presence a la :meth:`Client.change_presence`. When you receive a + user's presence this will be :attr:`offline` instead. .. _discord_api_data: