diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index ae43e83..33eff25 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -4,8 +4,8 @@ jobs: lint_python: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 - run: pip install --upgrade pip wheel setuptools - run: pip install bandit black codespell flake8 isort mypy pytest pyupgrade safety - run: bandit --recursive --skip B101,B311 . diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 4e1ef42..74240a7 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -13,19 +13,19 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/README.md b/README.md index b66b980..8bf4d70 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![GitHub stars](https://img.shields.io/github/stars/qwertyquerty/pypresence.svg?style=for-the-badge&label=Stars)](https://github.com/qwertyquerty/pypresence) [![license](https://img.shields.io/github/license/qwertyquerty/pypresence.svg?style=for-the-badge)](https://github.com/qwertyquerty/pypresence/blob/master/LICENSE) ![GitHub last commit](https://img.shields.io/github/last-commit/qwertyquerty/pypresence.svg?style=for-the-badge) ![GitHub top language](https://img.shields.io/github/languages/top/qwertyquerty/pypresence.svg?style=for-the-badge) ![PyPI](https://img.shields.io/pypi/v/pypresence.svg?style=for-the-badge) -## NOTE: Only Python versions 3.8 and above are supported. +## NOTE: Only Python versions 3.9 and above are supported. ### [Documentation](https://qwertyquerty.github.io/pypresence/html/index.html), [Discord Server](https://discord.gg/JF3kg77), [Patreon](https://www.patreon.com/qwertyquerty) diff --git a/pypresence/__init__.py b/pypresence/__init__.py index 5f4e8a6..cf17837 100644 --- a/pypresence/__init__.py +++ b/pypresence/__init__.py @@ -15,4 +15,4 @@ __author__ = 'qwertyquerty' __copyright__ = 'Copyright 2018 - Current qwertyquerty' __license__ = 'MIT' -__version__ = '4.3.0' +__version__ = '4.4.0' diff --git a/pypresence/baseclient.py b/pypresence/baseclient.py index 78e7d11..917c4d7 100644 --- a/pypresence/baseclient.py +++ b/pypresence/baseclient.py @@ -1,11 +1,10 @@ +from __future__ import annotations + import asyncio import inspect import json -import os import struct import sys -import tempfile -from typing import Union, Optional # TODO: Get rid of this import * lol from .exceptions import * @@ -30,8 +29,8 @@ def __init__(self, client_id: str, **kwargs): else: self.update_event_loop(get_event_loop()) - self.sock_reader: Optional[asyncio.StreamReader] = None - self.sock_writer: Optional[asyncio.StreamWriter] = None + self.sock_reader: asyncio.StreamReader | None = None + self.sock_writer: asyncio.StreamWriter | None = None self.client_id = client_id @@ -52,7 +51,7 @@ def __init__(self, client_id: str, **kwargs): else: err_handler = self._err_handle - loop.set_exception_handler(err_handler) + self.loop.set_exception_handler(err_handler) self.handler = handler if getattr(self, "on_event", None): # Tasty bad code ;^) @@ -88,7 +87,7 @@ async def read_output(self): raise ServerError(payload["data"]["message"]) return payload - def send_data(self, op: int, payload: Union[dict, Payload]): + def send_data(self, op: int, payload: dict | Payload): if isinstance(payload, Payload): payload = payload.data payload = json.dumps(payload) @@ -110,7 +109,7 @@ async def handshake(self): try: if sys.platform == 'linux' or sys.platform == 'darwin': self.sock_reader, self.sock_writer = await asyncio.wait_for(asyncio.open_unix_connection(ipc_path), self.connection_timeout) - elif sys.platform == 'win32' or sys.platform == 'win64': + elif sys.platform == 'win32': self.sock_reader = asyncio.StreamReader(loop=self.loop) reader_protocol = asyncio.StreamReaderProtocol(self.sock_reader, loop=self.loop) self.sock_writer, _ = await asyncio.wait_for(self.loop.create_pipe_connection(lambda: reader_protocol, ipc_path), self.connection_timeout) diff --git a/pypresence/client.py b/pypresence/client.py index 7deaff5..76697c8 100644 --- a/pypresence/client.py +++ b/pypresence/client.py @@ -1,3 +1,4 @@ +from __future__ import annotations import asyncio import inspect import struct @@ -53,7 +54,7 @@ def on_event(self, data): self.sock_reader._transport = None else: self.sock_reader._paused = True - + end = 0 while end < len(data): # While chunks are available in data @@ -99,9 +100,9 @@ def get_channels(self, guild_id: str): self.send_data(1, payload) return self.loop.run_until_complete(self.read_output()) - def set_user_voice_settings(self, user_id: str, pan_left: float = None, - pan_right: float = None, volume: int = None, - mute: bool = None): + def set_user_voice_settings(self, user_id: str, pan_left: float | None = None, + pan_right: float | None = None, volume: int | None = None, + mute: bool | None = None): payload = Payload.set_user_voice_settings(user_id, pan_left, pan_right, volume, mute) self.send_data(1, payload) return self.loop.run_until_complete(self.read_output()) @@ -122,21 +123,21 @@ def select_text_channel(self, channel_id: str): return self.loop.run_until_complete(self.read_output()) def set_activity(self, pid: int = os.getpid(), - activity_type: ActivityType = None, - state: str = None, details: str = None, - start: int = None, 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: str = None, spectate: str = None, - match: str = None, buttons: list = None, + activity_type: ActivityType | None = None, + state: str | None = None, details: str | None = None, + start: int | None = None, end: int | None = None, + large_image: str | None = None, large_text: str | None = None, + small_image: str | None = None, small_text: str | None = None, + party_id: str | None = None, party_size: list | None = None, + join: str | None = None, spectate: str | None = None, + match: str | None = None, buttons: list | None = None, instance: bool = True): payload = Payload.set_activity(pid=pid, activity_type=activity_type, state=state, details=details, start=start, end=end, large_image=large_image, large_text=large_text, small_image=small_image, small_text=small_text, party_id=party_id, party_size=party_size, join=join, spectate=spectate, match=match, buttons=buttons, instance=instance, activity=True) - + self.send_data(1, payload) return self.loop.run_until_complete(self.read_output()) @@ -164,11 +165,11 @@ def get_voice_settings(self): self.send_data(1, payload) return self.loop.run_until_complete(self.read_output()) - def set_voice_settings(self, _input: dict = None, output: dict = None, - mode: dict = None, automatic_gain_control: bool = None, - echo_cancellation: bool = None, noise_suppression: bool = None, - qos: bool = None, silence_warning: bool = None, - deaf: bool = None, mute: bool = None): + def set_voice_settings(self, _input: dict | None = None, output: dict | None = None, + mode: dict | None = None, automatic_gain_control: bool | None = None, + echo_cancellation: bool | None = None, noise_suppression: bool | None = None, + qos: bool | None = None, silence_warning: bool | None = None, + deaf: bool | None = None, mute: bool | None = None): payload = Payload.set_voice_settings(_input, output, mode, automatic_gain_control, echo_cancellation, noise_suppression, qos, silence_warning, deaf, mute) self.send_data(1, payload) @@ -284,9 +285,9 @@ async def get_channels(self, guild_id: str): self.send_data(1, payload) return await self.read_output() - async def set_user_voice_settings(self, user_id: str, pan_left: float = None, - pan_right: float = None, volume: int = None, - mute: bool = None): + async def set_user_voice_settings(self, user_id: str, pan_left: float | None = None, + pan_right: float | None = None, volume: int | None = None, + mute: bool | None = None): payload = Payload.set_user_voice_settings(user_id, pan_left, pan_right, volume, mute) self.send_data(1, payload) return await self.read_output() @@ -307,15 +308,15 @@ async def select_text_channel(self, channel_id: str): return await self.read_output() async def set_activity(self, pid: int = os.getpid(), - activity_type: ActivityType = None, - state: str = None, details: str = None, - start: int = None, 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: str = None, spectate: str = None, - buttons: list = None, - match: str = None, instance: bool = True): + activity_type: ActivityType | None = None, + state: str | None = None, details: str | None = None, + start: int | None = None, end: int | None = None, + large_image: str | None = None, large_text: str | None = None, + small_image: str | None = None, small_text: str | None = None, + party_id: str | None = None, party_size: list | None = None, + join: str | None = None, spectate: str | None = None, + buttons: list | None = None, + match: str | None = None, instance: bool = True): payload = Payload.set_activity(pid, activity_type, state, details, start, end, large_image, large_text, small_image, small_text, party_id, party_size, join, spectate, match, buttons, instance, activity=True) @@ -346,11 +347,11 @@ async def get_voice_settings(self): self.send_data(1, payload) return await self.read_output() - async def set_voice_settings(self, _input: dict = None, output: dict = None, - mode: dict = None, automatic_gain_control: bool = None, - echo_cancellation: bool = None, noise_suppression: bool = None, - qos: bool = None, silence_warning: bool = None, - deaf: bool = None, mute: bool = None): + async def set_voice_settings(self, _input: dict | None = None, output: dict | None = None, + mode: dict | None = None, automatic_gain_control: bool | None = None, + echo_cancellation: bool | None = None, noise_suppression: bool | None = None, + qos: bool | None = None, silence_warning: bool | None = None, + deaf: bool | None = None, mute: bool | None = None): payload = Payload.set_voice_settings(_input, output, mode, automatic_gain_control, echo_cancellation, noise_suppression, qos, silence_warning, deaf, mute) self.send_data(1, payload) diff --git a/pypresence/exceptions.py b/pypresence/exceptions.py index 5fb8d97..f77f210 100644 --- a/pypresence/exceptions.py +++ b/pypresence/exceptions.py @@ -1,5 +1,8 @@ +from __future__ import annotations + + class PyPresenceException(Exception): - def __init__(self, message: str = None): + def __init__(self, message: str | None = None): if message is None: message = 'An error has occurred within PyPresence' super().__init__(message) @@ -16,7 +19,7 @@ def __init__(self): class InvalidArgument(PyPresenceException): - def __init__(self, expected, received, description: str = None): + def __init__(self, expected, received, description: str | None = None): description = '\n{0}'.format(description) if description else '' super().__init__('Bad argument passed. Expected {0} but got {1} instead{2}'.format(expected, received, description) @@ -29,7 +32,7 @@ def __init__(self, message: str): class DiscordError(PyPresenceException): - def __init__(self, code: int, message: str, override=False): + def __init__(self, code: int, message: str, override: bool = False): self.code = code self.message = message super().__init__('Error Code: {0} Message: {1}'.format(code, message) if not override else message) diff --git a/pypresence/payloads.py b/pypresence/payloads.py index b17a120..8453e1c 100644 --- a/pypresence/payloads.py +++ b/pypresence/payloads.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import json import os import time -from typing import List, Union from .utils import remove_none from .types import ActivityType @@ -23,15 +24,15 @@ def time(): @classmethod def set_activity(cls, pid: int = os.getpid(), - activity_type: ActivityType = None, - state: str = None, details: str = None, - start: int = None, 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: str = None, spectate: str = None, - match: str = None, buttons: list = None, - instance: bool = True, activity: Union[bool, None] = True, + activity_type: ActivityType | None = None, + state: str | None = None, details: str | None = None, + start: int | None = None, end: int | None = None, + large_image: str | None = None, large_text: str | None = None, + small_image: str | None = None, small_text: str | None = None, + party_id: str | None = None, party_size: list | None = None, + join: str | None = None, spectate: str | None = None, + match: str | None = None, buttons: list | None = None, + instance: bool = True, activity: bool | None = True, _rn: bool = True): # They should already be an int because we give typehints, but some people are fucking stupid and use @@ -91,7 +92,7 @@ def set_activity(cls, pid: int = os.getpid(), return cls(payload, clear) @classmethod - def authorize(cls, client_id: str, scopes: List[str]): + def authorize(cls, client_id: str, scopes: list[str]): payload = { "cmd": "AUTHORIZE", "args": { @@ -162,9 +163,9 @@ def get_channel(cls, channel_id: str): return cls(payload) @classmethod - def set_user_voice_settings(cls, user_id: str, pan_left: float = None, - pan_right: float = None, volume: int = None, - mute: bool = None): + def set_user_voice_settings(cls, user_id: str, pan_left: float | None = None, + pan_right: float | None = None, volume: int | None = None, + mute: bool | None = None): payload = { "cmd": "SET_USER_VOICE_SETTINGS", "args": { @@ -254,11 +255,11 @@ def get_voice_settings(cls): return cls(payload) @classmethod - def set_voice_settings(cls, _input: dict = None, output: dict = None, - mode: dict = None, automatic_gain_control: bool = None, - echo_cancellation: bool = None, noise_suppression: bool = None, - qos: bool = None, silence_warning: bool = None, - deaf: bool = None, mute: bool = None): + def set_voice_settings(cls, _input: dict | None = None, output: dict | None = None, + mode: dict | None = None, automatic_gain_control: bool | None = None, + echo_cancellation: bool | None = None, noise_suppression: bool | None = None, + qos: bool | None = None, silence_warning: bool | None = None, + deaf: bool | None = None, mute: bool | None = None): payload = { "cmd": "SET_VOICE_SETTINGS", "args": { diff --git a/pypresence/presence.py b/pypresence/presence.py index 33f6e47..23e5240 100644 --- a/pypresence/presence.py +++ b/pypresence/presence.py @@ -1,11 +1,10 @@ -import json +from __future__ import annotations import os -import time import sys from .baseclient import BaseClient from .payloads import Payload -from .utils import remove_none, get_event_loop +from .utils import get_event_loop from .types import ActivityType @@ -15,15 +14,15 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def update(self, pid: int = os.getpid(), - activity_type: ActivityType = None, - state: str = None, details: str = None, - start: int = None, 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: str = None, spectate: str = None, - match: str = None, buttons: list = None, - instance: bool = True, payload_override: dict = None): + activity_type: ActivityType | None = None, + state: str | None = None, details: str | None = None, + start: int | None = None, end: int | None = None, + large_image: str | None = None, large_text: str | None = None, + small_image: str | None = None, small_text: str | None = None, + party_id: str | None = None, party_size: list | None = None, + join: str | None = None, spectate: str | None = None, + match: str | None = None, buttons: list | None = None, + instance: bool = True, payload_override: dict | None = None): if payload_override is None: payload = Payload.set_activity(pid=pid, activity_type=activity_type, state=state, details=details, @@ -48,7 +47,7 @@ def connect(self): def close(self): self.send_data(2, {'v': 1, 'client_id': self.client_id}) self.loop.close() - if sys.platform == 'win32' or sys.platform == 'win64': + if sys.platform == 'win32': self.sock_writer._call_connection_lost(None) @@ -58,14 +57,14 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs, isasync=True) async def update(self, pid: int = os.getpid(), - activity_type: ActivityType = None, - state: str = None, details: str = None, - start: int = None, 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: str = None, spectate: str = None, - match: str = None, buttons: list = None, + activity_type: ActivityType | None = None, + state: str | None = None, details: str | None = None, + start: int | None = None, end: int | None = None, + large_image: str | None = None, large_text: str | None = None, + small_image: str | None = None, small_text: str | None = None, + party_id: str | None = None, party_size: list | None = None, + join: str | None = None, spectate: str | None = None, + match: str | None = None, buttons: list | None = None, instance: bool = True): payload = Payload.set_activity(pid=pid, activity_type=activity_type, state=state, details=details, start=start, end=end, large_image=large_image, large_text=large_text, @@ -87,5 +86,5 @@ async def connect(self): def close(self): self.send_data(2, {'v': 1, 'client_id': self.client_id}) self.loop.close() - if sys.platform == 'win32' or sys.platform == 'win64': + if sys.platform == 'win32': self.sock_writer._call_connection_lost(None) diff --git a/pypresence/py.typed b/pypresence/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/pypresence/utils.py b/pypresence/utils.py index 2b8f66a..aba3110 100644 --- a/pypresence/utils.py +++ b/pypresence/utils.py @@ -1,14 +1,10 @@ """Util functions that are needed but messy.""" import asyncio -import json import os import sys import tempfile -import time import socket -from .exceptions import PyPresenceException - def remove_none(d: dict): for item in d.copy(): @@ -22,9 +18,9 @@ def remove_none(d: dict): return d -def test_ipc_path(path): +def test_ipc_path(path) -> bool: '''Tests an IPC pipe to ensure that it actually works''' - if sys.platform == 'win32' or sys.platform == 'win64': + if sys.platform == 'win32': with open(path): return True else: @@ -47,7 +43,7 @@ def get_ipc_path(pipe=None): paths = ['.'] else: return - + for path in paths: full_path = os.path.abspath(os.path.join(tempdir, path)) if sys.platform == 'win32' or os.path.isdir(full_path): @@ -56,7 +52,7 @@ def get_ipc_path(pipe=None): return entry.path -def get_event_loop(force_fresh=False): +def get_event_loop(force_fresh: bool = False): if force_fresh: return asyncio.new_event_loop() try: diff --git a/setup.py b/setup.py index d72d69c..d4482a4 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ url='https://github.com/qwertyquerty/pypresence', version=pypresence.__version__, packages=['pypresence'], - python_requires='>=3.8', + python_requires='>=3.9', platforms=['Windows', 'Linux', 'OSX'], zip_safe=True, license='MIT', @@ -32,13 +32,15 @@ 'Operating System :: MacOS :: MacOS X', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: Implementation :: CPython', + 'Typing :: Typed', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries :: Python Modules',