Bugfixes in atfile,wss,connect
This commit is contained in:
parent
48f8090d2a
commit
cec2938804
10 changed files with 234 additions and 229 deletions
|
@ -28,7 +28,7 @@ class Client:
|
|||
|
||||
loginreq = atconn.request_cloudflare(
|
||||
f'https://aternos.org/panel/ajax/account/login.php',
|
||||
atconnect.REQPOST, data=credentials,
|
||||
'POST', data=credentials,
|
||||
sendtoken=True
|
||||
)
|
||||
|
||||
|
@ -51,7 +51,7 @@ class Client:
|
|||
def from_session(cls, session:str):
|
||||
|
||||
atconn = AternosConnect()
|
||||
atconn.session.cookies.set('ATERNOS_SESSION', session)
|
||||
atconn.session.cookies['ATERNOS_SESSION'] = session
|
||||
atconn.parse_token()
|
||||
atconn.generate_sec()
|
||||
|
||||
|
@ -63,14 +63,13 @@ class Client:
|
|||
atconn = AternosConnect()
|
||||
auth = atconn.request_cloudflare(
|
||||
'https://aternos.org/auth/google-login',
|
||||
atconnect.REQGET, redirect=False
|
||||
'GET', redirect=False
|
||||
)
|
||||
return auth.headers['Location']
|
||||
|
||||
def list_servers(self) -> List[atserver.AternosServer]:
|
||||
serverspage = self.atconn.request_cloudflare(
|
||||
'https://aternos.org/servers/',
|
||||
atconnect.REQGET
|
||||
'https://aternos.org/servers/', 'GET'
|
||||
)
|
||||
serverstree = lxml.html.fromstring(serverspage.content)
|
||||
serverslist = serverstree.xpath('//div[contains(@class,"servers ")]/div')
|
||||
|
|
|
@ -4,11 +4,11 @@ import lxml.html
|
|||
from typing import Any, Dict, List, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from . import atconnect
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .atserver import AternosServer
|
||||
|
||||
#
|
||||
# server.options
|
||||
class ServerOpts(enum.Enum):
|
||||
players = 'max-players'
|
||||
gm = 'gamemode'
|
||||
|
@ -25,16 +25,20 @@ class ServerOpts(enum.Enum):
|
|||
forcegm = 'force-gamemode'
|
||||
spawnlock = 'spawn-protection'
|
||||
cmds = 'allow-cheats'
|
||||
packreq = 'require-resource-pack'
|
||||
pack = 'resource-pack'
|
||||
|
||||
DAT_PREFIX = 'Data:'
|
||||
DAT_GR_PREFIX = 'Data:GameRules:'
|
||||
|
||||
# level.dat
|
||||
class WorldOpts(enum.Enum):
|
||||
seed = 'randomseed'
|
||||
seed12 = 'randomseed'
|
||||
seed = 'seed'
|
||||
hardcore = 'hardcore'
|
||||
difficulty = 'difficulty'
|
||||
difficulty = 'Difficulty'
|
||||
|
||||
# /gamerule
|
||||
class WorldRules(enum.Enum):
|
||||
advs = 'announceAdvancements'
|
||||
univanger = 'universalAnger'
|
||||
|
@ -56,6 +60,7 @@ class WorldRules(enum.Enum):
|
|||
drowndmg = 'drowningDamage'
|
||||
falldmg = 'fallDamage'
|
||||
firedmg = 'fireDamage'
|
||||
snowdmg = 'freezeDamage'
|
||||
forgive = 'forgiveDeadPlayers'
|
||||
keepinv = 'keepInventory'
|
||||
deathmsg = 'showDeathMessages'
|
||||
|
@ -64,7 +69,8 @@ class WorldRules(enum.Enum):
|
|||
entcram = 'maxEntityCramming'
|
||||
mobgrief = 'mobGriefing'
|
||||
regen = 'naturalRegeneration'
|
||||
rndtick = 'randomTickspeed'
|
||||
sleeppct = 'playersSleepingPercentage'
|
||||
rndtick = 'randomTickSpeed'
|
||||
spawnradius = 'spawnRadius'
|
||||
reducedf3 = 'reducedDebugInfo'
|
||||
spectchunkgen = 'spectatorsGenerateChunks'
|
||||
|
@ -85,24 +91,43 @@ class Difficulty(enum.IntEnum):
|
|||
normal = 2
|
||||
hard = 3
|
||||
|
||||
JDK = 'openjdk:{}'
|
||||
OJ9 = 'adoptopenjdk:{}-jre-openj9-bionic'
|
||||
#
|
||||
# jre types for set_java
|
||||
javatype = {
|
||||
'jdk': 'openjdk:{ver}',
|
||||
'openj9-1': 'adoptopenjdk:{ver}-jre-openj9-bionic',
|
||||
'openj9-2': 'ibm-semeru-runtimes:open-{ver}-jre'
|
||||
}
|
||||
# checking java version format
|
||||
javacheck = re.compile(
|
||||
''.join(
|
||||
list(
|
||||
map(
|
||||
# create a regexp for each jre type,
|
||||
# e.g.: (^openjdk:\d+$)|
|
||||
lambda i: '(^' + javatype[i].format(ver=r'\d+') + '$)|',
|
||||
javatype
|
||||
)
|
||||
)
|
||||
).rstrip('|')
|
||||
)
|
||||
# checking timezone format
|
||||
tzcheck = re.compile(r'(^[A-Z]\w+\/[A-Z]\w+$)|^UTC$')
|
||||
# options types converting
|
||||
convert = {
|
||||
'config-option-number': int,
|
||||
'config-option-select': int,
|
||||
'config-option-toggle': bool
|
||||
}
|
||||
|
||||
FLAG_PROP_TYPE = 1
|
||||
|
||||
# MAIN CLASS
|
||||
class AternosConfig:
|
||||
|
||||
def __init__(self, atserv:'AternosServer') -> None:
|
||||
|
||||
self.atserv = atserv
|
||||
|
||||
@property
|
||||
def timezone(self) -> str:
|
||||
def get_timezone(self) -> str:
|
||||
|
||||
optreq = self.atserv.atserver_request(
|
||||
'https://aternos.org/options', 'GET'
|
||||
|
@ -113,12 +138,11 @@ class AternosConfig:
|
|||
tztext = tzopt.xpath('.//div[@class="option current"]')[0].text
|
||||
return tztext.strip()
|
||||
|
||||
@timezone.setter
|
||||
def timezone(self, value:str) -> None:
|
||||
def set_timezone(self, value:str) -> None:
|
||||
|
||||
matches_tz = re.search(r'(?:^[A-Z]\w+\/[A-Z]\w+$)|^UTC$', value)
|
||||
if matches_tz == None:
|
||||
raise AttributeError('Timezone must match zoneinfo format: Area/Location')
|
||||
matches_tz = tzcheck.search(value)
|
||||
if not matches_tz:
|
||||
raise ValueError('Timezone must match zoneinfo format: Area/Location')
|
||||
|
||||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/timezone.php',
|
||||
|
@ -126,24 +150,21 @@ class AternosConfig:
|
|||
sendtoken=True
|
||||
)
|
||||
|
||||
@property
|
||||
def java_version(self) -> str:
|
||||
def get_java(self) -> str:
|
||||
|
||||
optreq = self.atserv.atserver_request(
|
||||
'https://aternos.org/options',
|
||||
'GET'
|
||||
'https://aternos.org/options', 'GET'
|
||||
)
|
||||
opttree = lxml.html.fromstring(optreq)
|
||||
|
||||
imgopt = opttree.xpath('//div[@class="options-other-input image-switch"]')[0]
|
||||
imgver = imgopt.xpath('.//div[@class="option current"]/@data-value')[0]
|
||||
return imgver
|
||||
|
||||
@java_version.setter
|
||||
def java_version(self, value:str) -> None:
|
||||
matches_jdkver = re.search(r'^(?:adopt)?openjdk:(\d+)(?:-jre-openj9-bionic)?$', value)
|
||||
if matches_jdkver == None:
|
||||
raise AttributeError('Incorrect Java image version format!')
|
||||
def set_java(self, value:str) -> None:
|
||||
|
||||
matches_jdkver = javacheck.search(value)
|
||||
if not matches_jdkver:
|
||||
raise ValueError('Incorrect Java image version format!')
|
||||
|
||||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/image.php',
|
||||
|
@ -160,8 +181,8 @@ class AternosConfig:
|
|||
option, value
|
||||
)
|
||||
|
||||
def get_server_props(self, flags:int=FLAG_PROP_TYPE) -> Dict[str,Any]:
|
||||
return self.__get_all_props('https://aternos.org/options', flags)
|
||||
def get_server_props(self, proptyping:bool=True) -> Dict[str,Any]:
|
||||
return self.__get_all_props('https://aternos.org/options', proptyping)
|
||||
|
||||
def set_server_props(self, props:Dict[str,Any]) -> None:
|
||||
for key in props:
|
||||
|
@ -186,11 +207,11 @@ class AternosConfig:
|
|||
|
||||
def get_world_props(
|
||||
self, world:str='world',
|
||||
flags:int=FLAG_PROP_TYPE) -> Dict[str,Any]:
|
||||
proptyping:bool=True) -> Dict[str,Any]:
|
||||
|
||||
self.__get_all_props(
|
||||
f'https://aternos.org/files/{world}/level.dat',
|
||||
flags, [DAT_PREFIX, DAT_GR_PREFIX]
|
||||
proptyping, [DAT_PREFIX, DAT_GR_PREFIX]
|
||||
)
|
||||
|
||||
def set_world_props(self, props:Dict[str,Any]) -> None:
|
||||
|
@ -212,37 +233,28 @@ class AternosConfig:
|
|||
)
|
||||
|
||||
def __get_all_props(
|
||||
self, url:str, flags:int=FLAG_PROP_TYPE,
|
||||
self, url:str, proptyping:bool=True,
|
||||
prefixes:Optional[List[str]]=None) -> Dict[str,Any]:
|
||||
|
||||
optreq = self.atserv.atserver_request(
|
||||
url,
|
||||
'GET'
|
||||
)
|
||||
optreq = self.atserv.atserver_request(url, 'GET')
|
||||
opttree = lxml.html.fromstring(optreq.content)
|
||||
|
||||
configs = opttree.xpath('//div[@class="config-options"]')
|
||||
for i in range(len(configs)):
|
||||
|
||||
conf = configs[i]
|
||||
for i, conf in enumerate(configs):
|
||||
opts = conf.xpath('/div[contains(@class,"config-option ")]')
|
||||
result = {}
|
||||
|
||||
for opt in opts:
|
||||
|
||||
key = opt.xpath('.//span[@class="config-option-output-key"]')[0].text
|
||||
value = opt.xpath('.//span[@class="config-option-output-value"]')[0].text
|
||||
|
||||
if prefixes != None:
|
||||
key = f'{prefixes[i]}{key}'
|
||||
|
||||
opttype = opt.xpath('/@class').split(' ')[1]
|
||||
if flags == FLAG_PROP_TYPE:
|
||||
if proptyping and opttype in convert:
|
||||
value = convert[opttype](value)
|
||||
|
||||
if opttype == 'config-option-number'\
|
||||
or opttype == 'config-option-select':
|
||||
value = int(value)
|
||||
|
||||
elif opttype == 'config-option-toggle':
|
||||
value = bool(value)
|
||||
result[key] = value
|
||||
|
||||
return result
|
||||
|
|
|
@ -7,7 +7,7 @@ from cloudscraper import CloudScraper
|
|||
from typing import Optional, Union
|
||||
|
||||
from . import atjsparse
|
||||
from .aterrors import CredentialsError, CloudflareError
|
||||
from .aterrors import CredentialsError
|
||||
|
||||
REQUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Goanna/4.8 Firefox/68.0 PaleMoon/29.4.0.2'
|
||||
|
||||
|
@ -15,17 +15,14 @@ class AternosConnect:
|
|||
|
||||
def __init__(self) -> None:
|
||||
|
||||
pass
|
||||
self.session = CloudScraper()
|
||||
|
||||
def parse_token(self, response:Optional[Union[str,bytes]]=None) -> str:
|
||||
def parse_token(self) -> str:
|
||||
|
||||
if response == None:
|
||||
loginpage = self.request_cloudflare(
|
||||
f'https://aternos.org/go/', 'GET'
|
||||
).content
|
||||
pagetree = lxml.html.fromstring(loginpage)
|
||||
else:
|
||||
pagetree = lxml.html.fromstring(response)
|
||||
|
||||
try:
|
||||
pagehead = pagetree.head
|
||||
|
@ -58,9 +55,10 @@ class AternosConnect:
|
|||
|
||||
def generate_aternos_rand(self, randlen:int=16) -> str:
|
||||
|
||||
rand_arr = []
|
||||
for i in range(randlen+1):
|
||||
rand_arr.append('')
|
||||
# a list with randlen+1 empty strings:
|
||||
# generate a string with spaces,
|
||||
# then split it by space
|
||||
rand_arr = (' ' * (randlen+1)).split(' ')
|
||||
|
||||
rand = random.random()
|
||||
rand_alphanum = self.convert_num(rand, 36) + ('0' * 17)
|
||||
|
@ -88,40 +86,24 @@ class AternosConnect:
|
|||
return result
|
||||
|
||||
def request_cloudflare(
|
||||
self, url:str, method:str, retries:int=10,
|
||||
self, url:str, method:str,
|
||||
params:Optional[dict]=None, data:Optional[dict]=None,
|
||||
headers:Optional[dict]=None, reqcookies:Optional[dict]=None,
|
||||
sendtoken:bool=False, redirect:bool=True) -> Response:
|
||||
|
||||
cftitle = '<title>Please Wait... | Cloudflare</title>'
|
||||
|
||||
if params == None:
|
||||
params = {}
|
||||
|
||||
if headers == None:
|
||||
headers = {}
|
||||
params = params if params else {}
|
||||
data = data if data else {}
|
||||
headers = headers if headers else {}
|
||||
reqcookies = reqcookies if reqcookies else {}
|
||||
headers['User-Agent'] = REQUA
|
||||
|
||||
if sendtoken:
|
||||
url += f'?TOKEN={self.token}&SEC={self.sec}'
|
||||
params['TOKEN'] = self.token
|
||||
params['SEC'] = self.sec
|
||||
|
||||
try:
|
||||
cookies = self.session.cookies
|
||||
except AttributeError:
|
||||
cookies = None
|
||||
|
||||
countdown = retries
|
||||
while True:
|
||||
|
||||
self.session = CloudScraper()
|
||||
if cookies != None:
|
||||
self.session.cookies = cookies
|
||||
|
||||
if reqcookies != None:
|
||||
for cookiekey in reqcookies:
|
||||
self.session.cookies.set(cookiekey, reqcookies[cookiekey])
|
||||
|
||||
time.sleep(1)
|
||||
# requests.cookies.CookieConflictError bugfix
|
||||
reqcookies['ATERNOS_SESSION'] = self.session.cookies['ATERNOS_SESSION']
|
||||
del self.session.cookies['ATERNOS_SESSION']
|
||||
|
||||
if method == 'POST':
|
||||
req = self.session.post(
|
||||
|
@ -131,17 +113,9 @@ class AternosConnect:
|
|||
)
|
||||
else:
|
||||
req = self.session.get(
|
||||
url, params=params,
|
||||
url, params={**params, **data},
|
||||
headers=headers, cookies=reqcookies,
|
||||
allow_redirects=redirect
|
||||
)
|
||||
|
||||
if not cftitle in req.text:
|
||||
break
|
||||
if not countdown > 0:
|
||||
raise CloudflareError(
|
||||
'The retries limit has been reached'
|
||||
)
|
||||
countdown -= 1
|
||||
|
||||
return req
|
||||
|
|
|
@ -9,6 +9,3 @@ class ServerError(AternosError):
|
|||
|
||||
class FileError(AternosError):
|
||||
pass
|
||||
|
||||
class CloudflareError(AternosError):
|
||||
pass
|
||||
|
|
|
@ -17,51 +17,53 @@ class AternosFile:
|
|||
def __init__(
|
||||
self, atserv:'AternosServer',
|
||||
path:str, name:str, ftype:int=FileType.file,
|
||||
size:Union[int,float]=0, dlallowed:bool=False) -> None:
|
||||
size:Union[int,float]=0) -> None:
|
||||
|
||||
self.atserv = atserv
|
||||
self._path = path
|
||||
self._name = name
|
||||
self._full = path + name
|
||||
self._ftype = ftype
|
||||
self._size = float(size)
|
||||
self._dlallowed = dlallowed
|
||||
|
||||
def delete(self) -> None:
|
||||
|
||||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/delete.php',
|
||||
'POST', data={'file': self._name},
|
||||
'POST', data={'file': self._full},
|
||||
sendtoken=True
|
||||
)
|
||||
|
||||
def get_content(self) -> bytes:
|
||||
|
||||
file = self.atserv.atserver_request(
|
||||
f'https://aternos.org/panel/ajax/files/download.php',
|
||||
'https://aternos.org/panel/ajax/files/download.php',
|
||||
'GET', params={
|
||||
'file': self._path
|
||||
'file': self._full
|
||||
}
|
||||
)
|
||||
if not self._dlallowed:
|
||||
raise FileError('Downloading this file is not allowed. Try to get text')
|
||||
if file.content == b'{"success":false}':
|
||||
raise FileError('Unable to download the file. Try to get text')
|
||||
return file.content
|
||||
|
||||
def set_content(self, value:bytes) -> None:
|
||||
|
||||
self.atserv.atserver_request(
|
||||
f'https://aternos.org/panel/ajax/save.php',
|
||||
'POST', data={
|
||||
'file': self._path,
|
||||
'file': self._full,
|
||||
'content': value
|
||||
}, sendtoken=True
|
||||
)
|
||||
|
||||
def get_text(self) -> str:
|
||||
|
||||
editor = self.atserv.atserver_request(
|
||||
f'https://aternos.org/files/{self._name}', 'GET'
|
||||
)
|
||||
edittree = lxml.html.fromstring(editor.content)
|
||||
|
||||
editfield = edittree.xpath('//div[@class="ace_layer ace_text-layer"]')[0]
|
||||
editlines = editfield.xpath('/div[@class="ace_line"]')
|
||||
editlines = edittree.xpath('//div[@class="ace_line"]')
|
||||
rawlines = []
|
||||
|
||||
for line in editlines:
|
||||
|
@ -69,6 +71,7 @@ class AternosFile:
|
|||
return rawlines
|
||||
|
||||
def set_text(self, value:str) -> None:
|
||||
|
||||
self.set_content(value.encode('utf-8'))
|
||||
|
||||
@property
|
||||
|
@ -79,6 +82,10 @@ class AternosFile:
|
|||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def full(self) -> str:
|
||||
return self._full
|
||||
|
||||
@property
|
||||
def is_dir(self) -> bool:
|
||||
if self._ftype == FileType.directory:
|
||||
|
@ -94,7 +101,3 @@ class AternosFile:
|
|||
@property
|
||||
def size(self) -> float:
|
||||
return self._size
|
||||
|
||||
@property
|
||||
def dlallowed(self) -> bool:
|
||||
return self._dlallowed
|
||||
|
|
|
@ -18,21 +18,18 @@ class FileManager:
|
|||
f'https://aternos.org/files/{path}', 'GET'
|
||||
)
|
||||
filestree = lxml.html.fromstring(filesreq.content)
|
||||
fileslist = filestree.xpath(
|
||||
'//div[@class="files"]' + \
|
||||
'/div[@class="directory dropzone"]' + \
|
||||
'/div[@class="file clickable"]'
|
||||
)
|
||||
fileslist = filestree.xpath('//div[@class="file clickable"]')
|
||||
|
||||
files = []
|
||||
for f in fileslist:
|
||||
|
||||
ftype_raw = f.xpath('/@data-type')
|
||||
ftype_raw = f.xpath('@data-type')[0]
|
||||
ftype = FileType.file \
|
||||
if ftype_raw == 'file' \
|
||||
else FileType.directory
|
||||
|
||||
fsize_raw = f.xpath('/div[@class="filesize"]')
|
||||
fsize_raw = f.xpath('./div[@class="filesize"]')
|
||||
print(fsize_raw)
|
||||
fsize = 0
|
||||
if len(fsize_raw) > 0:
|
||||
|
||||
|
@ -45,19 +42,14 @@ class FileManager:
|
|||
except ValueError:
|
||||
fsize = -1
|
||||
|
||||
dlbutton = f.xpath('/div[contains(@class,"js-download-file ")]')
|
||||
dlallowed = False
|
||||
if len(dlbutton) > 0:
|
||||
dlallowed = True
|
||||
|
||||
fullpath = f.xpath('/@data-path')
|
||||
fullpath = f.xpath('@data-path')[0]
|
||||
filepath = fullpath[:fullpath.rfind('/')]
|
||||
filename = fullpath[fullpath.rfind('/'):]
|
||||
files.append(
|
||||
AternosFile(
|
||||
self.atserv,
|
||||
filepath, filename,
|
||||
ftype, fsize, dlallowed
|
||||
ftype, fsize
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import enum
|
||||
import lxml.html
|
||||
from typing import List
|
||||
from typing import List, Union
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -15,40 +15,34 @@ class Lists(enum.Enum):
|
|||
|
||||
class PlayersList:
|
||||
|
||||
def __init__(self, lst:str, atserv:'AternosServer') -> None:
|
||||
|
||||
for ltype in Lists:
|
||||
if ltype.value == lst:
|
||||
break
|
||||
else:
|
||||
raise ValueError(
|
||||
'Incorrect players list type! ' + \
|
||||
'Use atplayers.Lists enum'
|
||||
)
|
||||
def __init__(self, lst:Union[str,Lists], atserv:'AternosServer') -> None:
|
||||
|
||||
self.atserv = atserv
|
||||
self.lst = lst
|
||||
self.lst = Lists(lst)
|
||||
self.players = []
|
||||
self.parsed = False
|
||||
|
||||
def list_players(self, cache:bool=True) -> List[str]:
|
||||
|
||||
if cache:
|
||||
if cache and self.parsed:
|
||||
return self.players
|
||||
|
||||
listreq = self.atserv.atserver_request(
|
||||
f'https://aternos.org/players/{self.lst}', 'GET'
|
||||
f'https://aternos.org/players/{self.lst.value}',
|
||||
'GET'
|
||||
)
|
||||
listtree = lxml.html.fromstring(listreq.content)
|
||||
items = listtree.xpath(
|
||||
'//div[@class="player-list"]' + \
|
||||
'/div[@class="list-item-container"]' + \
|
||||
'/div[@class="list-item"]'
|
||||
'//div[@class="list-item"]'
|
||||
)
|
||||
|
||||
result = []
|
||||
for i in items:
|
||||
name = i.xpath('./div[@class="list-name"]')
|
||||
result.append(name)
|
||||
result.append(name[0].text.strip())
|
||||
|
||||
self.players = result
|
||||
self.parsed = True
|
||||
return result
|
||||
|
||||
def add(self, name:str) -> None:
|
||||
|
@ -56,9 +50,9 @@ class PlayersList:
|
|||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/players/add.php',
|
||||
'POST', data={
|
||||
'list': self.lst,
|
||||
'list': self.lst.value,
|
||||
'name': name
|
||||
}
|
||||
}, sendtoken=True
|
||||
)
|
||||
|
||||
self.players.append(name)
|
||||
|
@ -68,9 +62,9 @@ class PlayersList:
|
|||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/players/remove.php',
|
||||
'POST', data={
|
||||
'list': self.lst,
|
||||
'list': self.lst.value,
|
||||
'name': name
|
||||
}
|
||||
}, sendtoken=True
|
||||
)
|
||||
|
||||
for i, j in enumerate(self.players):
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import enum
|
||||
import re
|
||||
import json
|
||||
import lxml.html
|
||||
from requests import Response
|
||||
from typing import Optional
|
||||
|
||||
|
@ -12,9 +10,6 @@ from .atconf import AternosConfig
|
|||
from .atplayers import PlayersList
|
||||
from .atwss import AternosWss
|
||||
|
||||
JAVA = 0
|
||||
BEDROCK = 1
|
||||
|
||||
class Edition(enum.IntEnum):
|
||||
java = 0
|
||||
bedrock = 1
|
||||
|
@ -32,37 +27,22 @@ class AternosServer:
|
|||
|
||||
def __init__(
|
||||
self, servid:str,
|
||||
atconn:AternosConnect,
|
||||
savelog:bool=True) -> None:
|
||||
atconn:AternosConnect) -> None:
|
||||
|
||||
self.servid = servid
|
||||
self.atconn = atconn
|
||||
self.savelog = savelog
|
||||
self.log = []
|
||||
|
||||
def fetch(self) -> None:
|
||||
|
||||
servreq = self.atserver_request(
|
||||
'https://aternos.org/server', 'GET'
|
||||
'https://aternos.org/panel/ajax/status.php',
|
||||
'GET', sendtoken=True
|
||||
)
|
||||
servtree = lxml.html.fromstring(servreq.content)
|
||||
self._info = json.loads(servreq.content)
|
||||
|
||||
self._info = json.loads(
|
||||
re.search(
|
||||
r'var\s*lastStatus\s*=\s*({.*})',
|
||||
servtree.head.text_content()
|
||||
)[1]
|
||||
)
|
||||
self._ram = 0
|
||||
self._tps = 0
|
||||
def wss(self, autoconfirm:bool=False) -> AternosWss:
|
||||
|
||||
self.atconn.parse_token(servreq.content)
|
||||
self.atconn.generate_sec()
|
||||
|
||||
async def wss(self) -> AternosWss:
|
||||
|
||||
return AternosWss(
|
||||
self.atconn.session.cookies,
|
||||
self.servid
|
||||
)
|
||||
return AternosWss(self, autoconfirm)
|
||||
|
||||
def start(self, headstart:bool=False, accepteula:bool=True) -> None:
|
||||
|
||||
|
@ -98,7 +78,7 @@ class AternosServer:
|
|||
|
||||
elif error == 'file':
|
||||
raise ServerError(
|
||||
'File server is unavailbale, view status.aternos.gmbh'
|
||||
'File server is unavailbale, view https://status.aternos.gmbh'
|
||||
)
|
||||
|
||||
elif error == 'size':
|
||||
|
@ -155,7 +135,7 @@ class AternosServer:
|
|||
|
||||
return AternosConfig(self)
|
||||
|
||||
def get_players(self, lst:str) -> PlayersList:
|
||||
def players(self, lst:str) -> PlayersList:
|
||||
|
||||
return PlayersList(lst, self)
|
||||
|
||||
|
@ -227,9 +207,13 @@ class AternosServer:
|
|||
return self._info['version']
|
||||
|
||||
@property
|
||||
def status(self) -> int:
|
||||
def status(self) -> str:
|
||||
return self._info['class']
|
||||
|
||||
@property
|
||||
def status_num(self) -> int:
|
||||
return int(self._info['status'])
|
||||
|
||||
@property
|
||||
def ram(self) -> int:
|
||||
return self._ram
|
||||
return int(self._info['ram'])
|
||||
|
|
|
@ -3,8 +3,11 @@ import json
|
|||
import asyncio
|
||||
import websockets
|
||||
from typing import Union, Any, Dict, Callable, Coroutine
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .atconnect import REQUA
|
||||
if TYPE_CHECKING:
|
||||
from .atserver import AternosServer
|
||||
|
||||
class Streams(enum.IntEnum):
|
||||
status = 0
|
||||
|
@ -15,11 +18,19 @@ class Streams(enum.IntEnum):
|
|||
|
||||
class AternosWss:
|
||||
|
||||
def __init__(self, session:str, servid:str) -> None:
|
||||
def __init__(self, atserv:'AternosServer', autoconfirm:bool=False) -> None:
|
||||
|
||||
self.session = session
|
||||
self.servid = servid
|
||||
self.atserv = atserv
|
||||
self.cookies = atserv.atconn.session.cookies
|
||||
self.session = self.cookies['ATERNOS_SESSION']
|
||||
self.servid = self.cookies['ATERNOS_SERVER']
|
||||
self.recv = {}
|
||||
self.autoconfirm = autoconfirm
|
||||
self.confirmed = False
|
||||
|
||||
async def confirm(self) -> None:
|
||||
|
||||
self.atserv.confirm()
|
||||
|
||||
def wssreceiver(self, stream:int) -> Callable[[Callable[[Any],Coroutine[Any,Any,None]]],Any]:
|
||||
def decorator(func:Callable[[Any],Coroutine[Any,Any,None]]) -> None:
|
||||
|
@ -29,6 +40,7 @@ class AternosWss:
|
|||
async def connect(self) -> None:
|
||||
|
||||
headers = [
|
||||
('Host', 'aternos.org'),
|
||||
('User-Agent', REQUA),
|
||||
(
|
||||
'Cookie',
|
||||
|
@ -37,10 +49,12 @@ class AternosWss:
|
|||
)
|
||||
]
|
||||
self.socket = await websockets.connect(
|
||||
'wss://aternos.org/hermes',
|
||||
'wss://aternos.org/hermes/',
|
||||
origin='https://aternos.org',
|
||||
extra_headers=headers
|
||||
)
|
||||
asyncio.run(wssworker())
|
||||
|
||||
await self.wssworker()
|
||||
|
||||
async def close(self) -> None:
|
||||
|
||||
|
@ -56,8 +70,8 @@ class AternosWss:
|
|||
|
||||
async def wssworker(self) -> None:
|
||||
|
||||
keep = asyncio.create_task(keepalive())
|
||||
msgs = asyncio.create_task(receiver())
|
||||
keep = asyncio.create_task(self.keepalive())
|
||||
msgs = asyncio.create_task(self.receiver())
|
||||
await keep
|
||||
await msgs
|
||||
|
||||
|
@ -90,6 +104,16 @@ class AternosWss:
|
|||
msgtype = Streams.status
|
||||
msg = json.loads(obj['message'])
|
||||
|
||||
if not self.autoconfirm:
|
||||
continue
|
||||
if msg['class'] == 'queueing' \
|
||||
and msg['queue']['pending'] == 'pending'\
|
||||
and not self.confirmed:
|
||||
t = asyncio.create_task(
|
||||
self.confirm()
|
||||
)
|
||||
await t
|
||||
|
||||
if msgtype in self.recv:
|
||||
t = asyncio.create_task(
|
||||
self.recv[msgtype](msg)
|
||||
|
|
|
@ -1,30 +1,56 @@
|
|||
import re
|
||||
import unittest
|
||||
|
||||
from python_aternos import atjsparse
|
||||
|
||||
# Use tests from a file
|
||||
tests = []
|
||||
class TestJs2Py(unittest.TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
|
||||
self.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
|
||||
self.tests = lines
|
||||
|
||||
for f in tests:
|
||||
self.results = [
|
||||
'2rKOA1IFdBcHhEM616cb'
|
||||
'2rKOA1IFdBcHhEM616cb'
|
||||
'2rKOA1IFdBcHhEM616cb'
|
||||
'2rKOA1IFdBcHhEM616cb'
|
||||
'2rKOA1IFdBcHhEM616cb'
|
||||
'2rKOA1IFdBcHhEM616cb'
|
||||
'2rKOA1IFdBcHhEM616cb'
|
||||
'2rKOA1IFdBcHhEM616cb'
|
||||
'2rKOA1IFdBcHhEM616cb'
|
||||
'2iXh5W5uEYq5fWJIazQ6'
|
||||
'CuUcmZ27Fb8bVBNw12Vj'
|
||||
'YPPe8Ph7vzYaZ9PF9oQP'
|
||||
'UfLlemvKEE16ltk0hZNM'
|
||||
'q6pYdP6r7xiVHhbotvlN'
|
||||
'q6pYdP6r7xiVHhbotvlN'
|
||||
'XAIbksgkVX9JYboMDI7D'
|
||||
]
|
||||
|
||||
def test_base64(self) -> None:
|
||||
|
||||
encoded = 'QEhlbGxvIFdvcmxkIQ=='
|
||||
decoded = atjsparse.atob(encoded)
|
||||
self.assertEqual(decoded, '@Hello World!')
|
||||
|
||||
def test_conv(self) -> None:
|
||||
|
||||
token = '(() => {window["AJAX_TOKEN"]=("2r" + "KO" + "A1" + "IFdBcHhEM" + "61" + "6cb");})();'
|
||||
f = atjsparse.to_ecma5_function(token)
|
||||
self.assertEqual(f, '(function(){window["AJAX_TOKEN"]=("2r" + "KO" + "A1" + "IFdBcHhEM" + "61" + "6cb");})()')
|
||||
|
||||
def test_exec(self) -> None:
|
||||
|
||||
for i, f in enumerate(self.tests):
|
||||
ctx = atjsparse.exec(f)
|
||||
print(ctx.window['AJAX_TOKEN'])
|
||||
res = ctx.window['AJAX_TOKEN']
|
||||
self.assertEqual(res, self.results[i])
|
||||
|
||||
# 2rKOA1IFdBcHhEM616cb
|
||||
# 2rKOA1IFdBcHhEM616cb
|
||||
# 2rKOA1IFdBcHhEM616cb
|
||||
# 2rKOA1IFdBcHhEM616cb
|
||||
# 2rKOA1IFdBcHhEM616cb
|
||||
# 2rKOA1IFdBcHhEM616cb
|
||||
# 2rKOA1IFdBcHhEM616cb
|
||||
# 2rKOA1IFdBcHhEM616cb
|
||||
# 2rKOA1IFdBcHhEM616cb
|
||||
# 2iXh5W5uEYq5fWJIazQ6
|
||||
# CuUcmZ27Fb8bVBNw12Vj
|
||||
# YPPe8Ph7vzYaZ9PF9oQP
|
||||
# UfLlemvKEE16ltk0hZNM
|
||||
# q6pYdP6r7xiVHhbotvlN
|
||||
# q6pYdP6r7xiVHhbotvlN
|
||||
# XAIbksgkVX9JYboMDI7D
|
||||
def tearDown(self) -> None:
|
||||
del self.tests
|
||||
del self.results
|
||||
|
|
Reference in a new issue