diff --git a/disagreement/client.py b/disagreement/client.py index b1316b7..fbd096a 100644 --- a/disagreement/client.py +++ b/disagreement/client.py @@ -36,6 +36,7 @@ from .ext import loader as ext_loader from .interactions import Interaction, Snowflake from .error_handler import setup_global_error_handler from .voice_client import VoiceClient +from .models import Activity if TYPE_CHECKING: from .models import ( @@ -439,8 +440,7 @@ class Client: async def change_presence( self, status: str, - activity_name: Optional[str] = None, - activity_type: int = 0, + activity: Optional[Activity] = None, since: int = 0, afk: bool = False, ): @@ -449,8 +449,7 @@ class Client: Args: status (str): The new status for the client (e.g., "online", "idle", "dnd", "invisible"). - activity_name (Optional[str]): The name of the activity. - activity_type (int): The type of the activity. + activity (Optional[Activity]): Activity instance describing what the bot is doing. since (int): The timestamp (in milliseconds) of when the client went idle. afk (bool): Whether the client is AFK. """ @@ -460,8 +459,7 @@ class Client: if self._gateway: await self._gateway.update_presence( status=status, - activity_name=activity_name, - activity_type=activity_type, + activity=activity, since=since, afk=afk, ) diff --git a/disagreement/gateway.py b/disagreement/gateway.py index 3875d92..b22459a 100644 --- a/disagreement/gateway.py +++ b/disagreement/gateway.py @@ -14,6 +14,8 @@ import time import random from typing import Optional, TYPE_CHECKING, Any, Dict +from .models import Activity + from .enums import GatewayOpcode, GatewayIntent from .errors import GatewayException, DisagreementException, AuthenticationError from .interactions import Interaction @@ -213,26 +215,17 @@ class GatewayClient: async def update_presence( self, status: str, - activity_name: Optional[str] = None, - activity_type: int = 0, + activity: Optional[Activity] = None, + *, since: int = 0, afk: bool = False, - ): + ) -> None: """Sends the presence update payload to the Gateway.""" payload = { "op": GatewayOpcode.PRESENCE_UPDATE, "d": { "since": since, - "activities": ( - [ - { - "name": activity_name, - "type": activity_type, - } - ] - if activity_name - else [] - ), + "activities": [activity.to_dict()] if activity else [], "status": status, "afk": afk, }, @@ -353,7 +346,10 @@ class GatewayClient: future._members.extend(raw_event_d_payload.get("members", [])) # type: ignore # If this is the last chunk, resolve the future - if raw_event_d_payload.get("chunk_index") == raw_event_d_payload.get("chunk_count", 1) - 1: + if ( + raw_event_d_payload.get("chunk_index") + == raw_event_d_payload.get("chunk_count", 1) - 1 + ): future.set_result(future._members) # type: ignore del self._member_chunk_requests[nonce] diff --git a/disagreement/models.py b/disagreement/models.py index ad42fb4..2aa8b74 100644 --- a/disagreement/models.py +++ b/disagreement/models.py @@ -2361,6 +2361,37 @@ class ThreadMember: return f"" +class Activity: + """Represents a user's presence activity.""" + + def __init__(self, name: str, type: int) -> None: + self.name = name + self.type = type + + def to_dict(self) -> Dict[str, Any]: + return {"name": self.name, "type": self.type} + + +class Game(Activity): + """Represents a playing activity.""" + + def __init__(self, name: str) -> None: + super().__init__(name, 0) + + +class Streaming(Activity): + """Represents a streaming activity.""" + + def __init__(self, name: str, url: str) -> None: + super().__init__(name, 1) + self.url = url + + def to_dict(self) -> Dict[str, Any]: + payload = super().to_dict() + payload["url"] = self.url + return payload + + class PresenceUpdate: """Represents a PRESENCE_UPDATE event.""" @@ -2371,7 +2402,17 @@ class PresenceUpdate: self.user = User(data["user"]) self.guild_id: Optional[str] = data.get("guild_id") self.status: Optional[str] = data.get("status") - self.activities: List[Dict[str, Any]] = data.get("activities", []) + self.activities: List[Activity] = [] + for activity in data.get("activities", []): + act_type = activity.get("type", 0) + name = activity.get("name", "") + if act_type == 0: + obj = Game(name) + elif act_type == 1: + obj = Streaming(name, activity.get("url", "")) + else: + obj = Activity(name, act_type) + self.activities.append(obj) self.client_status: Dict[str, Any] = data.get("client_status", {}) def __repr__(self) -> str: diff --git a/docs/presence.md b/docs/presence.md index 0e5da19..ac87644 100644 --- a/docs/presence.md +++ b/docs/presence.md @@ -1,6 +1,7 @@ # Updating Presence The `Client.change_presence` method allows you to update the bot's status and displayed activity. +Pass an :class:`~disagreement.models.Activity` (such as :class:`~disagreement.models.Game` or :class:`~disagreement.models.Streaming`) to describe what your bot is doing. ## Status Strings @@ -22,8 +23,18 @@ An activity dictionary must include a `name` and a `type` field. The type value | `4` | Custom | | `5` | Competing | -Example: +Example using the provided activity classes: ```python -await client.change_presence(status="idle", activity={"name": "with Discord", "type": 0}) +from disagreement.models import Game + +await client.change_presence(status="idle", activity=Game("with Discord")) +``` + +You can also specify a streaming URL: + +```python +from disagreement.models import Streaming + +await client.change_presence(status="online", activity=Streaming("My Stream", "https://twitch.tv/someone")) ``` diff --git a/tests/test_presence_update.py b/tests/test_presence_update.py index 3135cf7..73551d9 100644 --- a/tests/test_presence_update.py +++ b/tests/test_presence_update.py @@ -2,6 +2,7 @@ import pytest from unittest.mock import AsyncMock from disagreement.client import Client +from disagreement.models import Game from disagreement.errors import DisagreementException @@ -18,11 +19,11 @@ class DummyGateway(MagicMock): async def test_change_presence_passes_arguments(): client = Client(token="t") client._gateway = DummyGateway() - - await client.change_presence(status="idle", activity_name="hi", activity_type=0) + game = Game("hi") + await client.change_presence(status="idle", activity=game) client._gateway.update_presence.assert_awaited_once_with( - status="idle", activity_name="hi", activity_type=0, since=0, afk=False + status="idle", activity=game, since=0, afk=False )