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:
|
# From legilimens.js:
|
||||||
# let tps = Math.round(Math.min(1000 / data.averageTickTime, 20) * 10) / 10;
|
# 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'\((?!\)|[\'\"])(.+?)(?<!\(|[\'\"])\)')
|
brkregex = re.compile(r'\((?!\)|[\'\"])(.+?)(?<!\(|[\'\"])\)')
|
||||||
|
|
||||||
def parse_brackets(f):
|
def parse_brackets(f, arrow):
|
||||||
return brkregex.search(f)[1]
|
|
||||||
|
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):
|
def to_ecma5_function(f):
|
||||||
fnstart = f.find('{')+1
|
fnstart = f.find('{')+1
|
||||||
|
|
|
@ -11,6 +11,7 @@ from . import aterrors
|
||||||
from . import atfm
|
from . import atfm
|
||||||
from . import atconf
|
from . import atconf
|
||||||
from . import atplayers
|
from . import atplayers
|
||||||
|
from .atwss import AternosWss
|
||||||
|
|
||||||
JAVA = 0
|
JAVA = 0
|
||||||
BEDROCK = 1
|
BEDROCK = 1
|
||||||
|
@ -26,7 +27,9 @@ class Status(enum.IntEnum):
|
||||||
on = 1
|
on = 1
|
||||||
loading = 2
|
loading = 2
|
||||||
shutdown = 3
|
shutdown = 3
|
||||||
|
unknown = 6
|
||||||
error = 7
|
error = 7
|
||||||
|
confirm = 10
|
||||||
|
|
||||||
class AternosServer:
|
class AternosServer:
|
||||||
|
|
||||||
|
@ -58,38 +61,12 @@ class AternosServer:
|
||||||
self.atconn.parse_token(servreq.content)
|
self.atconn.parse_token(servreq.content)
|
||||||
self.atconn.generate_sec()
|
self.atconn.generate_sec()
|
||||||
|
|
||||||
async def wss(self):
|
async def wss(self) -> AternosWss:
|
||||||
|
|
||||||
session = self.atconn.session.cookies['ATERNOS_SESSION']
|
return AternosWss(
|
||||||
headers = [
|
self.atconn.session.cookies,
|
||||||
('User-Agent', atconnect.REQUA),
|
self.servid
|
||||||
('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'])
|
|
||||||
|
|
||||||
def start(self, headstart:bool=False, accepteula:bool=True) -> None:
|
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,
|
install_requires=requires,
|
||||||
packages=['python_aternos'],
|
packages=['python_aternos'],
|
||||||
python_requires=">=3.6",
|
python_requires=">=3.7",
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,19 +3,6 @@ import re
|
||||||
import base64
|
import base64
|
||||||
import js2py
|
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
|
# Use tests from a file
|
||||||
tests = []
|
tests = []
|
||||||
with open('../token.txt', 'rt') as f:
|
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
|
del lines[len(lines)-1] # Remove empty string
|
||||||
tests = lines
|
tests = lines
|
||||||
|
|
||||||
brkregex = re.compile(r'\((?!\)|[\'\"])(.+?)(?<!\(|[\'\"])\)')
|
arrowre = re.compile(r'(\w+?|\(\w+?(?:,\s*\w+?)*\)|\(\))\s*=>\s*({\s*[\s\S]+\s*}|[^\r\n]+?(?:;|$))')
|
||||||
|
|
||||||
def parse_brackets(f):
|
|
||||||
return brkregex.search(f)[1]
|
|
||||||
|
|
||||||
def to_ecma5_function(f):
|
def to_ecma5_function(f):
|
||||||
# return "(function() { " + f[f.index("{")+1 : f.index("}")] + "})();"
|
# return "(function() { " + f[f.index("{")+1 : f.index("}")] + "})();"
|
||||||
|
@ -39,29 +23,28 @@ def atob(s):
|
||||||
return base64.standard_b64decode(str(s)).decode('utf-8')
|
return base64.standard_b64decode(str(s)).decode('utf-8')
|
||||||
|
|
||||||
def arrow_conv(f):
|
def arrow_conv(f):
|
||||||
if '=>' in f:
|
m = arrowre.search(f)
|
||||||
inner = parse_brackets(f)
|
while m != None:
|
||||||
while brkregex.match(inner) != None:
|
print(f)
|
||||||
inner = parse_brackets(inner)
|
params = m.group(1).strip('()')
|
||||||
|
body = m.group(2)
|
||||||
func = re.sub(
|
if body.startswith('{')\
|
||||||
r'(\w+)\s*=>\s*(.+)',
|
and body.endswith('}'):
|
||||||
r'function(\1){return \2}', inner
|
body = body.strip('{}')
|
||||||
)
|
else:
|
||||||
start = f.find(inner)
|
body = f'return {body}'
|
||||||
end = start + len(inner)
|
f = arrowre.sub(f'function({params}){{{body}}}', f)
|
||||||
f = f[:start] + func + f[end:]
|
m = arrowre.search(f)
|
||||||
|
print(f)
|
||||||
|
#print('function(' + m.group(1).strip("()") + '){return ' + m.group(2) + ';}')
|
||||||
return f
|
return f
|
||||||
|
|
||||||
ctx = js2py.EvalJs({'atob': atob})
|
ctx = js2py.EvalJs({'atob': atob})
|
||||||
|
|
||||||
for f in tests:
|
for f in tests:
|
||||||
try:
|
c = to_ecma5_function(f)
|
||||||
c = to_ecma5_function(f)
|
ctx.execute(c)
|
||||||
ctx.execute(c)
|
print(ctx.window['AJAX_TOKEN'])
|
||||||
print(ctx.window['AJAX_TOKEN'])
|
|
||||||
except Exception as e:
|
|
||||||
print(c, '\n', e)
|
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# 2rKOA1IFdBcHhEM616cb
|
# 2rKOA1IFdBcHhEM616cb
|
||||||
|
@ -76,5 +59,5 @@ for f in tests:
|
||||||
# 2iXh5W5uEYq5fWJIazQ6
|
# 2iXh5W5uEYq5fWJIazQ6
|
||||||
# CuUcmZ27Fb8bVBNw12Vj
|
# CuUcmZ27Fb8bVBNw12Vj
|
||||||
# YPPe8Ph7vzYaZ9PF9oQP
|
# YPPe8Ph7vzYaZ9PF9oQP
|
||||||
# (Note: The last three
|
# (Note: The last four
|
||||||
# tokens are different)
|
# 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[atob('QUpBWF9UT0tFTg==')]=atob('MmlYaDVXNXVFWXE1ZldKSWF6UTY=');})();
|
||||||
(() => {window[["_XAJA","NEKOT"].map(s => s.split('').reverse().join('')).join('')]=!window[("encodeURI" + "Componen" + "t")] || atob('Q3VVY21aMjdGYjhiVkJOdzEyVmo=');})();
|
(() => {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[["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