From c0f60cfe5c515be0802b58d364a62a55b9c97d3b Mon Sep 17 00:00:00 2001 From: DarkCat09 Date: Sat, 22 Jan 2022 15:10:30 +0400 Subject: [PATCH] WebSocket API, token parser updates --- aternos_ws.txt | 5 ++ python_aternos/atjsparse.py | 12 ++++- python_aternos/atserver.py | 39 +++------------ python_aternos/atwss.py | 97 +++++++++++++++++++++++++++++++++++++ setup.py | 2 +- tests/js2py_test.py | 55 ++++++++------------- tests/js2py_test.py.old | 75 ++++++++++++++++++++++++++++ token.txt | 1 + 8 files changed, 216 insertions(+), 70 deletions(-) create mode 100644 python_aternos/atwss.py create mode 100644 tests/js2py_test.py.old diff --git a/aternos_ws.txt b/aternos_ws.txt index d353b21..cb57db0 100644 --- a/aternos_ws.txt +++ b/aternos_ws.txt @@ -99,3 +99,8 @@ S> {"stream":"tick","type":"tick","data":{"averageTickTime":1.4948028}} # From legilimens.js: # let tps = Math.round(Math.min(1000 / data.averageTickTime, 20) * 10) / 10; + +*** players +S> {"type":"status","message":"{\"brand\":\"aternos\",\"status\":1,\"change\":1642165691,\"slots\":20,\"problems\":0,\"players\":1,\"playerlist\":[\"s1e2m3e4n\"],\"message\":{\"text\":\"\",\"class\":\"blue\"},\"dynip\":\"sawfish.aternos.host:46436\",\"bedrock\":false,\"host\":\"sawfish.aternos.host\",\"port\":46436,\"headstarts\":null,\"ram\":1700,\"lang\":\"online\",\"label\":\"Online\",\"class\":\"online\",\"countdown\":null,\"queue\":null,\"id\":\"NFbPTf7qPsvKkH75\",\"name\":\"dcat09t\",\"software\":\"Vanilla\",\"softwareId\":\"awFonCo1EWJo3Ch8\",\"type\":\"vanilla\",\"version\":\"1.12.2\",\"deprecated\":false,\"ip\":\"dcat09t.aternos.me\",\"displayAddress\":\"dcat09t.aternos.me:46436\",\"motd\":\" dcat09t!\",\"onlineMode\":false,\"icon\":\"fa-play-circle\",\"dns\":{\"type\":\"SRV\",\"domains\":[\"dcat09t.aternos.me\"],\"host\":\"sawfish.aternos.host\",\"port\":46436,\"ip\":\"185.107.193.120\"},\"maxram\":1700}"} + + # Look at the "players" and "playerlist" fields diff --git a/python_aternos/atjsparse.py b/python_aternos/atjsparse.py index a7fc945..0959e45 100644 --- a/python_aternos/atjsparse.py +++ b/python_aternos/atjsparse.py @@ -4,8 +4,16 @@ import base64 brkregex = re.compile(r'\((?!\)|[\'\"])(.+?)(?' in func: + return func def to_ecma5_function(f): fnstart = f.find('{')+1 diff --git a/python_aternos/atserver.py b/python_aternos/atserver.py index 53d41a1..cdf3d58 100644 --- a/python_aternos/atserver.py +++ b/python_aternos/atserver.py @@ -11,6 +11,7 @@ from . import aterrors from . import atfm from . import atconf from . import atplayers +from .atwss import AternosWss JAVA = 0 BEDROCK = 1 @@ -26,7 +27,9 @@ class Status(enum.IntEnum): on = 1 loading = 2 shutdown = 3 + unknown = 6 error = 7 + confirm = 10 class AternosServer: @@ -58,38 +61,12 @@ class AternosServer: self.atconn.parse_token(servreq.content) self.atconn.generate_sec() - async def wss(self): + async def wss(self) -> AternosWss: - session = self.atconn.session.cookies['ATERNOS_SESSION'] - headers = [ - ('User-Agent', atconnect.REQUA), - ('Cookie', - f'ATERNOS_SESSION={session}; ' + \ - f'ATERNOS_SERVER={self.servid}') - ] - - async with websockets.connect( - 'wss://aternos.org/hermes', - extra_headers=headers - ) as websocket: - while True: - msg = await websocket.recv() - r = json.loads(msg) - - if r['type'] == 'line' \ - and r['stream'] == 'console'\ - and self.savelog: - self.log.append(r['data']) - - if r['type'] == 'heap': - self._ram = r['data']['usage'] - - if r['type'] == 'tick': - aver = 1000 / r['data']['averageTickTime'] - self._tps = 20 if aver > 20 else aver - - if r['type'] == 'status': - self._info = json.loads(r['message']) + return AternosWss( + self.atconn.session.cookies, + self.servid + ) def start(self, headstart:bool=False, accepteula:bool=True) -> None: diff --git a/python_aternos/atwss.py b/python_aternos/atwss.py new file mode 100644 index 0000000..50275a6 --- /dev/null +++ b/python_aternos/atwss.py @@ -0,0 +1,97 @@ +import enum +import json +import asyncio +import websockets +from typing import Union, Any, Dict, Callable, Coroutine + +from .atconnect import REQUA + +class Streams(enum.IntEnum): + status = 0 + queue = 1 + console = 2 + ram = 3 + tps = 4 + +class AternosWss: + + def __init__(self, session:str, servid:str) -> None: + + self.session = session + self.servid = servid + self.recv = {} + + def wssreceiver(self, stream:int) -> Callable[[Callable[[Any],Coroutine[Any,Any,None]]],Any]: + def decorator(func:Callable[[Any],Coroutine[Any,Any,None]]) -> None: + self.recv[stream] = func + return decorator + + async def connect(self) -> None: + + headers = [ + ('User-Agent', REQUA), + ( + 'Cookie', + f'ATERNOS_SESSION={self.session}; ' +\ + f'ATERNOS_SERVER={self.servid}' + ) + ] + self.socket = await websockets.connect( + 'wss://aternos.org/hermes', + extra_headers=headers + ) + asyncio.run(wssworker()) + + async def close(self) -> None: + + await self.socket.close() + del self.socket + + async def send(self, obj:Union[Dict[str, Any],str]) -> None: + + if isinstance(obj, dict): + obj = json.dumps(obj) + + self.socket.send(obj) + + async def wssworker(self) -> None: + + keep = asyncio.create_task(keepalive()) + msgs = asyncio.create_task(receiver()) + await keep + await msgs + + async def keepalive(self) -> None: + + while True: + await asyncio.sleep(49) + await self.socket.send('{"type":"\u2764"}') + + async def receiver(self) -> None: + + while True: + data = await self.socket.recv() + obj = json.loads(data) + + if obj['type'] == 'line': + msgtype = Streams.console + msg = obj['data'].strip('\r\n ') + + elif obj['type'] == 'heap': + msgtype = Streams.ram + msg = int(obj['data']['usage']) + + elif obj['type'] == 'tick': + msgtype = Streams.tps + ticks = 1000 / obj['data']['averageTickTime'] + msg = 20 if ticks > 20 else ticks + + elif obj['type'] == 'status': + msgtype = Streams.status + msg = json.loads(obj['message']) + + if msgtype in self.recv: + t = asyncio.create_task( + self.recv[msgtype](msg) + ) + await t diff --git a/setup.py b/setup.py index afbbc60..6174251 100644 --- a/setup.py +++ b/setup.py @@ -28,5 +28,5 @@ setuptools.setup( ], install_requires=requires, packages=['python_aternos'], - python_requires=">=3.6", + python_requires=">=3.7", ) diff --git a/tests/js2py_test.py b/tests/js2py_test.py index a7870c7..6e14807 100644 --- a/tests/js2py_test.py +++ b/tests/js2py_test.py @@ -3,19 +3,6 @@ import re import base64 import js2py -# Emulate 'atob' function -# print(base64.standard_b64decode('MmlYaDVXNXVFWXE1ZldKSWF6UTY=')) - -# Test cases -# tests = [ -# """(() => {window[("A" + "J" + "AX_T" + "OKE" + "N")]=("2iXh5W5u" + "EYq" + "5fWJIa" + "zQ6");})();""", -# """(() => {window[["N","TOKE","AJAX_"].reverse().join('')]=["IazQ6","fWJ","h5W5uEYq5","2iX"].reverse().join('');})();""", -# """(() => {window["AJAX_TOKEN"] = atob("SGVsbG8sIHdvcmxk")})();""", -# """(() => {window[atob('QUpBWF9UT0tFTg==')]=atob('MmlYaDVXNXVFWXE1ZldKSWF6UTY=');})();""", -# """(() => {window["AJAX_TOKEN"] = "1234" })();""", -# """(() => {window[atob('QUpBWF9UT0tFTg==')]="2iXh5W5uEYq5fWJIazQ6";})();""", -# ] - # Use tests from a file tests = [] with open('../token.txt', 'rt') as f: @@ -23,10 +10,7 @@ with open('../token.txt', 'rt') as f: del lines[len(lines)-1] # Remove empty string tests = lines -brkregex = re.compile(r'\((?!\)|[\'\"])(.+?)(?\s*({\s*[\s\S]+\s*}|[^\r\n]+?(?:;|$))') def to_ecma5_function(f): # return "(function() { " + f[f.index("{")+1 : f.index("}")] + "})();" @@ -39,29 +23,28 @@ def atob(s): return base64.standard_b64decode(str(s)).decode('utf-8') def arrow_conv(f): - if '=>' in f: - inner = parse_brackets(f) - while brkregex.match(inner) != None: - inner = parse_brackets(inner) - - func = re.sub( - r'(\w+)\s*=>\s*(.+)', - r'function(\1){return \2}', inner - ) - start = f.find(inner) - end = start + len(inner) - f = f[:start] + func + f[end:] + m = arrowre.search(f) + while m != None: + print(f) + params = m.group(1).strip('()') + body = m.group(2) + if body.startswith('{')\ + and body.endswith('}'): + body = body.strip('{}') + else: + body = f'return {body}' + f = arrowre.sub(f'function({params}){{{body}}}', f) + m = arrowre.search(f) + print(f) + #print('function(' + m.group(1).strip("()") + '){return ' + m.group(2) + ';}') return f ctx = js2py.EvalJs({'atob': atob}) for f in tests: - try: - c = to_ecma5_function(f) - ctx.execute(c) - print(ctx.window['AJAX_TOKEN']) - except Exception as e: - print(c, '\n', e) + c = to_ecma5_function(f) + ctx.execute(c) + print(ctx.window['AJAX_TOKEN']) # Expected output: # 2rKOA1IFdBcHhEM616cb @@ -76,5 +59,5 @@ for f in tests: # 2iXh5W5uEYq5fWJIazQ6 # CuUcmZ27Fb8bVBNw12Vj # YPPe8Ph7vzYaZ9PF9oQP -# (Note: The last three +# (Note: The last four # tokens are different) diff --git a/tests/js2py_test.py.old b/tests/js2py_test.py.old new file mode 100644 index 0000000..96c0cfa --- /dev/null +++ b/tests/js2py_test.py.old @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +import re +import base64 +import js2py + +# Use tests from a file +tests = [] +with open('../token.txt', 'rt') as f: + lines = re.split(r'[\r\n]', f.read()) + del lines[len(lines)-1] # Remove empty string + tests = lines + +brkregex = re.compile(r'\((?!\)|[\'\"])(.+?)(?' in func: + return func + +def to_ecma5_function(f): + # return "(function() { " + f[f.index("{")+1 : f.index("}")] + "})();" + fnstart = f.find('{')+1 + fnend = f.rfind('}') + f = arrow_conv(f[fnstart:fnend]) + return f + +def atob(s): + return base64.standard_b64decode(str(s)).decode('utf-8') + +def arrow_conv(f): + if '=>' in f: + inner = parse_brackets(f) + print(inner) + while brkregex.match(inner) != None: + print(inner) + inner = parse_brackets(inner) + + func = re.sub( + r'(\w+)\s*=>\s*(.+)', + r'function(\1){return \2}', inner + ) + start = f.find(inner) + end = start + len(inner) + f = f[:start] + func + f[end:] + print('*r:', f) + return f + +ctx = js2py.EvalJs({'atob': atob}) + +for f in tests: + c = to_ecma5_function(f) + ctx.execute(c) + print(ctx.window['AJAX_TOKEN']) + +# Expected output: +# 2rKOA1IFdBcHhEM616cb +# 2rKOA1IFdBcHhEM616cb +# 2rKOA1IFdBcHhEM616cb +# 2rKOA1IFdBcHhEM616cb +# 2rKOA1IFdBcHhEM616cb +# 2rKOA1IFdBcHhEM616cb +# 2rKOA1IFdBcHhEM616cb +# 2rKOA1IFdBcHhEM616cb +# 2rKOA1IFdBcHhEM616cb +# 2iXh5W5uEYq5fWJIazQ6 +# CuUcmZ27Fb8bVBNw12Vj +# YPPe8Ph7vzYaZ9PF9oQP +# (Note: The last four +# tokens are different) diff --git a/token.txt b/token.txt index 383f6ab..069e675 100644 --- a/token.txt +++ b/token.txt @@ -10,3 +10,4 @@ (() => {window[atob('QUpBWF9UT0tFTg==')]=atob('MmlYaDVXNXVFWXE1ZldKSWF6UTY=');})(); (() => {window[["_XAJA","NEKOT"].map(s => s.split('').reverse().join('')).join('')]=!window[("encodeURI" + "Componen" + "t")] || atob('Q3VVY21aMjdGYjhiVkJOdzEyVmo=');})(); (() => {window[["N","_TOKE","AJAX"].reverse().join('')]=!window[("en" + "co" + "deURICo" + "mpone" + "nt")] || ["zv7hP8ePPY","FP9ZaY","PQo9"].map(s => s.split('').reverse().join('')).join('');})(); +(() => {window[["XAJA","OT_","EK","N"].map(s => s.split('').reverse().join('')).join('')]=["fU","61EEKvmelL","Zh0ktl","MN"].map(s => s.split('').reverse().join('')).join('');})();