WebSocket API, token parser updates

This commit is contained in:
DarkCat09 2022-01-22 15:10:30 +04:00
parent 6993aadf03
commit c0f60cfe5c
8 changed files with 216 additions and 70 deletions

View file

@ -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

View file

@ -4,8 +4,16 @@ import base64
brkregex = re.compile(r'\((?!\)|[\'\"])(.+?)(?<!\(|[\'\"])\)')
def parse_brackets(f):
return brkregex.search(f)[1]
def parse_brackets(f, arrow):
match = brkregex.finditer(f)
if not arrow:
return match[0].group(1)
for r in match:
func = r.group(1)
if '=>' in func:
return func
def to_ecma5_function(f):
fnstart = f.find('{')+1

View file

@ -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:

97
python_aternos/atwss.py Normal file
View file

@ -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

View file

@ -28,5 +28,5 @@ setuptools.setup(
],
install_requires=requires,
packages=['python_aternos'],
python_requires=">=3.6",
python_requires=">=3.7",
)

View file

@ -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'\((?!\)|[\'\"])(.+?)(?<!\(|[\'\"])\)')
def parse_brackets(f):
return brkregex.search(f)[1]
arrowre = re.compile(r'(\w+?|\(\w+?(?:,\s*\w+?)*\)|\(\))\s*=>\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)

75
tests/js2py_test.py.old Normal file
View file

@ -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'\((?!\)|[\'\"])(.+?)(?<!\(|[\'\"])\)')
def parse_brackets(f, arrow=True):
match = brkregex.finditer(f)
if not arrow:
return match[0].group(1)
for r in match:
func = r.group(1)
if '=>' 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)

View file

@ -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('');})();