Server config feauture, players management, bugfix
This commit is contained in:
parent
a055f9ae9d
commit
61460b2f74
6 changed files with 383 additions and 10 deletions
219
python_aternos/atconf.py
Normal file
219
python_aternos/atconf.py
Normal file
|
@ -0,0 +1,219 @@
|
|||
import re
|
||||
import lxml.html
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from . import atserver
|
||||
from . import atconnect
|
||||
|
||||
OPT_PLAYERS = 'max-players'
|
||||
OPT_GAMEMODE = 'gamemode'
|
||||
OPT_DIFFICULTY = 'difficulty'
|
||||
OPT_WHITELIST = 'white-list'
|
||||
OPT_ONLINE = 'online-mode'
|
||||
OPT_PVP = 'pvp'
|
||||
OPT_CMDBLOCK = 'enable-command-block'
|
||||
OPT_FLIGHT = 'allow-flight'
|
||||
OPT_ANIMALS = 'spawn-animals'
|
||||
OPT_MONSTERS = 'spawn-monsters'
|
||||
OPT_VILLAGERS = 'spawn-npcs'
|
||||
OPT_NETHER = 'allow-nether'
|
||||
OPT_FORCEGM = 'force-gamemode'
|
||||
OPT_SPAWNLOCK = 'spawn-protection'
|
||||
OPT_CHEATS = 'allow-cheats'
|
||||
OPT_RESOURCEPACK = 'resource-pack'
|
||||
|
||||
DAT_PREFIX = 'Data:'
|
||||
DAT_SEED = 'RandomSeed'
|
||||
DAT_HARDCORE = 'hardcore'
|
||||
DAT_DIFFICULTY = 'Difficulty'
|
||||
|
||||
DAT_GR_PREFIX = 'Data:GameRules:'
|
||||
DAT_GR_ADVS = 'announceAdvancements'
|
||||
DAT_GR_CMDOUT = 'commandBlockOutput'
|
||||
DAT_GR_ELYTRA = 'disableElytraMovementCheck'
|
||||
DAT_GR_DAYLIGHT = 'doDaylightCycle'
|
||||
DAT_GR_ENTDROPS = 'doEntityDrops'
|
||||
DAT_GR_FIRETICK = 'doFireTick'
|
||||
DAT_GR_LIMITCRAFT = 'doLimitedCrafting'
|
||||
DAT_GR_MOBLOOT = 'doMobLoot'
|
||||
DAT_GR_MOBS = 'doMobSpawning'
|
||||
DAT_GR_TILEDROPS = 'doTileDrops'
|
||||
DAT_GR_WEATHER = 'doWeatherCycle'
|
||||
DAT_GR_KEEPINV = 'keepInventory'
|
||||
DAT_GR_DEATHMSG = 'showDeathMessages'
|
||||
DAT_GR_ADMINCMDLOG = 'logAdminCommands'
|
||||
DAT_GR_CMDLEN = 'maxCommandChainLength'
|
||||
DAT_GR_ENTCRAM = 'maxEntityCramming'
|
||||
DAT_GR_MOBGRIEF = 'mobGriefing'
|
||||
DAT_GR_REGEN = 'naturalRegeneration'
|
||||
DAT_GR_RNDTICK = 'randomTickSpeed'
|
||||
DAT_GR_SPAWNRADIUS = 'spawnRadius'
|
||||
DAT_GR_REDUCEDF3 = 'reducedDebugInfo'
|
||||
DAT_GR_SPECTCHUNK = 'spectatorsGenerateChunks'
|
||||
DAT_GR_CMDFB = 'sendCommandFeedback'
|
||||
|
||||
DAT_TYPE_WORLD = 0
|
||||
DAT_TYPE_GR = 1
|
||||
|
||||
GM_SURVIVAL = 0
|
||||
GM_CREATIVE = 1
|
||||
GM_ADVENTURE = 2
|
||||
GM_SPECTATOR = 3
|
||||
|
||||
DF_PEACEFUL = 0
|
||||
DF_EASY = 1
|
||||
DF_NORMAL = 2
|
||||
DF_HARD = 3
|
||||
|
||||
JAVA_JDK = 'openjdk:{}'
|
||||
JAVA_OPENJ9 = 'adoptopenjdk:{}-jre-openj9-bionic'
|
||||
|
||||
FLAG_PROP_TYPE = 1
|
||||
|
||||
class AternosConfig:
|
||||
|
||||
def __init__(self, atserv:atserver.AternosServer) -> None:
|
||||
|
||||
self.atserv = atserv
|
||||
|
||||
@property
|
||||
def timezone(self) -> str:
|
||||
optreq = self.atserv.atserver_request(
|
||||
'https://aternos.org/options',
|
||||
atconnect.REQGET
|
||||
)
|
||||
opttree = lxml.html.fromstring(optreq)
|
||||
|
||||
tzopt = opttree.xpath('//div[@class="options-other-input timezone-switch"]')[0]
|
||||
tztext = tzopt.xpath('.//div[@class="option current"]')[0].text
|
||||
return tztext.strip()
|
||||
|
||||
@timezone.setter
|
||||
def 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')
|
||||
|
||||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/timezone.php',
|
||||
atconnect.REQPOST, data={'timezone': value},
|
||||
sendtoken=True
|
||||
)
|
||||
|
||||
@property
|
||||
def java_version(self) -> str:
|
||||
optreq = self.atserv.atserver_request(
|
||||
'https://aternos.org/options',
|
||||
atconnect.REQGET
|
||||
)
|
||||
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('Java image version must match "[adopt]openjdk:%d[-jre-openj9-bionic]" format')
|
||||
|
||||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/image.php',
|
||||
atconnect.REQPOST, data={'image': value},
|
||||
sendtoken=True
|
||||
)
|
||||
|
||||
#
|
||||
# server.properties
|
||||
#
|
||||
def set_server_prop(self, option:str, value:Any) -> None:
|
||||
self.__set_prop(
|
||||
'/server.properties',
|
||||
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 set_server_props(self, props:Dict[str,Any]) -> None:
|
||||
for key in props:
|
||||
set_server_prop(key, props[key])
|
||||
|
||||
#
|
||||
# level.dat
|
||||
#
|
||||
def set_world_prop(
|
||||
self, option:str, value:Any,
|
||||
proptype:int, world:str='world') -> None:
|
||||
prefix = DAT_PREFIX
|
||||
if proptype == DAT_TYPE_GR:
|
||||
prefix = DAT_GR_PREFIX
|
||||
|
||||
self.__set_prop(
|
||||
f'/{world}/level.dat',
|
||||
f'{prefix}{option}',
|
||||
value
|
||||
)
|
||||
|
||||
def get_world_props(
|
||||
self, world:str='world',
|
||||
flags:int=FLAG_PROP_TYPE) -> Dict[str,Any]:
|
||||
self.__get_all_props(
|
||||
f'https://aternos.org/files/{world}/level.dat',
|
||||
flags, [DAT_PREFIX, DAT_GR_PREFIX]
|
||||
)
|
||||
|
||||
def set_world_props(self, props:Dict[str,Any]) -> None:
|
||||
for key in props:
|
||||
set_world_prop(key, prop[key])
|
||||
|
||||
#
|
||||
# helpers
|
||||
#
|
||||
def __set_prop(self, file:str, option:str, value:Any) -> None:
|
||||
|
||||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/config.php',
|
||||
atconnect.REQPOST, data={
|
||||
'file': file,
|
||||
'option': option,
|
||||
'value': value
|
||||
}, sendtoken=True
|
||||
)
|
||||
|
||||
def __get_all_props(
|
||||
self, url:str, flags:int=FLAG_PROP_TYPE,
|
||||
prefixes:Optional[List[str]]=None) -> Dict[str,Any]:
|
||||
|
||||
optreq = self.atserv.atserver_request(
|
||||
'https://aternos.org/options',
|
||||
atconnect.REQGET
|
||||
)
|
||||
opttree = lxml.html.fromstring(optreq.content)
|
||||
|
||||
configs = opttree.xpath('//div[@class="config-options"]')
|
||||
for i in range(len(configs)):
|
||||
|
||||
conf = configs[i]
|
||||
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 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
|
|
@ -9,3 +9,7 @@ class AternosCredentialsError(AternosError):
|
|||
class AternosServerStartError(AternosError):
|
||||
|
||||
pass
|
||||
|
||||
class AternosIOError(AternosError):
|
||||
|
||||
pass
|
||||
|
|
|
@ -12,12 +12,14 @@ class AternosFile:
|
|||
def __init__(
|
||||
atserv:atserver.AternosServer,
|
||||
path:str, name:str, ftype:int=FTYPE_FILE,
|
||||
size:Union[]=0) -> None:
|
||||
size:Union[int,float]=0, dlallowed:bool=False) -> None:
|
||||
|
||||
self.atserv = atserv
|
||||
self._path = path
|
||||
self._name = name
|
||||
self._ftype = ftype
|
||||
self._size = float(size)
|
||||
self._dlallowed = dlallowed
|
||||
|
||||
def delete(self) -> None:
|
||||
|
||||
|
@ -27,6 +29,24 @@ class AternosFile:
|
|||
sendtoken=True
|
||||
)
|
||||
|
||||
@property
|
||||
def content(self) -> bytes:
|
||||
file = self.atserv.atserver_request(
|
||||
f'https://aternos.org/panel/ajax/files/download.php',
|
||||
atconnect.REQGET, params={'file': self.path.replace('/','%2F')}
|
||||
)
|
||||
if not self._dlallowed:
|
||||
raise AternosIOError('Downloading this file is not allowed. Try to get text')
|
||||
return file.content
|
||||
|
||||
@content.setter
|
||||
def content(self, value:bytes) -> None:
|
||||
self.atserv.atserver_request(
|
||||
f'https://aternos.org/panel/ajax/save.php',
|
||||
atconnect.REQPOST, data={'content': value},
|
||||
sendtoken=True
|
||||
)
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
editor = self.atserv.atserver_request(
|
||||
|
@ -44,21 +64,37 @@ class AternosFile:
|
|||
return rawlines
|
||||
|
||||
@text.setter
|
||||
def text(self, value:Union[str,bytes]) -> None:
|
||||
def text(self, value:str) -> None:
|
||||
self.atserv.atserver_request(
|
||||
f'https://aternos.org/panel/ajax/save.php',
|
||||
atconnect.REQPOST, data={'content': value},
|
||||
sendtoken=True
|
||||
)
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def ftype(self) -> int:
|
||||
return self._ftype
|
||||
def is_dir(self) -> bool:
|
||||
if self._ftype == FTYPE_DIR:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_file(self) -> bool:
|
||||
if self._ftype == FTYPE_FILE:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def size(self) -> float:
|
||||
return self._size
|
||||
|
||||
@property
|
||||
def dlallowed(self):
|
||||
return self._dlallowed
|
||||
|
|
|
@ -44,6 +44,11 @@ class AternosFileManager:
|
|||
except ValueError:
|
||||
fsize = -1
|
||||
|
||||
dlbutton = f.xpath('/div[@class="js-download-file btn btn-main btn-small btn-notext btn-no-margin"]')
|
||||
dlallowed = False
|
||||
if len(dlbutton) > 0:
|
||||
dlallowed = True
|
||||
|
||||
fullpath = f.xpath('/@data-path')[0]
|
||||
filepath = fullpath[:fullpath.rfind('/')]
|
||||
filename = fullpath[fullpath.rfind('/'):]
|
||||
|
@ -51,7 +56,7 @@ class AternosFileManager:
|
|||
atfile.AternosFile(
|
||||
self.atserv,
|
||||
filepath, filename,
|
||||
ftype, fsize
|
||||
ftype, fsize, dlallowed
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -87,7 +92,7 @@ class AternosFileManager:
|
|||
|
||||
file = self.atserv.atserver_request(
|
||||
f'https://aternos.org/panel/ajax/files/download.php?' + \
|
||||
f'file={path.replace('/','%2F')}',
|
||||
f'file={path.replace("/","%2F")}',
|
||||
atconnect.REQGET
|
||||
)
|
||||
|
||||
|
@ -97,7 +102,7 @@ class AternosFileManager:
|
|||
|
||||
world = self.atserv.atserver_request(
|
||||
f'https://aternos.org/panel/ajax/worlds/download.php?' + \
|
||||
f'world={world.replace('/','%2F')}',
|
||||
f'world={world.replace("/","%2F")}',
|
||||
atconnect.REQGET
|
||||
)
|
||||
|
||||
|
|
50
python_aternos/atplayers.py
Normal file
50
python_aternos/atplayers.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
import lxml.html
|
||||
from typing import List
|
||||
|
||||
from . import atserver
|
||||
from . import atconnect
|
||||
|
||||
class AternosPlayersList:
|
||||
|
||||
def __init__(self, lst:str, atserv:atserver.AternosServer) -> None:
|
||||
|
||||
self.atserv = atserv
|
||||
self.lst = lst
|
||||
|
||||
def add(self, name:str) -> None:
|
||||
|
||||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/players/add.php',
|
||||
atconnect.REQPOST, data={
|
||||
'list': self.lst,
|
||||
'name': name
|
||||
}
|
||||
)
|
||||
|
||||
def remove(self, name:str) -> None:
|
||||
|
||||
self.atserv.atserver_request(
|
||||
'https://aternos.org/panel/ajax/players/remove.php',
|
||||
atconnect.REQPOST, data={
|
||||
'list': self.lst,
|
||||
'name': name
|
||||
}
|
||||
)
|
||||
|
||||
@property
|
||||
def players(self) -> List[str]:
|
||||
listreq = atserv.atserver_request(
|
||||
f'https://aternos.org/players/{lst}',
|
||||
atconnect.REQGET
|
||||
)
|
||||
listtree = lxml.html.fromstring(listreq.content)
|
||||
|
||||
items = listtree.xpath(
|
||||
'//div[@class="player-list"]' + \
|
||||
'/div[@class="list-item-container"]' + \
|
||||
'/div[@class="list-item"]'
|
||||
)
|
||||
result = []
|
||||
for i in items:
|
||||
name = i.xpath('./div[@class="list-name"]')
|
||||
result.append(name)
|
|
@ -7,6 +7,22 @@ from typing import Optional, Dict
|
|||
from . import atconnect
|
||||
from . import aterrors
|
||||
from . import atfm
|
||||
from . import atconf
|
||||
from . import atplayers
|
||||
|
||||
SOFTWARE_JAVA = 0
|
||||
SOFTWARE_BEDROCK = 1
|
||||
|
||||
PLAYERS_ALLOWED = 'whitelist'
|
||||
PLAYERS_OPS = 'ops'
|
||||
PLAYERS_BANNED = 'banned-players'
|
||||
PLAYERS_IPS = 'banned-ips'
|
||||
|
||||
STATUS_OFFLINE = 0
|
||||
STATUS_ONLINE = 1
|
||||
STATUS_LOADING = 2
|
||||
STATUS_SHUTDOWN = 3
|
||||
STATUS_ERROR = 7
|
||||
|
||||
class AternosServer:
|
||||
|
||||
|
@ -98,6 +114,14 @@ class AternosServer:
|
|||
|
||||
return atfm.AternosFileManager(self)
|
||||
|
||||
def config(self) -> atconf.AternosConfig:
|
||||
|
||||
return atconf.AternosConfig(self)
|
||||
|
||||
def players(self, lst:str) -> atplayers.AternosPlayersList:
|
||||
|
||||
return atplayers.AternosPlayersList(lst, self)
|
||||
|
||||
def atserver_request(
|
||||
self, url:str, method:int,
|
||||
params:Optional[dict]=None,
|
||||
|
@ -116,8 +140,27 @@ class AternosServer:
|
|||
)
|
||||
|
||||
@property
|
||||
def info(self) -> dict:
|
||||
return self._info
|
||||
def subdomain(self) -> str:
|
||||
atdomain = self.domain
|
||||
return atdomain[:atdomain.find('.')]
|
||||
|
||||
@subdomain.setter
|
||||
def subdomain(self, value:str) -> None:
|
||||
self.atserver_request(
|
||||
'https://aternos.org/panel/ajax/options/subdomain.php',
|
||||
atconnect.REQGET, params={'subdomain': value}
|
||||
)
|
||||
|
||||
@property
|
||||
def motd(self) -> str:
|
||||
return self._info['motd']
|
||||
|
||||
@motd.setter
|
||||
def motd(self, value:str) -> None:
|
||||
self.atserver_request(
|
||||
'https://aternos.org/panel/ajax/options/motd.php',
|
||||
atconnect.REQPOST, data={'motd': value}
|
||||
)
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
|
@ -125,12 +168,20 @@ class AternosServer:
|
|||
|
||||
@property
|
||||
def domain(self) -> str:
|
||||
return self._info['displayAddress']
|
||||
return self._info['ip']
|
||||
|
||||
@property
|
||||
def port(self) -> int:
|
||||
return self._info['port']
|
||||
|
||||
@property
|
||||
def edition(self) -> int:
|
||||
soft_type = self._info['bedrock']
|
||||
if soft_type == True:
|
||||
return SOFTWARE_BEDROCK
|
||||
else:
|
||||
return SOFTWARE_JAVA
|
||||
|
||||
@property
|
||||
def software(self) -> str:
|
||||
return self._info['software']
|
||||
|
@ -138,3 +189,11 @@ class AternosServer:
|
|||
@property
|
||||
def version(self) -> str:
|
||||
return self._info['version']
|
||||
|
||||
@property
|
||||
def status(self) -> int:
|
||||
return int(self._info['status'])
|
||||
|
||||
@property
|
||||
def ram(self) -> int:
|
||||
return int(self._info['ram'])
|
||||
|
|
Reference in a new issue