@@ -186,6 +186,14 @@ class Client:
186186 A status to start your presence with upon logging on to Discord.
187187 activity: Optional[:class:`.BaseActivity`]
188188 An activity to start your presence with upon logging on to Discord.
189+ activities: List[:class:`.BaseActivity`]
190+ A list of activities to start your presence with upon logging on to Discord. Cannot be sent with ``activity``.
191+
192+ .. versionadded:: 2.0
193+ afk: :class:`bool`
194+ Whether to start your session as AFK. Defaults to ``False``.
195+
196+ .. versionadded:: 2.1
189197 allowed_mentions: Optional[:class:`AllowedMentions`]
190198 Control how the client handles mentions by default on every message sent.
191199
@@ -324,6 +332,7 @@ def _handle_connect(self) -> None:
324332 if status or activities :
325333 if status is None :
326334 status = getattr (state .settings , 'status' , None ) or Status .unknown
335+ _log .debug ('Setting initial presence to %s %s' , status , activities )
327336 self .loop .create_task (self .change_presence (activities = activities , status = status ))
328337
329338 @property
@@ -684,11 +693,21 @@ async def on_internal_settings_update(self, old_settings: UserSettings, new_sett
684693 ):
685694 return # Nothing changed
686695
696+ current_activity = None
697+ for activity in self .activities :
698+ if activity .type != ActivityType .custom :
699+ current_activity = activity
700+ break
701+
702+ if new_settings .status == self .client_status and new_settings .custom_activity == current_activity :
703+ return # Nothing changed
704+
687705 status = new_settings .status
688- activities = [a for a in self .activities if a .type != ActivityType .custom ]
706+ activities = [a for a in self .client_activities if a .type != ActivityType .custom ]
689707 if new_settings .custom_activity is not None :
690708 activities .append (new_settings .custom_activity )
691709
710+ _log .debug ('Syncing presence to %s %s' , status , new_settings .custom_activity )
692711 await self .change_presence (status = status , activities = activities , edit_settings = False )
693712
694713 # Hooks
@@ -1230,6 +1249,32 @@ def client_activities(self) -> Tuple[ActivityTypes]:
12301249 activities = (activity ,) if activity else activities
12311250 return activities or tuple ()
12321251
1252+ def is_afk (self ) -> bool :
1253+ """:class:`bool`: Indicates if the client is currently AFK.
1254+
1255+ This allows the Discord client to know how to handle push notifications
1256+ better for you in case you are away from your keyboard.
1257+
1258+ .. versionadded:: 2.1
1259+ """
1260+ if self .ws :
1261+ return self .ws .afk
1262+ return False
1263+
1264+ @property
1265+ def idle_since (self ) -> Optional [datetime ]:
1266+ """Optional[:class:`datetime.datetime`]: When the client went idle.
1267+
1268+ This indicates that you are truly idle and not just lying.
1269+
1270+ .. versionadded:: 2.1
1271+ """
1272+ ws = self .ws
1273+ if ws is None or not ws .idle_since :
1274+ return None
1275+
1276+ return utils .parse_timestamp (ws .idle_since )
1277+
12331278 @property
12341279 def allowed_mentions (self ) -> Optional [AllowedMentions ]:
12351280 """Optional[:class:`~discord.AllowedMentions`]: The allowed mention configuration.
@@ -1606,21 +1651,30 @@ async def on_ready():
16061651 async def change_presence (
16071652 self ,
16081653 * ,
1609- activity : Optional [ActivityTypes ] = None ,
1610- activities : Optional [List [ActivityTypes ]] = None ,
1611- status : Optional [Status ] = None ,
1612- afk : bool = False ,
1654+ activity : Optional [ActivityTypes ] = MISSING ,
1655+ activities : List [ActivityTypes ] = MISSING ,
1656+ status : Status = MISSING ,
1657+ afk : bool = MISSING ,
1658+ idle_since : Optional [datetime ] = MISSING ,
16131659 edit_settings : bool = True ,
16141660 ) -> None :
16151661 """|coro|
16161662
16171663 Changes the client's presence.
16181664
1665+ .. versionchanged:: 2.1
1666+
1667+ The default value for parameters is now the current value.
1668+ ``None`` is no longer a valid value for most; you must explicitly
1669+ set it to the default value if you want to reset it.
1670+
16191671 .. versionchanged:: 2.0
1672+
16201673 Edits are no longer in place.
16211674 Added option to update settings.
16221675
16231676 .. versionchanged:: 2.0
1677+
16241678 This function will now raise :exc:`TypeError` instead of
16251679 ``InvalidArgument``.
16261680
@@ -1636,55 +1690,79 @@ async def change_presence(
16361690 ----------
16371691 activity: Optional[:class:`.BaseActivity`]
16381692 The activity being done. ``None`` if no activity is done.
1639- activities: Optional[ List[:class:`.BaseActivity`] ]
1640- A list of the activities being done. ``None`` if no activities
1641- are done. Cannot be sent with ``activity``.
1642- status: Optional[:class:`.Status`]
1643- Indicates what status to change to. If ``None``, then
1644- :attr:`.Status.online` is used .
1693+ activities: List[:class:`.BaseActivity`]
1694+ A list of the activities being done. Cannot be sent with ``activity``.
1695+
1696+ .. versionadded:: 2.0
1697+ status: :class:`.Status`
1698+ Indicates what status to change to .
16451699 afk: :class:`bool`
16461700 Indicates if you are going AFK. This allows the Discord
16471701 client to know how to handle push notifications better
1648- for you in case you are actually idle and not lying.
1702+ for you in case you are away from your keyboard.
1703+ idle_since: Optional[:class:`datetime.datetime`]
1704+ When the client went idle. This indicates that you are
1705+ truly idle and not just lying.
16491706 edit_settings: :class:`bool`
1650- Whether to update the settings with the new status and/or
1707+ Whether to update user settings with the new status and/or
16511708 custom activity. This will broadcast the change and cause
16521709 all connected (official) clients to change presence as well.
1710+
1711+ This should be set to ``False`` for idle changes.
1712+
16531713 Required for setting/editing ``expires_at`` for custom activities.
1654- It's not recommended to change this, as setting it to ``False`` causes undefined behavior.
1714+ It's not recommended to change this, as setting it to ``False``
1715+ can cause undefined behavior.
16551716
16561717 Raises
16571718 ------
16581719 TypeError
16591720 The ``activity`` parameter is not the proper type.
16601721 Both ``activity`` and ``activities`` were passed.
1722+ ValueError
1723+ More than one custom activity was passed.
16611724 """
1662- if activity and activities :
1725+ if activity is not MISSING and activities is not MISSING :
16631726 raise TypeError ('Cannot pass both activity and activities' )
1664- activities = activities or activity and [activity ]
1665- if activities is None :
1666- activities = []
16671727
1668- if status is None :
1669- status = Status .online
1670- elif status is Status .offline :
1671- status = Status .invisible
1728+ skip_activities = False
1729+ if activities is MISSING :
1730+ if activity is not MISSING :
1731+ activities = [activity ] if activity else []
1732+ else :
1733+ activities = list (self .client_activities )
1734+ skip_activities = True
1735+ else :
1736+ activities = activities or []
16721737
1673- await self .ws .change_presence (status = status , activities = activities , afk = afk )
1738+ skip_status = status is MISSING
1739+ if status is MISSING :
1740+ status = self .client_status
1741+ if status is Status .offline :
1742+ status = Status .invisible
16741743
1675- if edit_settings :
1676- custom_activity = None
1744+ if idle_since is MISSING :
1745+ since = self .ws .idle_since if self .ws else 0
1746+ else :
1747+ since = int (idle_since .timestamp () * 1000 ) if idle_since else 0
16771748
1749+ custom_activity = None
1750+ if not skip_activities :
16781751 for activity in activities :
16791752 if getattr (activity , 'type' , None ) is ActivityType .custom :
1753+ if custom_activity is not None :
1754+ raise ValueError ('More than one custom activity was passed' )
16801755 custom_activity = activity
16811756
1757+ await self .ws .change_presence (status = status , activities = activities , afk = afk , since = since )
1758+
1759+ if edit_settings and self .settings :
16821760 payload : Dict [str , Any ] = {}
1683- if status != getattr ( self .settings , ' status' , None ) :
1761+ if not skip_status and status != self .settings . status :
16841762 payload ['status' ] = status
1685- if custom_activity != getattr ( self .settings , ' custom_activity' , None ) :
1763+ if not skip_activities and custom_activity != self .settings . custom_activity :
16861764 payload ['custom_activity' ] = custom_activity
1687- if payload and self . settings :
1765+ if payload :
16881766 await self .settings .edit (** payload )
16891767
16901768 async def change_voice_state (
0 commit comments