diff --git a/scripts/__pycache__/webui-rpc.cpython-310.pyc b/scripts/__pycache__/webui-rpc.cpython-310.pyc new file mode 100644 index 0000000..ba90424 Binary files /dev/null and b/scripts/__pycache__/webui-rpc.cpython-310.pyc differ diff --git a/scripts/pypresence-4.2.1.dist-info/INSTALLER b/scripts/pypresence-4.2.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/scripts/pypresence-4.2.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/scripts/pypresence-4.2.1.dist-info/LICENSE b/scripts/pypresence-4.2.1.dist-info/LICENSE new file mode 100644 index 0000000..49e38b1 --- /dev/null +++ b/scripts/pypresence-4.2.1.dist-info/LICENSE @@ -0,0 +1,12 @@ +Copyright 2021 qwertyquerty + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/scripts/pypresence-4.2.1.dist-info/METADATA b/scripts/pypresence-4.2.1.dist-info/METADATA new file mode 100644 index 0000000..0167b77 --- /dev/null +++ b/scripts/pypresence-4.2.1.dist-info/METADATA @@ -0,0 +1,93 @@ +Metadata-Version: 2.1 +Name: pypresence +Version: 4.2.1 +Summary: Discord RPC client written in Python +Home-page: https://github.com/qwertyquerty/pypresence +Author: qwertyquerty +License: MIT +Keywords: discord rich presence pypresence rpc api wrapper gamers chat irc +Platform: Windows +Platform: Linux +Platform: OSX +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Communications :: Chat +Classifier: Framework :: AsyncIO +Requires-Python: >=3.5 +Description-Content-Type: text/markdown +License-File: LICENSE + +> A Discord Rich Presence Client in Python? Looks like you've come to the right place. + +Written by: [qwertyquerty](https://github.com/qwertyquerty) + +Notable Contributors: [GiovanniMCMXCIX](https://github.com/GiovanniMCMXCIX), [GhostofGoes](https://github.com/GhostofGoes) + +[![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. + + +## NOTICE: Activity() class has been removed in 4.0.0 + + +### [Documentation](https://qwertyquerty.github.io/pypresence/html/index.html), [Discord Server](https://discord.gg/JF3kg77), [Patreon](https://www.patreon.com/qwertyquerty) + +---------- + +**Use this badge in your project's Readme to show you're using pypresence! The markdown code is below.** + +[![pypresence](https://img.shields.io/badge/using-pypresence-00bb88.svg?style=for-the-badge&logo=discord&logoWidth=20)](https://github.com/qwertyquerty/pypresence) + +```markdown +[![pypresence](https://img.shields.io/badge/using-pypresence-00bb88.svg?style=for-the-badge&logo=discord&logoWidth=20)](https://github.com/qwertyquerty/pypresence) +``` + + +---------- + +# Installation + +Install pypresence with **`pip`** + +For the latest development version: + +### `pip install https://github.com/qwertyquerty/pypresence/archive/master.zip` + +For the latest stable version: + +### `pip install pypresence` + +---------- + + +# Documentation + +> **NOTE**: You need an **authorized app** to do anything besides rich presence! + +#### [pypresence Documentation](https://qwertyquerty.github.io/pypresence/html/index.html) +#### [Discord Rich Presence Documentation](https://discordapp.com/developers/docs/rich-presence/how-to) +#### [Discord RPC Documentation](https://discordapp.com/developers/docs/topics/rpc) +#### [pyresence Discord Support Server](https://discord.gg/JF3kg77) +#### [Discord API Support Server](https://discord.gg/discord-api) + +---------- + +# Examples + +Examples can be found in the [examples](https://github.com/qwertyquerty/pypresence/tree/master/examples) directory, and you can contribute your own examples if you wish, just read [examples.md](https://github.com/qwertyquerty/pypresence/blob/master/examples/examples.md)! + + diff --git a/scripts/pypresence-4.2.1.dist-info/RECORD b/scripts/pypresence-4.2.1.dist-info/RECORD new file mode 100644 index 0000000..1131e41 --- /dev/null +++ b/scripts/pypresence-4.2.1.dist-info/RECORD @@ -0,0 +1,22 @@ +pypresence-4.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pypresence-4.2.1.dist-info/LICENSE,sha256=IL42tAZ9O6DIG5I1n2zGEZmtvUwCbvAatrGNpeO5uqM,1458 +pypresence-4.2.1.dist-info/METADATA,sha256=UcCkjfMyBRIp2fM3u8xN9tzXqdjD34Jvrgu1x8Db87g,3954 +pypresence-4.2.1.dist-info/RECORD,, +pypresence-4.2.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pypresence-4.2.1.dist-info/WHEEL,sha256=WzZ8cwjh8l0jtULNjYq1Hpr-WCqCRgPr--TX4P5I1Wo,110 +pypresence-4.2.1.dist-info/top_level.txt,sha256=HgPRAKZXYeILcC2btf3X4WYdVa6CdfiT2Kl_O42RSuY,11 +pypresence-4.2.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +pypresence/__init__.py,sha256=sSVp9Oda3CZzK9stAnTsxXXwoPPbGIiujxjlRqaXIAY,385 +pypresence/__pycache__/__init__.cpython-311.pyc,, +pypresence/__pycache__/baseclient.cpython-311.pyc,, +pypresence/__pycache__/client.cpython-311.pyc,, +pypresence/__pycache__/exceptions.cpython-311.pyc,, +pypresence/__pycache__/payloads.cpython-311.pyc,, +pypresence/__pycache__/presence.cpython-311.pyc,, +pypresence/__pycache__/utils.cpython-311.pyc,, +pypresence/baseclient.py,sha256=GO-CTAL-arUPX06-W-RJuMWjnatzJ1AExJD4V3tWfg8,4520 +pypresence/client.py,sha256=qEAjRdN9Q_IUzzAKe4v1587oor6pj4HDq86GZggcwWM,15461 +pypresence/exceptions.py,sha256=QKjRCAPf-_NakKxxOama1-l9ircpMr2rhKTCCbcbQi0,1717 +pypresence/payloads.py,sha256=0-a_Mau58mqEorJaoCEFTB1VpmCU61CuKV8jFTQhmEE,8560 +pypresence/presence.py,sha256=5B-MvOCtvfQVwwjn5d4zwE1EqUSriWJ7WOkYLokmWr4,3629 +pypresence/utils.py,sha256=hOdCaK8Ic-vp-RMD7lKsOiaTdbC8gCOmUg53N5lTsvs,2056 diff --git a/scripts/pypresence-4.2.1.dist-info/REQUESTED b/scripts/pypresence-4.2.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/scripts/pypresence-4.2.1.dist-info/WHEEL b/scripts/pypresence-4.2.1.dist-info/WHEEL new file mode 100644 index 0000000..b733a60 --- /dev/null +++ b/scripts/pypresence-4.2.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/scripts/pypresence-4.2.1.dist-info/top_level.txt b/scripts/pypresence-4.2.1.dist-info/top_level.txt new file mode 100644 index 0000000..5272687 --- /dev/null +++ b/scripts/pypresence-4.2.1.dist-info/top_level.txt @@ -0,0 +1 @@ +pypresence diff --git a/scripts/pypresence-4.2.1.dist-info/zip-safe b/scripts/pypresence-4.2.1.dist-info/zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/scripts/pypresence-4.2.1.dist-info/zip-safe @@ -0,0 +1 @@ + diff --git a/scripts/pypresence/__init__.py b/scripts/pypresence/__init__.py new file mode 100644 index 0000000..75450dc --- /dev/null +++ b/scripts/pypresence/__init__.py @@ -0,0 +1,17 @@ +""" +Python RPC Client for Discord +----------------------------- +By: qwertyquerty and LewdNeko +""" + +from .baseclient import BaseClient +from .client import Client, AioClient +from .exceptions import * +from .presence import Presence, AioPresence + + +__title__ = 'pypresence' +__author__ = 'qwertyquerty' +__copyright__ = 'Copyright 2018 qwertyquerty' +__license__ = 'MIT' +__version__ = '4.2.1' diff --git a/scripts/pypresence/__pycache__/__init__.cpython-311.pyc b/scripts/pypresence/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..bc0f86c Binary files /dev/null and b/scripts/pypresence/__pycache__/__init__.cpython-311.pyc differ diff --git a/scripts/pypresence/__pycache__/baseclient.cpython-311.pyc b/scripts/pypresence/__pycache__/baseclient.cpython-311.pyc new file mode 100644 index 0000000..5310d2c Binary files /dev/null and b/scripts/pypresence/__pycache__/baseclient.cpython-311.pyc differ diff --git a/scripts/pypresence/__pycache__/client.cpython-311.pyc b/scripts/pypresence/__pycache__/client.cpython-311.pyc new file mode 100644 index 0000000..ecf2280 Binary files /dev/null and b/scripts/pypresence/__pycache__/client.cpython-311.pyc differ diff --git a/scripts/pypresence/__pycache__/exceptions.cpython-311.pyc b/scripts/pypresence/__pycache__/exceptions.cpython-311.pyc new file mode 100644 index 0000000..cc1e13c Binary files /dev/null and b/scripts/pypresence/__pycache__/exceptions.cpython-311.pyc differ diff --git a/scripts/pypresence/__pycache__/payloads.cpython-311.pyc b/scripts/pypresence/__pycache__/payloads.cpython-311.pyc new file mode 100644 index 0000000..3e70c23 Binary files /dev/null and b/scripts/pypresence/__pycache__/payloads.cpython-311.pyc differ diff --git a/scripts/pypresence/__pycache__/presence.cpython-311.pyc b/scripts/pypresence/__pycache__/presence.cpython-311.pyc new file mode 100644 index 0000000..cc02745 Binary files /dev/null and b/scripts/pypresence/__pycache__/presence.cpython-311.pyc differ diff --git a/scripts/pypresence/__pycache__/utils.cpython-311.pyc b/scripts/pypresence/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..c2688cf Binary files /dev/null and b/scripts/pypresence/__pycache__/utils.cpython-311.pyc differ diff --git a/scripts/pypresence/baseclient.py b/scripts/pypresence/baseclient.py new file mode 100644 index 0000000..ac3fae7 --- /dev/null +++ b/scripts/pypresence/baseclient.py @@ -0,0 +1,123 @@ +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 * +from .payloads import Payload +from .utils import get_ipc_path, get_event_loop + + +class BaseClient: + + def __init__(self, client_id: str, **kwargs): + pipe = kwargs.get('pipe', None) + loop = kwargs.get('loop', None) + handler = kwargs.get('handler', None) + self.isasync = kwargs.get('isasync', False) + + client_id = str(client_id) + self.ipc_path = get_ipc_path(pipe) + + if not self.ipc_path: + raise DiscordNotFound + + if loop is not None: + self.update_event_loop(loop) + else: + self.update_event_loop(get_event_loop()) + + self.sock_reader: Optional[asyncio.StreamReader] = None + self.sock_writer: Optional[asyncio.StreamWriter] = None + + self.client_id = client_id + + if handler is not None: + if not inspect.isfunction(handler): + raise PyPresenceException('Error handler must be a function.') + args = inspect.getfullargspec(handler).args + if args[0] == 'self': + args = args[1:] + if len(args) != 2: + raise PyPresenceException('Error handler should only accept two arguments.') + + if self.isasync: + if not inspect.iscoroutinefunction(handler): + raise InvalidArgument('Coroutine', 'Subroutine', 'You are running async mode - ' + 'your error handler should be awaitable.') + err_handler = self._async_err_handle + else: + err_handler = self._err_handle + + loop.set_exception_handler(err_handler) + self.handler = handler + + if getattr(self, "on_event", None): # Tasty bad code ;^) + self._events_on = True + else: + self._events_on = False + + def update_event_loop(self, loop): + # noinspection PyAttributeOutsideInit + self.loop = loop + asyncio.set_event_loop(self.loop) + + def _err_handle(self, loop, context: dict): + result = self.handler(context['exception'], context['future']) + if inspect.iscoroutinefunction(self.handler): + loop.run_until_complete(result) + + # noinspection PyUnusedLocal + async def _async_err_handle(self, loop, context: dict): + await self.handler(context['exception'], context['future']) + + async def read_output(self): + try: + preamble = await self.sock_reader.read(8) + status_code, length = struct.unpack(' 2 * self.sock_reader._limit): + try: + self.sock_reader._transport.pause_reading() + except NotImplementedError: + self.sock_reader._transport = None + else: + self.sock_reader._paused = True + + end = 0 + while end < len(data): + # While chunks are available in data + start = end + 8 + status_code, length = struct.unpack(' 2 * self.sock_reader._limit): + try: + self.sock_reader._transport.pause_reading() + except NotImplementedError: + self.sock_reader._transport = None + else: + self.sock_reader._paused = True + + payload = json.loads(data[8:].decode('utf-8')) + + if payload["evt"] is not None: + evt = payload["evt"].lower() + if evt in self._events: + await self._events[evt](payload["data"]) + elif evt == 'error': + raise DiscordError(payload["data"]["code"], payload["data"]["message"]) + + async def authorize(self, client_id: str, scopes: List[str]): + payload = Payload.authorize(client_id, scopes) + self.send_data(1, payload) + return await self.read_output() + + async def authenticate(self, token: str): + payload = Payload.authenticate(token) + self.send_data(1, payload) + return await self.read_output() + + async def get_guilds(self): + payload = Payload.get_guilds() + self.send_data(1, payload) + return await self.read_output() + + async def get_guild(self, guild_id: str): + payload = Payload.get_guild(guild_id) + self.send_data(1, payload) + return await self.read_output() + + async def get_channel(self, channel_id: str): + payload = Payload.get_channel(channel_id) + self.send_data(1, payload) + return await self.read_output() + + async def get_channels(self, guild_id: str): + payload = Payload.get_channels(guild_id) + 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): + payload = Payload.set_user_voice_settings(user_id, pan_left, pan_right, volume, mute) + self.send_data(1, payload) + return await self.read_output() + + async def select_voice_channel(self, channel_id: str): + payload = Payload.select_voice_channel(channel_id) + self.send_data(1, payload) + return await self.read_output() + + async def get_selected_voice_channel(self): + payload = Payload.get_selected_voice_channel() + self.send_data(1, payload) + return await self.read_output() + + async def select_text_channel(self, channel_id: str): + payload = Payload.select_text_channel(channel_id) + self.send_data(1, payload) + return await self.read_output() + + async def set_activity(self, pid: int = os.getpid(), + 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): + payload = Payload.set_activity(pid, state, details, start, end, large_image, large_text, + small_image, small_text, party_id, party_size, join, spectate, + match, buttons, instance, activity=True) + self.send_data(1, payload) + return await self.read_output() + + async def clear_activity(self, pid: int = os.getpid()): + payload = Payload.set_activity(pid, activity=None) + self.send_data(1, payload) + return await self.read_output() + + async def subscribe(self, event: str, args=None): + if args is None: + args = {} + payload = Payload.subscribe(event, args) + self.send_data(1, payload) + return await self.read_output() + + async def unsubscribe(self, event: str, args=None): + if args is None: + args = {} + payload = Payload.unsubscribe(event, args) + self.send_data(1, payload) + return await self.read_output() + + async def get_voice_settings(self): + payload = Payload.get_voice_settings() + 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): + 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) + return await self.read_output() + + async def capture_shortcut(self, action: str): + payload = Payload.capture_shortcut(action) + self.send_data(1, payload) + return await self.read_output() + + async def send_activity_join_invite(self, user_id: str): + payload = Payload.send_activity_join_invite(user_id) + self.send_data(1, payload) + return await self.read_output() + + async def close_activity_request(self, user_id: str): + payload = Payload.close_activity_request(user_id) + self.send_data(1, payload) + return await self.read_output() + + def close(self): + self.send_data(2, {'v': 1, 'client_id': self.client_id}) + self.sock_writer.close() + self._closed = True + self.loop.close() + + async def start(self): + await self.handshake() + + async def read(self): + return await self.read_output() diff --git a/scripts/pypresence/exceptions.py b/scripts/pypresence/exceptions.py new file mode 100644 index 0000000..281b061 --- /dev/null +++ b/scripts/pypresence/exceptions.py @@ -0,0 +1,50 @@ +class PyPresenceException(Exception): + def __init__(self, message: str = None): + if message is None: + message = 'An error has occurred within PyPresence' + super().__init__(message) + + +class DiscordNotFound(PyPresenceException): + def __init__(self): + super().__init__('Could not find Discord installed and running on this machine.') + + +class InvalidID(PyPresenceException): + def __init__(self): + super().__init__('Client ID is Invalid') + + +class InvalidPipe(PyPresenceException): + def __init__(self): + super().__init__('Pipe Not Found - Is Discord Running?') + + +class InvalidArgument(PyPresenceException): + def __init__(self, expected, received, description: str = 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) + ) + + +class ServerError(PyPresenceException): + def __init__(self, message: str): + super().__init__(message.replace(']', '').replace('[', '').capitalize()) + + +class DiscordError(PyPresenceException): + def __init__(self, code: int, message: str): + self.code = code + self.message = message + super().__init__('Error Code: {0} Message: {1}'.format(code, message)) + + +class ArgumentError(PyPresenceException): + def __init__(self): + super().__init__('Supplied function must have one argument.') + + +class EventNotFound(PyPresenceException): + def __init__(self, event): + super().__init__('No event with name {0} exists.'.format(event)) diff --git a/scripts/pypresence/payloads.py b/scripts/pypresence/payloads.py new file mode 100644 index 0000000..805670a --- /dev/null +++ b/scripts/pypresence/payloads.py @@ -0,0 +1,307 @@ +import json +import os +import time +from typing import List, Union + +from .utils import remove_none + + +class Payload: + + def __init__(self, data, clear_none=True): + if clear_none: + data = remove_none(data) + self.data = data + + def __str__(self): + return json.dumps(self.data, indent=2) + + @staticmethod + def time(): + return time.time() + + @classmethod + def set_activity(cls, pid: int = os.getpid(), + 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, + _rn: bool = True): + + # They should already be an int because we give typehints, but some people are fucking stupid and use + # IDLE or some other stupid shit. + if start: + start = int(start) + if end: + end = int(end) + + if activity is None: + act_details = None + clear = True + else: + act_details = { + "state": state, + "details": details, + "timestamps": { + "start": start, + "end": end + }, + "assets": { + "large_image": large_image, + "large_text": large_text, + "small_image": small_image, + "small_text": small_text + }, + "party": { + "id": party_id, + "size": party_size + }, + "secrets": { + "join": join, + "spectate": spectate, + "match": match + }, + "buttons": buttons, + "instance": instance + } + clear = False + + payload = { + "cmd": "SET_ACTIVITY", + "args": { + "pid": pid, + "activity": act_details + }, + "nonce": '{:.20f}'.format(cls.time()) + } + if _rn: + clear = _rn + return cls(payload, clear) + + @classmethod + def authorize(cls, client_id: str, scopes: List[str]): + payload = { + "cmd": "AUTHORIZE", + "args": { + "client_id": str(client_id), + "scopes": scopes + }, + "nonce": '{:.20f}'.format(cls.time()) + } + return cls(payload) + + @classmethod + def authenticate(cls, token: str): + payload = { + "cmd": "AUTHENTICATE", + "args": { + "access_token": token + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def get_guilds(cls): + payload = { + "cmd": "GET_GUILDS", + "args": { + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def get_guild(cls, guild_id: str): + payload = { + "cmd": "GET_GUILD", + "args": { + "guild_id": str(guild_id), + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def get_channels(cls, guild_id: str): + payload = { + "cmd": "GET_CHANNELS", + "args": { + "guild_id": str(guild_id), + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def get_channel(cls, channel_id: str): + payload = { + "cmd": "GET_CHANNEL", + "args": { + "channel_id": str(channel_id), + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + 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): + payload = { + "cmd": "SET_USER_VOICE_SETTINGS", + "args": { + "user_id": str(user_id), + "pan": { + "left": pan_left, + "right": pan_right + }, + "volume": volume, + "mute": mute + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload, True) + + @classmethod + def select_voice_channel(cls, channel_id: str): + payload = { + "cmd": "SELECT_VOICE_CHANNEL", + "args": { + "channel_id": str(channel_id), + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def get_selected_voice_channel(cls): + payload = { + "cmd": "GET_SELECTED_VOICE_CHANNEL", + "args": { + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def select_text_channel(cls, channel_id: str): + payload = { + "cmd": "SELECT_TEXT_CHANNEL", + "args": { + "channel_id": str(channel_id), + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def subscribe(cls, event: str, args=None): + if args is None: + args = {} + payload = { + "cmd": "SUBSCRIBE", + "args": args, + "evt": event.upper(), + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def unsubscribe(cls, event: str, args=None): + if args is None: + args = {} + payload = { + "cmd": "UNSUBSCRIBE", + "args": args, + "evt": event.upper(), + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def get_voice_settings(cls): + payload = { + "cmd": "GET_VOICE_SETTINGS", + "args": { + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + 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): + payload = { + "cmd": "SET_VOICE_SETTINGS", + "args": { + "input": _input, + "output": output, + "mode": mode, + "automatic_gain_control": automatic_gain_control, + "echo_cancellation": echo_cancellation, + "noise_suppression": noise_suppression, + "qos": qos, + "silence_warning": silence_warning, + "deaf": deaf, + "mute": mute + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload, True) + + @classmethod + def capture_shortcut(cls, action: str): + payload = { + "cmd": "CAPTURE_SHORTCUT", + "args": { + "action": action.upper() + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def send_activity_join_invite(cls, user_id: str): + payload = { + "cmd": "SEND_ACTIVITY_JOIN_INVITE", + "args": { + "user_id": str(user_id) + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) + + @classmethod + def close_activity_request(cls, user_id: str): + payload = { + "cmd": "CLOSE_ACTIVITY_REQUEST", + "args": { + "user_id": str(user_id) + }, + "nonce": '{:.20f}'.format(cls.time()) + } + + return cls(payload) diff --git a/scripts/pypresence/presence.py b/scripts/pypresence/presence.py new file mode 100644 index 0000000..4801d79 --- /dev/null +++ b/scripts/pypresence/presence.py @@ -0,0 +1,87 @@ +import json +import os +import time + +from .baseclient import BaseClient +from .payloads import Payload +from .utils import remove_none, get_event_loop + + +class Presence(BaseClient): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def update(self, pid: int = os.getpid(), + 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, + _donotuse=True): + + if _donotuse is True: + payload = Payload.set_activity(pid=pid, 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) + + else: + payload = _donotuse + self.send_data(1, payload) + return self.loop.run_until_complete(self.read_output()) + + def clear(self, pid: int = os.getpid()): + payload = Payload.set_activity(pid, activity=None) + self.send_data(1, payload) + return self.loop.run_until_complete(self.read_output()) + + def connect(self): + self.update_event_loop(get_event_loop()) + self.loop.run_until_complete(self.handshake()) + + def close(self): + self.send_data(2, {'v': 1, 'client_id': self.client_id}) + self.sock_writer.close() + self.loop.close() + + +class AioPresence(BaseClient): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs, isasync=True) + + async def update(self, pid: int = os.getpid(), + 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 = Payload.set_activity(pid=pid, 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 await self.read_output() + + async def clear(self, pid: int = os.getpid()): + payload = Payload.set_activity(pid, activity=None) + self.send_data(1, payload) + return await self.read_output() + + async def connect(self): + self.update_event_loop(get_event_loop()) + await self.handshake() + + def close(self): + self.send_data(2, {'v': 1, 'client_id': self.client_id}) + self.sock_writer.close() + self.loop.close() diff --git a/scripts/pypresence/utils.py b/scripts/pypresence/utils.py new file mode 100644 index 0000000..4de8cd2 --- /dev/null +++ b/scripts/pypresence/utils.py @@ -0,0 +1,70 @@ +"""Util functions that are needed but messy.""" +import asyncio +import json +import os +import sys +import tempfile +import time + +from .exceptions import PyPresenceException + + +# Made by https://github.com/LewdNeko ;^) +def remove_none(d: dict): + for item in d.copy(): + if isinstance(d[item], dict): + if len(d[item]): + d[item] = remove_none(d[item]) + if not len(d[item]): + del d[item] + elif d[item] is None: + del d[item] + return d + + +# Returns on first IPC pipe matching Discord's +def get_ipc_path(pipe=None): + ipc = 'discord-ipc-' + if pipe: + ipc = f"{ipc}{pipe}" + + if sys.platform in ('linux', 'darwin'): + tempdir = (os.environ.get('XDG_RUNTIME_DIR') or tempfile.gettempdir()) + paths = ['.', 'snap.discord', 'app/com.discordapp.Discord'] + elif sys.platform == 'win32': + tempdir = r'\\?\pipe' + 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): + for entry in os.scandir(full_path): + if entry.name.startswith(ipc): + return entry.path + + +def get_event_loop(force_fresh=False): + if sys.platform in ('linux', 'darwin'): + if force_fresh: + return asyncio.new_event_loop() + loop = asyncio.get_event_loop() + if loop.is_closed(): + return asyncio.new_event_loop() + return loop + elif sys.platform == 'win32': + if force_fresh: + return asyncio.ProactorEventLoop() + loop = asyncio.get_event_loop() + if isinstance(loop, asyncio.ProactorEventLoop) and not loop.is_closed(): + return loop + return asyncio.ProactorEventLoop() + + +# This code used to do something. I don't know what, though. +try: # Thanks, Rapptz :^) + create_task = asyncio.ensure_future +except AttributeError: + create_task = getattr(asyncio, "async") + # No longer crashes Python 3.7 diff --git a/scripts/webui-rpc.py b/scripts/webui-rpc.py index f270f5b..8dffd0e 100644 --- a/scripts/webui-rpc.py +++ b/scripts/webui-rpc.py @@ -2,10 +2,12 @@ import gradio as gr from modules import script_callbacks from modules import ui from modules import shared +from modules.txt2img import get_batch_size import threading import time import os + github_link = "https://github.com/davehornik/sd-discordRPC" enable_dynamic_status = True @@ -16,12 +18,11 @@ def start_rpc(): print(f'Bug reporting -> {github_link}') # Check if the required packages are installed, and install them if necessary - try: - import pypresence - except ImportError: - print("Installing the missing 'pypresence' package and its dependencies") - os.system("pip install pypresence") - import pypresence + from launch import is_installed, run_pip + if not is_installed("pypresence"): + print("Installing missing 'pypresence' module and its dependencies,") + print("In case of module error after the installation, restart webui.") + run_pip("install pypresence", "pypresence") if enable_dynamic_status: print("Remember that it uses multithreading, so there may occur cases when the whole program freezes") @@ -30,17 +31,21 @@ def start_rpc(): checkpoint_info = shared.sd_model.sd_checkpoint_info model_name = os.path.basename(checkpoint_info.filename) + import pypresence + client_id = "1091507869200957450" rpc = pypresence.Presence(client_id) rpc.connect() + + time_c=time.time() rpc.update( state="Waiting for the start" if enable_dynamic_status else "Dynamic Status - *WIP*", details=model_name, large_image="unknown" if enable_dynamic_status else "auto", - start=time.time() - ) + start=time_c + ) def RPC_thread(rpc): print('RPC thread on bg starting') @@ -48,16 +53,27 @@ def start_rpc(): rpc.update() def state_watcher_thread(): + reset_time = False while True: + checkpoint_info = shared.sd_model.sd_checkpoint_info model_name = os.path.basename(checkpoint_info.filename) if shared.state.job_count == 0: + if reset_time == False: + time_c = time.time() + reset_time= True + rpc.update(large_image="a1111", details=model_name, - state="Idle", start=time.time()) + state="Idle", start=time_c) else: + if reset_time == True: + time_c = time.time() + reset_time= False + rpc.update(large_image="a1111_gen", details=model_name, - state=f'generating {shared.state.job_count} pix', start=time.time()) + state=f'Total batch of {shared.state.job_count*get_batch_size()} image/s', start=time_c) time.sleep(2) # update once per two seconds + #print(get_batch_size()) rpc_watcher = threading.Thread(target=RPC_thread, args=(rpc,), daemon=True) state_watcher = threading.Thread(target=state_watcher_thread, daemon=True)