This repository has been archived on 2024-07-30. You can view files and clone it, but cannot push or open issues or pull requests.
python-aternos/python_aternos/atconf.py
DarkCat09 4892430f19 MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.

Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00

378 lines
9.9 KiB
Python

"""Modifying server and world options"""
import enum
import re
from typing import Any, Dict, List, Union, Optional
from typing import TYPE_CHECKING
import lxml.html
if TYPE_CHECKING:
from .atserver import AternosServer
DAT_PREFIX = 'Data:'
DAT_GR_PREFIX = 'Data:GameRules:'
class ServerOpts(enum.Enum):
"""server.options file"""
players = 'max-players'
gm = 'gamemode'
difficulty = 'difficulty'
whl = 'white-list'
online = 'online-mode'
pvp = 'pvp'
cmdblock = 'enable-command-block'
flight = 'allow-flight'
animals = 'spawn-animals'
monsters = 'spawn-monsters'
villagers = 'spawn-npcs'
nether = 'allow-nether'
forcegm = 'force-gamemode'
spawnlock = 'spawn-protection'
cmds = 'allow-cheats'
packreq = 'require-resource-pack'
pack = 'resource-pack'
class WorldOpts(enum.Enum):
"""level.dat file"""
seed12 = 'randomseed'
seed = 'seed'
hardcore = 'hardcore'
difficulty = 'Difficulty'
class WorldRules(enum.Enum):
"""/gamerule list"""
advs = 'announceAdvancements'
univanger = 'universalAnger'
cmdout = 'commandBlockOutput'
elytra = 'disableElytraMovementCheck'
raids = 'disableRaids'
daynight = 'doDaylightCycle'
entdrop = 'doEntityDrops'
fire = 'doFireTick'
phantoms = 'doInsomnia'
immrespawn = 'doImmediateRespawn'
limitcraft = 'doLimitedCrafting'
mobloot = 'doMobLoot'
mobs = 'doMobSpawning'
patrols = 'doPatrolSpawning'
blockdrop = 'doTileDrops'
traders = 'doTraderSpawning'
weather = 'doWeatherCycle'
drowndmg = 'drowningDamage'
falldmg = 'fallDamage'
firedmg = 'fireDamage'
snowdmg = 'freezeDamage'
forgive = 'forgiveDeadPlayers'
keepinv = 'keepInventory'
deathmsg = 'showDeathMessages'
admincmdlog = 'logAdminCommands'
cmdlen = 'maxCommandChainLength'
entcram = 'maxEntityCramming'
mobgrief = 'mobGriefing'
regen = 'naturalRegeneration'
sleeppct = 'playersSleepingPercentage'
rndtick = 'randomTickSpeed'
spawnradius = 'spawnRadius'
reducedf3 = 'reducedDebugInfo'
spectchunkgen = 'spectatorsGenerateChunks'
cmdfb = 'sendCommandFeedback'
class Gamemode(enum.IntEnum):
"""/gamemode numeric list"""
survival = 0
creative = 1
adventure = 2
spectator = 3
class Difficulty(enum.IntEnum):
"""/difficulty numeric list"""
peaceful = 0
easy = 1
normal = 2
hard = 3
# 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
}
class AternosConfig:
"""Class for editing server settings"""
def __init__(self, atserv: 'AternosServer') -> None:
"""Class for editing server settings
Args:
atserv (python_aternos.atserver.AternosServer):
atserver.AternosServer object
"""
self.atserv = atserv
def get_timezone(self) -> str:
"""Parses timezone from options page
Returns:
Area/Location
"""
optreq = self.atserv.atserver_request(
'https://aternos.org/options', 'GET'
)
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()
def set_timezone(self, value: str) -> None:
"""Sets new timezone
Args:
value (str): New timezone
Raises:
ValueError: If given string doesn't
match `Area/Location` format
"""
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',
'POST', data={'timezone': value},
sendtoken=True
)
def get_java(self) -> int:
"""Parses Java version from options page
Returns:
Java image version
"""
optreq = self.atserv.atserver_request(
'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]
jdkver = str(imgver or '').removeprefix('openjdk:')
return int(jdkver)
def set_java(self, value: int) -> None:
"""Sets new Java version
Args:
value (int): New Java image version
"""
self.atserv.atserver_request(
'https://aternos.org/panel/ajax/image.php',
'POST', data={'image': f'openjdk:{value}'},
sendtoken=True
)
#
# server.properties
#
def set_server_prop(self, option: str, value: Any) -> None:
"""Sets server.properties option
Args:
option (str): Option name
value (Any): New value
"""
self.__set_prop(
'/server.properties',
option, value
)
def get_server_props(self, proptyping: bool = True) -> Dict[str, Any]:
"""Parses all server.properties from options page
Args:
proptyping (bool, optional):
If the returned dict should
contain value that matches
property type (e.g. max-players will be int)
instead of string
Returns:
`server.properties` dictionary
"""
return self.__get_all_props('https://aternos.org/options', proptyping)
def set_server_props(self, props: Dict[str, Any]) -> None:
"""Updates server.properties options with the given dict
Args:
props (Dict[str,Any]):
Dictionary with `{key:value}` properties
"""
for key in props:
self.set_server_prop(key, props[key])
#
# level.dat
#
def set_world_prop(
self, option: Union[WorldOpts, WorldRules],
value: Any, gamerule: bool = False,
world: str = 'world') -> None:
"""Sets level.dat option for specified world
Args:
option (Union[WorldOpts, WorldRules]): Option name
value (Any): New value
gamerule (bool, optional): If the option is a gamerule
world (str, optional): Name of the world which
`level.dat` must be edited
"""
prefix = DAT_PREFIX
if gamerule:
prefix = DAT_GR_PREFIX
self.__set_prop(
f'/{world}/level.dat',
f'{prefix}{option}',
value
)
def get_world_props(
self, world: str = 'world',
proptyping: bool = True) -> Dict[str, Any]:
"""Parses level.dat from specified world's options page
Args:
world (str, optional): Name of the worl
proptyping (bool, optional):
If the returned dict should
contain the value that matches
property type (e.g. randomTickSpeed will be bool)
instead of string
Returns:
`level.dat` options dictionary
"""
return self.__get_all_props(
f'https://aternos.org/files/{world}/level.dat',
proptyping, [DAT_PREFIX, DAT_GR_PREFIX]
)
def set_world_props(
self,
props: Dict[Union[WorldOpts, WorldRules], Any],
world: str = 'world') -> None:
"""Sets level.dat options from
the dictionary for the specified world
Args:
props (Dict[Union[WorldOpts, WorldRules], Any]):
`level.dat` options
world (str): name of the world which
`level.dat` must be edited
"""
for key in props:
self.set_world_prop(
option=key,
value=props[key],
world=world
)
#
# helpers
#
def __set_prop(self, file: str, option: str, value: Any) -> None:
self.atserv.atserver_request(
'https://aternos.org/panel/ajax/config.php',
'POST', data={
'file': file,
'option': option,
'value': value
}, sendtoken=True
)
def __get_all_props(
self, url: str, proptyping: bool = True,
prefixes: Optional[List[str]] = None) -> Dict[str, Any]:
optreq = self.atserv.atserver_request(url, 'GET')
opttree = lxml.html.fromstring(optreq.content)
configs = opttree.xpath('//div[@class="config-options"]')
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 is not None:
key = f'{prefixes[i]}{key}'
opttype = opt.xpath('/@class').split(' ')[1]
if proptyping and opttype in convert:
value = convert[opttype](value)
result[key] = value
return result