diff --git a/DOCS.md b/DOCS.md index d66c334..eddc429 100644 --- a/DOCS.md +++ b/DOCS.md @@ -79,7 +79,7 @@ Examples can be seen in the repository (`Discord-RPC/examples`) or [here](https: Parameters : - state (`str`) - details (`str`) - - act_type (`int`) : [Activity Types](https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-types) (Activity Type `1` and `4` is currently disabled, see [#28](https://github.com/Senophyx/Discord-RPC/issues/28#issuecomment-2301287350)). + - act_type (`discordrpc.Activity`) : [Activity Types](https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-types) (Activity Type `1` and `4` is currently disabled, see [#28](https://github.com/Senophyx/Discord-RPC/issues/28#issuecomment-2301287350)). - ts_start (`int`) : Timestamp start. - ts_end (`int`) : Timestamp end. - large_image (`str`) : The name of the image that has been uploaded to the Discord Developer Portal. @@ -91,9 +91,9 @@ Examples can be seen in the repository (`Discord-RPC/examples`) or [here](https: - join_secret (`str`) : Secret for chat invitations and ask to join button. - spectate_secret (`str`) : Secret for spectate button. - match_secret (`str`) : Secret for for spectate and join button - - buttons (`list`) : list of dicts for buttons on user's profile. You can use `discordrpc.button.Button` for more easier. + - buttons (`list`) : list of dicts for buttons on user's profile. You can use `discordrpc.Button` for more easier. - Return : nothing. + Return : `True` if rpc successfully connected. - method `RPC.disconnect()`
Disconnecting and closing RPC socket. @@ -122,17 +122,36 @@ Examples can be seen in the repository (`Discord-RPC/examples`) or [here](https: Return : `True` or `False` +## class `discordrpc.Activity` +- Enum `Activity`
+ Simplified Activity type payload in `RPC.set_activity` + + Available values : + - Playing + - Streaming + - Listening + - Watching + - Custom + - Competing + +> [!NOTE] +> Activity Type `Streaming` and `Custom` currently disabled.
+> [Details](https://github.com/Senophyx/Discord-RPC/issues/28#issuecomment-2301287350) + + ## class `discordrpc.Button()` - function `Button()`
Simplified button payload in `RPC.set_activity` Parameters : - - button_one_label (`str`) : Label for button one. - - button_one_url (`str`) : Url for button one. - - button_two_label (`str`) : Label for button two. - - button_two_url (`str`) : Url for button two. + - text (`test`) + - text (`url`) + + Return : Payload dict. - Return : List of button dict. +> [!NOTE] +> Discord does not display buttons in your own Activity.
+> You won’t see them yourself — but other users will see them correctly. ## class `discordrpc.utils` - variable `discordrpc.utils.timestamp()`
@@ -193,4 +212,4 @@ Examples can be seen in the repository (`Discord-RPC/examples`) or [here](https: ``` Discord-RPC project is under MIT License Copyright (c) 2021-2024 Senophyx and EterNomm. -``` \ No newline at end of file +``` diff --git a/README.md b/README.md index d642e0b..f644582 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ # Discord RPC An Python wrapper for Discord RPC API. Allow you to make own custom RPC. -[![Changelog](https://img.shields.io/badge/Discord--RPC-Changelog-informational?style=for-the-badge&logo=github)](https://senophyx.id/projects/discord-rpc/#change-logs) +[![Changelog](https://img.shields.io/badge/Changelog-blue?style=for-the-badge&logo=github)](https://senophyx.id/projects/discord-rpc/#change-logs) +[![Documentation](https://img.shields.io/badge/Documentation-gray?style=for-the-badge&logo=googledocs&logoColor=white)](https://github.com/Senophyx/Discord-RPC/blob/main/DOCS.md) ## Install - PyPI @@ -32,7 +33,6 @@ rpc.run() ``` `rpc.run()` is only used if you are only running Discord RPC on the current file/instance. If there are other programs/tasks on the current instance, `rpc.run()` does not need to be used. -See documentation [here](https://github.com/Senophyx/Discord-RPC/blob/main/DOCS.md).
More examples [here](https://github.com/Senophyx/discord-rpc/tree/main/examples). diff --git a/discordrpc/__init__.py b/discordrpc/__init__.py index 9cb6ee3..e8598b3 100644 --- a/discordrpc/__init__.py +++ b/discordrpc/__init__.py @@ -1,6 +1,7 @@ from .presence import RPC from .button import Button from .exceptions import * +from .types import * from .utils import timestamp, date_to_timestamp __title__ = "Discord RPC" diff --git a/discordrpc/button.py b/discordrpc/button.py index b053e00..154220b 100644 --- a/discordrpc/button.py +++ b/discordrpc/button.py @@ -1,32 +1,7 @@ from .exceptions import * -valid_url = ["https://", "http://"] -def _payload(label:str, url:str): - if any(v in url for v in valid_url): - payloads = {"label": label, "url": url} - return payloads - else: +def Button(text:str, url:str): + if not url.startswith(("http://", "https://")): raise InvalidURL - - -def Button( - button_one_label:str, - button_one_url:str, - button_two_label:str, - button_two_url:str): - - if button_one_label == None: - raise ButtonError('"button_one_label" cannot None') - if button_one_url == None: - raise ButtonError('"button_one_url" cannot None') - if button_two_label == None: - raise ButtonError('"button_two_label" cannot None') - if button_two_url == None: - raise ButtonError('"button_two_url" cannot None') - - btn_one = _payload(label=button_one_label, url=button_one_url) - btn_two = _payload(label=button_two_label, url=button_two_url) - payloads = [btn_one, btn_two] - - return payloads \ No newline at end of file + return {"label": text, "url": url} diff --git a/discordrpc/exceptions.py b/discordrpc/exceptions.py index 1d8f3da..740152e 100644 --- a/discordrpc/exceptions.py +++ b/discordrpc/exceptions.py @@ -28,7 +28,11 @@ class ButtonError(RPCException): def __init__(self, message: str = None): super().__init__(message=message) -# https://github.com/Senophyx/Discord-RPC/issues/28#issuecomment-2301287350 class InvalidActivityType(RPCException): + def __init__(self, message): + super().__init__(f"Activity type must be , not {message}") + +# https://github.com/Senophyx/Discord-RPC/issues/28#issuecomment-2301287350 +class ActivityTypeDisabled(RPCException): def __init__(self): super().__init__(f"Activity Type 1 and 4 currently disabled. See https://github.com/Senophyx/Discord-RPC/issues/28#issuecomment-2301287350") \ No newline at end of file diff --git a/discordrpc/presence.py b/discordrpc/presence.py index fba93b3..c8ebfee 100644 --- a/discordrpc/presence.py +++ b/discordrpc/presence.py @@ -6,6 +6,7 @@ import uuid import re from .exceptions import * +from .types import * from .utils import remove_none import logging import time @@ -55,27 +56,32 @@ def _setup(self): def set_activity( self, - state: str=None, details:str=None, act_type:int=0, + state: str=None, details:str=None, act_type:Activity=Activity.Playing, ts_start:int=None, ts_end:int=None, large_image:str=None, large_text:str=None, small_image:str=None, small_text:str=None, party_id:str=None, party_size:list=None, join_secret:str=None, spectate_secret:str=None, match_secret:str=None, buttons:list=None - ): + ) -> bool: if type(party_id) == int: party_id = str(party_id) + if type(act_type) != Activity: + raise InvalidActivityType(type(act_type)) + # https://github.com/Senophyx/Discord-RPC/issues/28#issuecomment-2301287350 - invalidType = ["1", "4"] - if any(invtype in str(act_type) for invtype in invalidType): - raise InvalidActivityType() + if act_type in [Activity.Streaming, Activity.Custom]: + raise ActivityTypeDisabled() + + if buttons and len(buttons) > 2: + raise ButtonError("Max 2 buttons allowed") act = { "state": state, "details": details, - "type": act_type, + "type": act_type.value, "timestamps": { "start": ts_start, "end": ts_end @@ -113,9 +119,14 @@ def set_activity( if not self.ipc.connected: return - self.ipc._send(payload, OP_FRAME) - self.is_running = True - log.info('RPC set') + try: + self.ipc._send(payload, OP_FRAME) + self.is_running = True + log.info('RPC set') + return True + except Exception as e: + log.error('Failed to set RPC') + self.disconnect() def disconnect(self): if not self.ipc.connected: @@ -146,16 +157,19 @@ def __init__(self, app_id, exit_if_discord_close, exit_on_disconnect): try: self.socket = open(path, "w+b") except OSError as e: - if not self.exit_if_discord_close: - raise Error("Failed to open {!r}: {}".format(path, e)) + if self.exit_if_discord_close: + log.debug("Failed to open {!r}: {}".format(path, e)) + raise DiscordNotOpened() + else: + log.debug("Discord seems to be close.") else: break else: - if not self.exit_if_discord_close: + if self.exit_if_discord_close: raise DiscordNotOpened() else: - log.debug("Discord seems to be close.") + log.warning("Discord is closed") self.connected = False if self.connected: @@ -208,10 +222,14 @@ def handshake(self): raise InvalidID def disconnect(self): - self._send({}, OP_CLOSE) - - self.socket.close() + try: + self._send({}, OP_CLOSE) + self.socket.close() + except Exception as e: + log.debug("Socket closed before command was received") + self.socket = None + self.connected = False log.warning("Closing RPC") if self.exit_on_disconnect: @@ -239,10 +257,10 @@ def __init__(self, app_id, exit_if_discord_close, exit_on_disconnect): pass else: - if not self.exit_if_discord_close: + if self.exit_if_discord_close: raise DiscordNotOpened() else: - log.debug("Discord seems to be close.") + log.warning("Discord is closed") self.connected = False if self.connected: @@ -285,12 +303,16 @@ def handshake(self): raise InvalidID def disconnect(self): - self._send({}, OP_CLOSE) + try: + self._send({}, OP_CLOSE) + self.socket.shutdown(socket.SHUT_RDWR) + self.socket.close() + except Exception as e: + log.debug("Socket closed before command was received") - self.socket.shutdown(socket.SHUT_RDWR) - self.socket.close() self.socket = None + self.connected = False log.warning("Closing RPC") if self.exit_on_disconnect: - sys.exit() + sys.exit() diff --git a/discordrpc/types.py b/discordrpc/types.py new file mode 100644 index 0000000..31251c0 --- /dev/null +++ b/discordrpc/types.py @@ -0,0 +1,12 @@ +from enum import Enum + + +# https://discord.com/developers/docs/events/gateway-events#activity-object-activity-types + +class Activity(Enum): + Playing = 0 + Streaming = 1 + Listening = 2 + Watching = 3 + Custom = 4 + Competing = 5 diff --git a/examples/rpc-with-activitytype.py b/examples/rpc-with-activitytype.py new file mode 100644 index 0000000..da30c87 --- /dev/null +++ b/examples/rpc-with-activitytype.py @@ -0,0 +1,21 @@ +import discordrpc +from discordrpc import Activity +import time + + +rpc = discordrpc.RPC(app_id=123456789) + + +current_time = int(time.time()) +finish_time = current_time + 200 + +rpc.set_activity( + state="With activity type", + details="Music", + act_type=Activity.Listening, + ts_start=current_time, + ts_end=finish_time +) + + +rpc.run() diff --git a/examples/rpc-with-button.py b/examples/rpc-with-button.py index 388b307..436ae4c 100644 --- a/examples/rpc-with-button.py +++ b/examples/rpc-with-button.py @@ -1,21 +1,17 @@ import discordrpc -from discordrpc.button import Button +from discordrpc import Button rpc = discordrpc.RPC(app_id=1234567891011) -button = Button( - button_one_label="Repository", - button_one_url="https://github.com/Senophyx/discord-rpc", - button_two_label="Discord Server", - button_two_url="https://discord.gg/qpT2AeYZRN" - ) rpc.set_activity( state="Made by Senophyx", details="Discord-RPC", - buttons=button + buttons=[ + Button("Repository", "https://github.com/Senophyx/discord-rpc"), + Button("Discord", "https://discord.gg/qpT2AeYZRN"), + ] ) - rpc.run() \ No newline at end of file diff --git a/setup.py b/setup.py index db4e658..4b1e249 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,11 @@ from setuptools import setup, find_packages import re -with open('README.md') as f: +with open('README.md', encoding='utf-8') as f: long_description = f.read() version = '' -with open('discordrpc/__init__.py') as f: +with open('discordrpc/__init__.py', encoding='utf-8') as f: version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) if not version: