WebSocket API, token parser updates
This commit is contained in:
parent
6993aadf03
commit
c0f60cfe5c
8 changed files with 216 additions and 70 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
97
python_aternos/atwss.py
Normal 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
|
2
setup.py
2
setup.py
|
@ -28,5 +28,5 @@ setuptools.setup(
|
|||
],
|
||||
install_requires=requires,
|
||||
packages=['python_aternos'],
|
||||
python_requires=">=3.6",
|
||||
python_requires=">=3.7",
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
||||
# 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
75
tests/js2py_test.py.old
Normal 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)
|
|
@ -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('');})();
|
||||
|
|
Reference in a new issue