diff --git a/PresenceClient/PresenceClient-Py/.env b/PresenceClient/PresenceClient-Py/.env new file mode 100644 index 0000000..f98e017 --- /dev/null +++ b/PresenceClient/PresenceClient-Py/.env @@ -0,0 +1,2 @@ +IP = 'the ip of your device' +APPLICATION_ID = 'your the discord application id' diff --git a/PresenceClient/PresenceClient-Py/presence-client.py b/PresenceClient/PresenceClient-Py/presence-client.py index bf44f04..76c96d6 100644 --- a/PresenceClient/PresenceClient-Py/presence-client.py +++ b/PresenceClient/PresenceClient-Py/presence-client.py @@ -1,24 +1,44 @@ import argparse -import sys import json import socket import struct import time import re import requests +import os from pypresence import Presence +from dotenv import load_dotenv +import subprocess +import time + +load_dotenv() + +switch_ip = os.getenv('IP') +client_id = os.getenv('APPLICATION_ID') + +rpc = Presence(str(client_id)) TCP_PORT = 0xCAFE PACKETMAGIC = 0xFFAADD23 parser = argparse.ArgumentParser() -parser.add_argument('ip', help='The IP address of your device') -parser.add_argument('client_id', help='The Client ID of your Discord Rich Presence application') -parser.add_argument('--ignore-home-screen', dest='ignore_home_screen', action='store_true', help='Don\'t display the home screen. Defaults to false if missing this flag.') +parser.add_argument('--ignore-home-screen', dest='ignore_home_screen', action='store_true', help='Hide the home screen. Defaults to false if missing this flag.') +parser.add_argument('--ignore-tinfoil', dest='ignore_tinfoil', action='store_true', help='Hide the Tinfoil app. Defaults to false if missing this flag.') +parser.add_argument('--low-latancy', dest='latancy',action='store_true', help='Enable low-latancy mode. Defaults to false if missing this flag.') +parser.set_defaults(ignore_home_screen=False, ignore_tinfoil=False, latancy=False) +consoleargs = parser.parse_args() questOverrides = None switchOverrides = None +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +switch_server_address = (switch_ip, TCP_PORT) + +if consoleargs.latancy == True: + latancy = 15 +else: + latancy = 60 + try: questOverrides = json.loads(requests.get("https://raw.githubusercontent.com/Sun-Research-University/PresenceClient/master/Resource/QuestApplicationOverrides.json").text) switchOverrides = json.loads(requests.get("https://raw.githubusercontent.com/Sun-Research-University/PresenceClient/master/Resource/SwitchApplicationOverrides.json").text) @@ -26,115 +46,116 @@ print('Failed to retrieve Override files') exit() -#Defines a title packet -class Title: +def restart(): + rpc.clear() + rpc.close() + sock.close() + + command = ['python3', 'presence-client.py'] + + if consoleargs.latancy: + command.append('--low-latancy') + if consoleargs.ignore_tinfoil: + command.append('--ignore-tinfoil') + if consoleargs.ignore_home_screen: + command.append('--ignore-home-screen') + subprocess.Popen(command) + exit() + +class Title: def __init__(self, raw_data): + if len(raw_data) != 628: + restart() + unpacker = struct.Struct('2L612s') enc_data = unpacker.unpack(raw_data) + self.magic = int(enc_data[0]) - if int(enc_data[1]) == 0: - self.pid = int(enc_data[1]) - self.name = 'Home Menu' + self.pid = int(enc_data[1]) + + if self.pid == 0: + self.name = 'Home Menu' if self.magic != PACKETMAGIC else 'Tinfoil' else: - self.pid = int(enc_data[1]) self.name = enc_data[2].decode('utf-8', 'ignore').split('\x00')[0] - if int(enc_data[0]) == PACKETMAGIC: - if self.name in questOverrides: - if questOverrides[self.name]['CustomName'] != '': - self.name = questOverrides[self.name]['CustomName'] - else: - if self.name in switchOverrides: - if switchOverrides[self.name]['CustomName'] != '': - self.name = switchOverrides[self.name]['CustomName'] + overrides = questOverrides if self.magic == PACKETMAGIC else switchOverrides + if overrides is not None and self.name in overrides and overrides[self.name]['CustomName']: + self.name = overrides[self.name]['CustomName'] def main(): - consoleargs = parser.parse_args() - - switch_ip = consoleargs.ip - client_id = consoleargs.client_id - if not checkIP(switch_ip): print('Invalid IP') exit() - rpc = Presence(str(client_id)) try: rpc.connect() rpc.clear() except: print('Unable to start RPC!') - - switch_server_address = (switch_ip, TCP_PORT) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - try: - sock.connect(switch_server_address) - print('Successfully connected to %s' % switch_ip + ':' + str(TCP_PORT)) - except: - print('Error connection to %s refused' % switch_ip + ':' + str(TCP_PORT)) - exit() - + while True: + try: + sock.connect(switch_server_address) + print(f'Successfully connected to {switch_ip}:{TCP_PORT}') + break + except socket.error: + print(f'Error connecting to {switch_ip}:{TCP_PORT}. Retrying in {latancy} seconds.') + time.sleep(latancy) lastProgramName = '' startTimer = 0 - while True: data = None try: + sock.settimeout(15) data = sock.recv(628) - except: + sock.settimeout(None) + except socket.timeout: + sock.settimeout(None) + sock.close() + restart() + except socket.error: print('Could not connect to Server! Retrying...') - startTimer = 0 - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + time.sleep(latancy) try: sock.connect(switch_server_address) - print('Successfully reconnected to %s' % - repr(switch_server_address)) - except: - print('Error reconnection to %s refused' % - repr(switch_server_address)) - exit() + print('Successfully reconnected to %s' % repr(switch_server_address)) + except socket.error: + print(f'Error reconnecting to {repr(switch_server_address)}. Retrying...') + time.sleep(latancy) + continue + def get_details(title, overrides): + if title.name in overrides: + orinfo = overrides[title.name] + largeimagekey = orinfo['CustomKey'] or iconFromPid(title.pid) + details = orinfo['CustomPrefix'] or 'Playing' + else: + largeimagekey = iconFromPid(title.pid) if int(title.pid) != PACKETMAGIC else title.name.lower().replace(' ', '') + details = 'Playing' + details += ' ' + title.name + return largeimagekey, details + title = Title(data) + if not hasattr(title, 'magic'): + restart() + continue + if title.magic == PACKETMAGIC: if lastProgramName != title.name: startTimer = int(time.time()) if consoleargs.ignore_home_screen and title.name == 'Home Menu': rpc.clear() + elif consoleargs.ignore_tinfoil and title.name == 'Tinfoil': + rpc.clear() else: - smallimagetext = '' - largeimagekey = '' - details = '' - largeimagetext = title.name - if int(title.pid) != PACKETMAGIC: - smallimagetext = 'SwitchPresence-Rewritten' - if title.name not in switchOverrides: - largeimagekey = iconFromPid(title.pid) - details = 'Playing ' + str(title.name) - else: - orinfo = switchOverrides[title.name] - largeimagekey = orinfo['CustomKey'] or iconFromPid(title.pid) - details = orinfo['CustomPrefix'] or 'Playing' - details += ' ' + title.name - else: - smallimagetext = 'QuestPresence' - if title.name not in questOverrides: - largeimagekey = title.name.lower().replace(' ', '') - details = 'Playing ' + title.name - else: - orinfo = questOverrides[title.name] - largeimagekey = orinfo['CustomKey'] or title.name.lower().replace( - ' ', '') - details = orinfo['CustomPrefix'] or 'Playing' - details += ' ' + title.name - if not title.name: - title.name = '' - lastProgramName = title.name + smallimagetext = 'SwitchPresence-Rewritten' if int(title.pid) != PACKETMAGIC else 'QuestPresence' + overrides = switchOverrides if int(title.pid) != PACKETMAGIC else questOverrides + largeimagekey, details = get_details(title, overrides) + largeimagetext = title.name or '' + lastProgramName = title.name or '' rpc.update(details=details, start=startTimer, large_image=largeimagekey, large_text=largeimagetext, small_text=smallimagetext) time.sleep(1) else: - time.sleep(1) rpc.clear() rpc.close() sock.close() @@ -151,6 +172,5 @@ def checkIP(ip): def iconFromPid(pid): return '0' + str(hex(int(pid))).split('0x')[1] - if __name__ == '__main__': main() \ No newline at end of file