summaryrefslogtreecommitdiff
path: root/discord_rpc/connection
diff options
context:
space:
mode:
authorJesusaves <cpntb1@ymail.com>2024-02-05 18:35:33 -0300
committerJesusaves <cpntb1@ymail.com>2024-02-05 18:35:33 -0300
commitd6d698227b4c1ba1004d371ab5fb35ec8024b5da (patch)
treef7f59ab57e081aaa8e493e1324edabca1dc4c5ae /discord_rpc/connection
parent29ffe5de3c308013742b5bd97f7d75b09bd3b427 (diff)
downloadtkinter-d6d698227b4c1ba1004d371ab5fb35ec8024b5da.tar.gz
tkinter-d6d698227b4c1ba1004d371ab5fb35ec8024b5da.tar.bz2
tkinter-d6d698227b4c1ba1004d371ab5fb35ec8024b5da.tar.xz
tkinter-d6d698227b4c1ba1004d371ab5fb35ec8024b5da.zip
Update discord_rpc to 4.0
https://pypi.org/project/discord-rpc
Diffstat (limited to 'discord_rpc/connection')
-rw-r--r--discord_rpc/connection/__init__.py0
-rw-r--r--discord_rpc/connection/ipc.py387
-rw-r--r--discord_rpc/connection/rpc.py175
3 files changed, 0 insertions, 562 deletions
diff --git a/discord_rpc/connection/__init__.py b/discord_rpc/connection/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/discord_rpc/connection/__init__.py
+++ /dev/null
diff --git a/discord_rpc/connection/ipc.py b/discord_rpc/connection/ipc.py
deleted file mode 100644
index bbb26a7..0000000
--- a/discord_rpc/connection/ipc.py
+++ /dev/null
@@ -1,387 +0,0 @@
-from __future__ import absolute_import
-import errno
-import logging
-from ..codes import errorcodes
-from ..util.utils import is_windows, range, get_temp_path, to_bytes, bytes, to_unicode, is_python3, is_callable
-import struct
-import sys
-if is_windows():
- # we're going to have to do some ugly things, because Windows sucks
- import ctypes
- GENERIC_READ = 0x80000000
- GENERIC_WRITE = 0x40000000
- OPEN_EXISTING = 0x3
- INVALID_HANDLE_VALUE = -1
- PIPE_READMODE_MESSAGE = 0x2
- ERROR_FILE_NOT_FOUND = 0x2
- ERROR_PIPE_BUSY = 0xE7
- ERROR_MORE_DATA = 0xEA
- BUFSIZE = 512
-else:
- try:
- from socket import MSG_NOSIGNAL
- _msg_flags = MSG_NOSIGNAL
- except ImportError:
- _msg_flags = 0
- try:
- from socket import SO_NOSIGPIPE
- _do_sock_opt = True
- except ImportError:
- _do_sock_opt = False
- import socket
- import fcntl
- from os import O_NONBLOCK
-
-
-class BaseConnection(object):
- """Generate IPC Connection handler."""
- # *nix specific
- __sock = None
- # Windows specific
- __pipe = None
-
- __open = False
- __logger = None
- __is_logging = False
-
- def __init__(self, log=True, logger=None, log_file=None, log_level=logging.INFO):
- if not isinstance(log, bool):
- raise TypeError('log must be of bool type!')
- if log:
- if logger is not None:
- # Panda3D notifies are similar, so we simply check if we can make the same calls as logger
- if not hasattr(logger, 'debug'):
- raise TypeError('logger must be of type logging!')
- self.__logger = logger
- else:
- self.__logger = logging.getLogger(__name__)
- log_fmt = logging.Formatter('[%(asctime)s][%(levelname)s] ' + '%(name)s - %(message)s')
- if log_file is not None and hasattr(log_file, 'strip'):
- fhandle = logging.FileHandler(log_file)
- fhandle.setLevel(log_level)
- fhandle.setFormatter(log_fmt)
- self.__logger.addHandler(fhandle)
- shandle = logging.StreamHandler(sys.stdout)
- shandle.setLevel(log_level)
- shandle.setFormatter(log_fmt)
- self.__logger.addHandler(shandle)
- self.__is_logging = True
-
- def log(self, callback_name, *args):
- if self.__logger is not None:
- if hasattr(self.__logger, callback_name) and is_callable(self.__logger.__getattribute__(callback_name)):
- self.__logger.__getattribute__(callback_name)(*args)
-
- def __open_pipe(self, pipe_name, log_type='warning'):
- """
- :param pipe_name: the named pipe string
- :param log_type: the log type to use (default 'warning')
- :return: opened(bool), try_again(bool)
- """
- if not is_windows():
- self.log('error', 'Attempted to call a Windows call on a non-Windows OS.')
- return
- pipe = ctypes.windll.kernel32.CreateFileW(pipe_name, GENERIC_READ | GENERIC_WRITE, 0, None, OPEN_EXISTING, 0,
- None)
- if pipe != INVALID_HANDLE_VALUE:
- self.__pipe = pipe
- return True, False
- err = ctypes.windll.kernel32.GetLastError()
- if err == ERROR_FILE_NOT_FOUND:
- self.log(log_type, 'File not found.')
- self.log(log_type, 'Pipe name: {}'.format(pipe_name))
- return False, False
- elif err == ERROR_PIPE_BUSY:
- if ctypes.windll.kernel32.WaitNamedPipeW(pipe_name, 10000) == 0:
- self.log(log_type, 'Pipe busy.')
- return False, False
- else:
- # try again, should be free now
- self.log('debug', 'Pipe was busy, but should be free now. Try again.')
- return False, True
- # some other error we don't care about
- self.log('debug', 'Unknown error: {}'.format(err))
- return False, False
-
- def open(self, pipe_no=None):
- if pipe_no is not None:
- if not isinstance(pipe_no, int):
- raise TypeError('pipe_no must be of type int!')
- if pipe_no not in range(0, 10):
- raise ValueError('pipe_no must be within range (0 <= pipe number < 10)!')
- if is_windows():
- # NOTE: don't forget to use a number after ipc-
- pipe_name = u'\\\\.\\pipe\\discord-ipc-{}'
- if pipe_no is not None:
- # we only care about the first value if pipe_no isn't None
- opened, try_again = self.__open_pipe(pipe_name.format(pipe_no))
- if opened:
- self.__open = True
- self.log('info', 'Connected to pipe {}, as user requested.'.format(pipe_no))
- return
- elif try_again:
- self.open(pipe_no=pipe_no)
- return
- else:
- num = 0
- while True:
- if num >= 10:
- break
- opened, try_again = self.__open_pipe(pipe_name.format(num), log_type='debug')
- if opened:
- self.__open = True
- self.log('debug', 'Automatically connected to pipe {}.'.format(num))
- return
- if try_again:
- continue
- num += 1
- # we failed to get a pipe
- self.__pipe = None
- self.log('warning', 'Could not open a connection.')
- else:
- self.__sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- if self.__sock is None or self.__sock == -1:
- self.log('warning', 'Could not open socket.')
- self.close()
- return
- try:
- fcntl.fcntl(self.__sock, fcntl.F_SETFL, O_NONBLOCK)
- except Exception as e:
- self.log('warning', e)
- self.close()
- return
- if _do_sock_opt:
- try:
- socket.setsockopt(socket.SOL_SOCKET, SO_NOSIGPIPE)
- except Exception as e:
- self.log('warning', e)
- self.log('debug', 'Attempting to use sock as is. Notify a developer if an error occurs.')
- sock_addr = get_temp_path()
- if sock_addr.endswith('/'):
- sock_addr = sock_addr[:-1]
- sock_addr += '/discord-ipc-{}'
- if pipe_no is not None:
- ret_val = self.__sock.connect_ex(sock_addr.format(pipe_no))
- if ret_val == 0:
- self.__open = True
- self.log('info', 'Connected to socket {}, as user requested.'.format(pipe_no))
- return
- else:
- self.log('warning', 'Could not open socket {}.'.format(pipe_no))
- self.close()
- else:
- for num in range(0, 10):
- ret_val = self.__sock.connect_ex(sock_addr.format(num))
- if ret_val == 0:
- self.__open = True
- self.log('debug', 'Automatically connected to socket {}.'.format(num))
- return
- self.log('warning', 'Could not open socket.')
- self.close()
-
- def write(self, data, opcode):
- if not self.connected():
- self.log('warning', 'Cannot write if we aren\'t connected yet!')
- return False
- if not isinstance(opcode, int):
- raise TypeError('Opcode must be of int type!')
- if data is None:
- data = ''
- try:
- data = to_bytes(data)
- except Exception as e:
- self.log('warning', e)
- return False
- data_len = len(data)
- # the following data must be little endian unsigned ints
- # see: https://github.com/discordapp/discord-rpc/blob/master/documentation/hard-mode.md#notes
- header = struct.pack('<II', opcode, data_len)
- # append header to data
- data = header + data
- # get new data size
- data_len = len(data)
- if self.__pipe is not None:
- written = ctypes.c_ulong(0)
- success = ctypes.windll.kernel32.WriteFile(self.__pipe, ctypes.c_char_p(data), data_len,
- ctypes.byref(written), None)
- if (not success) or (data_len != written.value):
- self.log('warning', 'Failed to write data onto pipe.')
- return False
- return True
- elif self.__sock is not None:
- data_sent = 0
- while data_sent < data_len:
- try:
- sent = self.__sock.send(data[data_sent:], _msg_flags)
- except Exception as e:
- self.log('warning', e)
- return False
- if sent == 0:
- self.log('warning', 'Socket connection broken!')
- if data_sent == 0:
- self.log('warning', 'No data sent; closing connection.')
- self.close()
- return False
- data_sent += sent
- return True
- self.log('warning', 'write() executed code that shouldn\'t have run.')
- return False
-
- def read(self):
- ret_val = [False, None, None]
- if not self.connected():
- self.log('warning', 'Cannot read if we haven\'t opened a connection!')
- return ret_val
- data = bytes()
- header_size = struct.calcsize('<II')
- # (is_successful_read, OpCode, data)
- if self.__pipe is not None:
- available = ctypes.c_ulong(0)
- if not ctypes.windll.kernel32.PeekNamedPipe(self.__pipe, None, 0, None, ctypes.byref(available), None):
- self.log('warning', 'Peek on pipe for header failed.')
- self.close()
- ret_val[2] = [errorcodes.PipeClosed, 'Pipe closed']
- return ret_val
- if available.value < header_size:
- self.log('debug', 'Pipe doesn\'t have enough data to read in header.')
- # assume this is like errno.EAGAIN
- ret_val[2] = [errorcodes.PipeClosed, 'Pipe closed']
- return ret_val
- cb_read = ctypes.c_ulong(0)
- buff = ctypes.create_string_buffer(header_size)
- success = 0
- while not success:
- success = ctypes.windll.kernel32.ReadFile(self.__pipe, buff, header_size, ctypes.byref(cb_read), None)
- if success == 1:
- # we successfully read the HEADER :O
- # Note: we use RAW here, otherwise it'll be a 1 byte kinda weird thing
- header = buff.raw
- break
- elif ctypes.windll.kernel32.GetLastError() != ERROR_MORE_DATA:
- # we don't have more data; close pipe
- self.log('warning', 'Failed to read in header from pipe.')
- self.close()
- ret_val[2] = [errorcodes.PipeClosed, 'Pipe closed']
- return ret_val
- opcode, data_len = struct.unpack('<II', header)
- cb_read = ctypes.c_ulong(0)
- buff = ctypes.create_string_buffer(data_len)
- success = 0
- available = ctypes.c_ulong(0)
- if not ctypes.windll.kernel32.PeekNamedPipe(self.__pipe, None, 0, None, ctypes.byref(available), None):
- self.log('warning', 'Peek on pipe for data failed.')
- self.close()
- ret_val[2] = [errorcodes.ReadCorrupt, 'Partial data in frame']
- return ret_val
- if available.value < data_len:
- self.log('warning', 'Pipe doesn\'t have enough data to read in data.')
- # assume this is like errno.EAGAIN
- ret_val[2] = [errorcodes.ReadCorrupt, 'Partial data in frame']
- return ret_val
- while not success:
- success = ctypes.windll.kernel32.ReadFile(self.__pipe, buff, data_len, ctypes.byref(cb_read), None)
- if success == 1:
- # we successfully read the DATA :O
- ret_val[0] = True
- ret_val[1] = opcode
- # value here actually works okay, so use that
- # Note: raw also seems to work, but meh
- data = buff.value
- break
- elif ctypes.windll.kernel32.GetLastError() != ERROR_MORE_DATA:
- # we don't have more data; close pipe
- self.log('warning', 'Failed to read in data from pipe.')
- self.close()
- ret_val[2] = [errorcodes.ReadCorrupt, 'Partial data in frame']
- return ret_val
- elif self.__sock is not None:
- packets = list()
- while len(bytes().join(packets)) < header_size:
- try:
- packet = self.__sock.recv(header_size - len(bytes().join(packets)))
- except Exception as e:
- ret_val[2] = [errorcodes.PipeClosed, 'Pipe closed']
- if hasattr(e, 'errno'):
- if e.errno == errno.EAGAIN:
- self.log('debug', e)
- self.log('debug', 'errno == EAGAIN')
- return ret_val
- self.log('warning', 'Failed to read in header!')
- self.log('warning', e)
- self.close()
- if packet is None or len(packet) == 0:
- self.log('warning', 'Socket connection broken!')
- if len(bytes().join(packets)) == 0:
- self.log('warning', 'No data sent; closing connection.')
- self.close()
- ret_val[2] = [errorcodes.PipeClosed, 'Pipe closed']
- return ret_val
- packets.append(packet)
- header = bytes().join(packets)
- packets = list()
- opcode, data_len = struct.unpack('<II', header)
- self.log('debug', 'Opcode: {}, data length: {}'.format(opcode, data_len))
- while len(bytes().join(packets)) < data_len:
- try:
- packet = self.__sock.recv(data_len - len(bytes().join(packets)))
- except Exception as e:
- ret_val[2] = [errorcodes.ReadCorrupt, 'Partial data in frame']
- if hasattr(e, 'errno'):
- if e.errno == errno.EAGAIN:
- self.log('debug', e)
- self.log('debug', 'errno == EAGAIN')
- return ret_val
- self.log('warning', 'Failed to read in data!')
- self.log('warning', e)
- if packet is None or len(packet) == 0:
- self.log('warning', 'Socket connection broken!')
- if len(bytes().join(packets)) == 0:
- self.log('warning', 'No data sent; closing connection.')
- self.close()
- ret_val[2] = [errorcodes.ReadCorrupt, 'Partial data in frame']
- return ret_val
- packets.append(packet)
- data = bytes().join(packets)
- ret_val[0] = True
- ret_val[1] = opcode
- if ret_val[0]:
- if is_python3():
- data = to_unicode(data)
- ret_val[2] = data
- self.log('debug', 'Return values: {}'.format(ret_val))
- return ret_val
-
- def close(self):
- # ensure we're using Windows before trying to close a pipe
- # Note: This should **never** execute on a non-Windows machine!
- if self.__pipe is not None and is_windows():
- ctypes.windll.kernel32.CloseHandle(self.__pipe)
- self.__pipe = None
- if self.__sock is not None:
- try:
- self.__sock.shutdown(socket.SHUT_RDWR)
- self.__sock.close()
- except Exception as e:
- self.log('warning', e)
- finally:
- self.__sock = None
- if self.__open:
- self.__open = False
- self.log('debug', 'Closed IPC connection.')
-
- def destroy(self):
- # make sure we close everything
- self.close()
- # if we automatically set our own logger, clean it up
- if self.__is_logging:
- for handle in self.__logger.handlers[:]:
- handle.close()
- self.__logger.removeHandler(handle)
- self.__logger = None
-
- @property
- def is_open(self):
- return self.__open
-
- def connected(self):
- return self.is_open
diff --git a/discord_rpc/connection/rpc.py b/discord_rpc/connection/rpc.py
deleted file mode 100644
index ad11007..0000000
--- a/discord_rpc/connection/rpc.py
+++ /dev/null
@@ -1,175 +0,0 @@
-from __future__ import absolute_import
-import logging
-import json
-from ..codes import errorcodes
-from ..codes import opcodes
-from ..codes import statecodes
-from ..util.utils import is_callable, json2dict, range
-from .ipc import BaseConnection
-
-
-_RPC_VERSION = 1
-
-
-class RpcConnection(object):
- _connection = None
- _state = statecodes.Disconnected
- _app_id = None
- _last_err_code = 0
- _last_err_msg = ''
- _pipe_no = 0
- _on_connect = None
- _on_disconnect = None
-
- def __init__(self, app_id, pipe_no=0, log=True, logger=None, log_file=None, log_level=logging.INFO):
- self._connection = BaseConnection(log=log, logger=logger, log_file=log_file, log_level=log_level)
- self._app_id = str(app_id)
- if pipe_no in range(0, 10):
- self._pipe_no = pipe_no
-
- def open(self):
- if self.state == statecodes.Connected:
- self.log('debug', 'Already connected; no need to open.')
- return
-
- if self.state == statecodes.Disconnected:
- self.connection.open(pipe_no=self._pipe_no)
- if not self.connection.is_open:
- self.log('warning', 'Failed to open IPC connection.')
- return
-
- if self.state == statecodes.SentHandshake:
- did_read, data = self.read()
- if did_read:
- cmd = data.get('cmd', None)
- evt = data.get('evt', None)
- if all(x is not None for x in (cmd, evt)) and cmd == 'DISPATCH' and evt == 'READY':
- self.state = statecodes.Connected
- if self.on_connect is not None:
- self.on_connect(data)
- self.log('info', 'IPC connected successfully.')
- else:
- data = {'v': _RPC_VERSION, 'client_id': self.app_id}
- if self.connection.write(json.dumps(data), opcodes.Handshake):
- self.state = statecodes.SentHandshake
- self.log('debug', 'IPC connection sent handshake.')
- else:
- self.log('warning', 'IPC failed to send handshake.')
- self.close()
-
- def close(self):
- if self.on_disconnect is not None and self.state in (statecodes.Connected, statecodes.SentHandshake):
- self.on_disconnect(self._last_err_code, self._last_err_msg)
- self.log('debug', 'Attempting to close IPC connection.')
- if self.connection is not None:
- self.connection.close()
- else:
- self.log('warning', 'Called close without a connection!')
- self.state = statecodes.Disconnected
-
- def write(self, data):
- if isinstance(data, dict):
- data = json.dumps(data)
- if not self.connection.write(data, opcodes.Frame):
- self.log('warning', 'Failed to write frame to IPC connection.')
- self.close()
- return False
- return True
-
- def read(self):
- if self.state not in (statecodes.Connected, statecodes.SentHandshake):
- self.log('debug', 'We aren\'t connected, therefore we cannot read data yet.')
- return False
- while True:
- did_read, opcode, data = self.connection.read()
- self.log('debug', 'ipc.read(): read: {}, Opcode: {}, data: {}'.format(did_read, opcode, data))
- if not did_read:
- err_reason = data[0]
- if (err_reason == errorcodes.PipeClosed and not self.connection.is_open) \
- or err_reason == errorcodes.ReadCorrupt:
- self._last_err_code = err_reason
- self._last_err_msg = data[1]
- self.log('debug', 'Failed to read; Connection closed. {}'.format(data))
- self.close()
- return False, None
- if opcode == opcodes.Close:
- data = json2dict(data)
- self._last_err_code = data.get('code', -1)
- self._last_err_msg = data.get('message', '')
- self.log('debug', 'Opcode == Close. Closing connection.')
- self.close()
- return False, None
- elif opcode == opcodes.Frame:
- data = json2dict(data)
- self.log('debug', 'Successful read: {}'.format(data))
- return True, data
- elif opcode == opcodes.Ping:
- if not self.connection.write('', opcodes.Pong):
- self.log('warning', 'Failed to send Pong message.')
- self.close()
- elif opcode == opcodes.Pong:
- # Discord does nothing here
- pass
- else:
- # something bad happened
- self._last_err_code = errorcodes.ReadCorrupt
- self._last_err_msg = 'Bad IPC frame.'
- self.log('warning', 'Got a bad frame from IPC connection.')
- self.close()
- return False, None
-
- def destroy(self):
- self.log('info', 'Destroying RPC connection.')
- self.close()
- self.connection.destroy()
- self._connection = None
-
- def log(self, *args):
- if self._connection is not None:
- self._connection.log(*args)
-
- @property
- def connection(self):
- return self._connection
-
- @property
- def state(self):
- return self._state
-
- @state.setter
- def state(self, state):
- if isinstance(state, int) and state in (statecodes.Connected, statecodes.SentHandshake,
- statecodes.Disconnected, statecodes.AwaitingResponse):
- self._state = state
- else:
- self.log('warning', 'Invalid state number!')
-
- @property
- def app_id(self):
- return self._app_id
-
- @property
- def is_open(self):
- return self.state == statecodes.Connected
-
- @property
- def on_connect(self):
- return self._on_connect
-
- @on_connect.setter
- def on_connect(self, callback):
- if callback is None or is_callable(callback):
- self._on_connect = callback
- else:
- self.log('warning', 'on_connect must be callable/None!')
-
- @property
- def on_disconnect(self):
- return self._on_disconnect
-
- @on_disconnect.setter
- def on_disconnect(self, callback):
- if callback is None or is_callable(callback):
- self._on_disconnect = callback
- else:
- self.log('warning', 'on_disconnect must be callable/None!')