Finally fixed 400 by updating URLs, improved logging

This commit is contained in:
DarkCat09 2023-05-29 11:44:19 +04:00
parent 8ae655a34e
commit 4baf4ea1a7
17 changed files with 174 additions and 267 deletions

View file

@ -1,74 +0,0 @@
aternos.org/software/v/**TYPE**/**MCVER**-latest|recommended|**SOFTVER**
(AternosSoftware) //div[@class="version-title"]
(software_name) /h1[@class="version-title-name"]
(software_id) /div[@id="install-software"]/@data-software
GET software/install.php
software: rLyATopqZP79WHHR
reinstall: 0 OR 1
GET confirm.php
GET config.php
file: /server.properties OR /world/level.dat
option: max-players OR resource-pack OR Data:hardcore OR Data:GameRules:commandBlockOutput
value: 20
GET timezone.php
timezone: Europe/Ulyanovsk
GET image.php
image: openjdk:8
GET mclogs.php
(save log to mclo.gs)
response.json().id
https://api.mclo.gs/1/raw/**ID**
POST create.php
file: /config/hello
type: directory OR file
POST delete.php
file: /config/123.txt
POST save.php
file: /config/123.txt
content: ... (x-www-form-urlencoded; charset=UTF-8)
GET files/download.php?file=**FILENAME_ABSOLUTE**
(ex. file=/world will download in ZIP all directory)
GET worlds/download.php?world=**WORLD_NAME**
GET players/add.php,remove.php
list: whitelist,ops,banned-players,banned-ips
name: CodePicker13 *OR* 1.2.3.4(in case of IP)
(list players) //div[@class="page-content page-players"]/div[@class="player-list"]/div[@class="list-item-container"]
(players[...]) ./div[@class="list-item"]/div[@class="list-name"] (and class="list-avatar")
POST friends/create.php
username: t3test
(LISTUSERIDs) //div[@class="friends-share-list list-players"]/div[@class="list-item-container"]/@data-id
POST friends/delete.php
id: **LISTUSERID**
POST friends/update.php
id: **LISTUSERID**
permissions: json(permissions)
GET driveBackup/autoBackups.php?enabled=**0or1**&amount=**AUTOBACKUPS_COUNT_LIMIT**
(list backups) //div[@class="backups"]/div[@class="file"]
(backups[...]) ./@id, re.search(r'backup-(\w+)', _)[1]
(backups[...]) ./div[@class="filename"] (/span[@class="backup-time js-date-time"], then /@data-date or content)
(backups[...]) ./div[@class="backup-user,filesize"]
POST driveBackup/create.php
name: MyBackup2
POST driveBackup/restore.php,delete.php
backupID: 5
GET /panel/img/skin.php?name=**NICKNAME**
(get player's head in png)

View file

@ -1,7 +1,5 @@
from getpass import getpass from getpass import getpass
from typing import Optional
from python_aternos import Client from python_aternos import Client
from python_aternos.atfile import AternosFile
user = input('Username: ') user = input('Username: ')
pswd = getpass('Password: ') pswd = getpass('Password: ')

View file

@ -11,7 +11,10 @@ atclient.login(user, pswd)
srvs = aternos.list_servers() srvs = aternos.list_servers()
for srv in srvs: for srv in srvs:
print('***', srv.domain, '***') print()
print('***', srv.servid, '***')
srv.fetch()
print(srv.domain)
print(srv.motd) print(srv.motd)
print('*** Status:', srv.status) print('*** Status:', srv.status)
print('*** Full address:', srv.address) print('*** Full address:', srv.address)
@ -20,3 +23,5 @@ for srv in srvs:
print('*** Minecraft:', srv.software, srv.version) print('*** Minecraft:', srv.software, srv.version)
print('*** IsBedrock:', srv.edition == atserver.Edition.bedrock) print('*** IsBedrock:', srv.edition == atserver.Edition.bedrock)
print('*** IsJava:', srv.edition == atserver.Edition.java) print('*** IsJava:', srv.edition == atserver.Edition.java)
print()

View file

@ -1,23 +1,25 @@
import asyncio import asyncio
import logging
from getpass import getpass from getpass import getpass
from typing import Tuple, Dict, Any from typing import Tuple, Dict, Any
from python_aternos import Client, Streams from python_aternos import Client, Streams
# Request credentials # Request credentials
user = input('Username: ') user = input('Username: ')
pswd = getpass('Password: ') pswd = getpass('Password: ')
# Debug logging # Instantiate Client
logs = input('Show detailed logs? (y/n) ').strip().lower() == 'y'
if logs:
logging.basicConfig(level=logging.DEBUG)
# Authentication
atclient = Client() atclient = Client()
aternos = atclient.account aternos = atclient.account
# Enable debug logging
logs = input('Show detailed logs? (y/n) ').strip().lower() == 'y'
if logs:
atclient.debug = True
# Authenticate
atclient.login(user, pswd) atclient.login(user, pswd)
server = aternos.list_servers()[0] server = aternos.list_servers()[0]

View file

@ -1,23 +1,25 @@
import asyncio import asyncio
import logging
from getpass import getpass from getpass import getpass
from typing import Tuple, Dict, Any from typing import Tuple, Dict, Any
from python_aternos import Client, Streams from python_aternos import Client, Streams
# Request credentials # Request credentials
user = input('Username: ') user = input('Username: ')
pswd = getpass('Password: ') pswd = getpass('Password: ')
# Debug logging # Instantiate Client
logs = input('Show detailed logs? (y/n) ').strip().lower() == 'y'
if logs:
logging.basicConfig(level=logging.DEBUG)
# Authentication
atclient = Client() atclient = Client()
aternos = atclient.account aternos = atclient.account
# Enable debug logging
logs = input('Show detailed logs? (y/n) ').strip().lower() == 'y'
if logs:
atclient.debug = True
# Authenticate
atclient.login(user, pswd) atclient.login(user, pswd)
server = aternos.list_servers()[0] server = aternos.list_servers()[0]

View file

@ -74,9 +74,9 @@ max-args=10
max-attributes=10 max-attributes=10
max-bool-expr=5 max-bool-expr=5
max-branches=12 max-branches=12
max-locals=16 max-locals=20
max-parents=7 max-parents=7
max-public-methods=30 max-public-methods=31
max-returns=6 max-returns=6
max-statements=50 max-statements=50
min-public-methods=2 min-public-methods=2

View file

@ -1,52 +1,11 @@
""" """Init"""
Unofficial Aternos API module written in Python.
It uses Aternos' private API and html parsing"""
from .atclient import Client from .atclient import Client
from .atserver import AternosServer from .atserver import AternosServer
from .atserver import Edition from .atserver import Edition
from .atserver import Status from .atserver import Status
from .atconnect import AternosConnect
from .atplayers import PlayersList from .atplayers import PlayersList
from .atplayers import Lists from .atplayers import Lists
from .atconf import AternosConfig
from .atconf import ServerOpts
from .atconf import WorldOpts
from .atconf import WorldRules
from .atconf import Gamemode
from .atconf import Difficulty
from .atwss import AternosWss
from .atwss import Streams from .atwss import Streams
from .atfm import FileManager
from .atfile import AternosFile
from .atfile import FileType
from .aterrors import AternosError
from .aterrors import CloudflareError
from .aterrors import CredentialsError
from .aterrors import TokenError
from .aterrors import ServerError
from .aterrors import ServerStartError
from .aterrors import FileError
from .aterrors import AternosPermissionError
from .atjsparse import Js2PyInterpreter from .atjsparse import Js2PyInterpreter
from .atjsparse import NodeInterpreter from .atjsparse import NodeInterpreter
__all__ = [
'atclient', 'atserver', 'atconnect',
'atplayers', 'atconf', 'atwss',
'atfm', 'atfile',
'aterrors', 'atjsparse',
'Client', 'AternosServer', 'AternosConnect',
'PlayersList', 'AternosConfig', 'AternosWss',
'FileManager', 'AternosFile', 'AternosError',
'CloudflareError', 'CredentialsError', 'TokenError',
'ServerError', 'ServerStartError', 'FileError',
'AternosPermissionError',
'Js2PyInterpreter', 'NodeInterpreter',
'Edition', 'Status', 'Lists',
'ServerOpts', 'WorldOpts', 'WorldRules',
'Gamemode', 'Difficulty', 'Streams', 'FileType',
]

View file

@ -21,6 +21,7 @@ if TYPE_CHECKING:
from .atclient import Client from .atclient import Client
ACCOUNT_URL = f'{AJAX_URL}/account'
email_re = re.compile( email_re = re.compile(
r'^[A-Za-z0-9\-_+.]+@[A-Za-z0-9\-_+.]+\.[A-Za-z0-9\-]+$|^$' r'^[A-Za-z0-9\-_+.]+@[A-Za-z0-9\-_+.]+\.[A-Za-z0-9\-]+$|^$'
) )
@ -116,7 +117,7 @@ class AternosAccount:
""" """
self.atconn.request_cloudflare( self.atconn.request_cloudflare(
f'{AJAX_URL}/account/username.php', f'{ACCOUNT_URL}/username',
'POST', data={'username': value}, 'POST', data={'username': value},
sendtoken=True, sendtoken=True,
) )
@ -136,7 +137,7 @@ class AternosAccount:
raise ValueError('Invalid e-mail') raise ValueError('Invalid e-mail')
self.atconn.request_cloudflare( self.atconn.request_cloudflare(
f'{AJAX_URL}/account/email.php', f'{ACCOUNT_URL}/email',
'POST', data={'email': value}, 'POST', data={'email': value},
sendtoken=True, sendtoken=True,
) )
@ -165,7 +166,7 @@ class AternosAccount:
""" """
self.atconn.request_cloudflare( self.atconn.request_cloudflare(
f'{AJAX_URL}/account/password.php', f'{ACCOUNT_URL}/password',
'POST', data={ 'POST', data={
'oldpassword': old, 'oldpassword': old,
'newpassword': new, 'newpassword': new,
@ -178,7 +179,7 @@ class AternosAccount:
a QR code for enabling 2FA""" a QR code for enabling 2FA"""
return self.atconn.request_cloudflare( return self.atconn.request_cloudflare(
f'{AJAX_URL}/account/secret.php', f'{ACCOUNT_URL}/secret',
'GET', sendtoken=True, 'GET', sendtoken=True,
).json() ).json()
@ -205,7 +206,7 @@ class AternosAccount:
""" """
self.atconn.request_cloudflare( self.atconn.request_cloudflare(
f'{AJAX_URL}/account/twofactor.php', f'{ACCOUNT_URL}/twofactor',
'POST', data={'code': code}, 'POST', data={'code': code},
sendtoken=True, sendtoken=True,
) )
@ -218,7 +219,7 @@ class AternosAccount:
""" """
self.atconn.request_cloudflare( self.atconn.request_cloudflare(
f'{AJAX_URL}/account/disbaleTwofactor.php', f'{ACCOUNT_URL}/disbaleTwofactor',
'POST', data={'code': code}, 'POST', data={'code': code},
sendtoken=True, sendtoken=True,
) )

View file

@ -5,7 +5,7 @@ import os
import re import re
from typing import Optional, Type from typing import Optional, Type
from .atlog import log from .atlog import log, is_debug, set_debug
from .atmd5 import md5encode from .atmd5 import md5encode
from .ataccount import AternosAccount from .ataccount import AternosAccount
@ -28,12 +28,11 @@ class Client:
def __init__(self) -> None: def __init__(self) -> None:
# Config # Config
self.debug = False
self.sessions_dir = '~' self.sessions_dir = '~'
self.js: Type[Interpreter] = Js2PyInterpreter self.js: Type[Interpreter] = Js2PyInterpreter
# ### # ###
self.saved_session = '' self.saved_session = '~/.aternos' # will be rewritten by login()
self.atconn = AternosConnect() self.atconn = AternosConnect()
self.account = AternosAccount(self) self.account = AternosAccount(self)
@ -101,7 +100,7 @@ class Client:
credentials['code'] = str(code) credentials['code'] = str(code)
loginreq = self.atconn.request_cloudflare( loginreq = self.atconn.request_cloudflare(
f'{AJAX_URL}/account/login.php', f'{AJAX_URL}/account/login',
'POST', data=credentials, sendtoken=True, 'POST', data=credentials, sendtoken=True,
) )
@ -123,18 +122,18 @@ class Client:
"""Log out from the Aternos account""" """Log out from the Aternos account"""
self.atconn.request_cloudflare( self.atconn.request_cloudflare(
f'{AJAX_URL}/account/logout.php', f'{AJAX_URL}/account/logout',
'GET', sendtoken=True, 'GET', sendtoken=True,
) )
self.remove_session(self.saved_session) self.remove_session(self.saved_session)
def restore_session(self, filename: str = '~/.aternos') -> None: def restore_session(self, file: str = '~/.aternos') -> None:
"""Restores ATERNOS_SESSION cookie and, """Restores ATERNOS_SESSION cookie and,
if included, servers list, from a session file if included, servers list, from a session file
Args: Args:
filename (str, optional): Filename file (str, optional): Filename
Raises: Raises:
FileNotFoundError: If the file cannot be found FileNotFoundError: If the file cannot be found
@ -142,13 +141,13 @@ class Client:
(or the file at all) has incorrect format (or the file at all) has incorrect format
""" """
filename = os.path.expanduser(filename) file = os.path.expanduser(file)
log.debug('Restoring session from %s', filename) log.debug('Restoring session from %s', file)
if not os.path.exists(filename): if not os.path.exists(file):
raise FileNotFoundError() raise FileNotFoundError()
with open(filename, 'rt', encoding='utf-8') as f: with open(file, 'rt', encoding='utf-8') as f:
saved = f.read() \ saved = f.read() \
.strip() \ .strip() \
.replace('\r\n', '\n') \ .replace('\r\n', '\n') \
@ -164,7 +163,7 @@ class Client:
self.account.refresh_servers(saved[1:]) self.account.refresh_servers(saved[1:])
self.atconn.session.cookies['ATERNOS_SESSION'] = session self.atconn.session.cookies['ATERNOS_SESSION'] = session
self.saved_session = filename self.saved_session = file
def save_session( def save_session(
self, self,
@ -231,3 +230,11 @@ class Client:
) )
return f'{sessions_dir}/.at_{secure}' return f'{sessions_dir}/.at_{secure}'
@property
def debug(self) -> bool:
return is_debug()
@debug.setter
def debug(self, state: bool) -> None:
return set_debug(state)

View file

@ -1,6 +1,5 @@
"""Stores API session and sends requests""" """Stores API session and sends requests"""
import logging
import re import re
import time import time
@ -16,7 +15,7 @@ import requests
from cloudscraper import CloudScraper from cloudscraper import CloudScraper
from .atlog import log from .atlog import log, is_debug
from . import atjsparse from . import atjsparse
from .aterrors import TokenError from .aterrors import TokenError
@ -163,7 +162,8 @@ class AternosConnect:
headers: Optional[Dict[Any, Any]] = None, headers: Optional[Dict[Any, Any]] = None,
reqcookies: Optional[Dict[Any, Any]] = None, reqcookies: Optional[Dict[Any, Any]] = None,
sendtoken: bool = False, sendtoken: bool = False,
retry: int = 5) -> requests.Response: retries: int = 5,
timeout: int = 4) -> requests.Response:
"""Sends a request to Aternos API bypass Cloudflare """Sends a request to Aternos API bypass Cloudflare
Args: Args:
@ -177,8 +177,9 @@ class AternosConnect:
Cookies only for this request Cookies only for this request
sendtoken (bool, optional): If the ajax and SEC token sendtoken (bool, optional): If the ajax and SEC token
should be sent should be sent
retry (int, optional): How many times parser must retry retries (int, optional): How many times parser must retry
connection to API bypass Cloudflare connection to API bypass Cloudflare
timeout (int, optional): Request timeout in seconds
Raises: Raises:
CloudflareError: When the parser has exceeded retries count CloudflareError: When the parser has exceeded retries count
@ -188,7 +189,7 @@ class AternosConnect:
API response API response
""" """
if retry <= 0: if retries <= 0:
raise CloudflareError('Unable to bypass Cloudflare protection') raise CloudflareError('Unable to bypass Cloudflare protection')
try: try:
@ -217,7 +218,7 @@ class AternosConnect:
reqcookies['ATERNOS_SESSION'] = self.atcookie reqcookies['ATERNOS_SESSION'] = self.atcookie
del self.session.cookies['ATERNOS_SESSION'] del self.session.cookies['ATERNOS_SESSION']
if log.level == logging.DEBUG: if is_debug():
reqcookies_dbg = { reqcookies_dbg = {
k: str(v or '')[:3] k: str(v or '')[:3]
@ -240,18 +241,19 @@ class AternosConnect:
sendreq = partial( sendreq = partial(
self.session.post, self.session.post,
params=params, params=params,
data=data data=data,
) )
else: else:
sendreq = partial( sendreq = partial(
self.session.get, self.session.get,
params={**params, **data} params={**params, **data},
) )
req = sendreq( req = sendreq(
url, url,
headers=headers, headers=headers,
cookies=reqcookies cookies=reqcookies,
timeout=timeout,
) )
resp_type = req.headers.get('content-type', '') resp_type = req.headers.get('content-type', '')
@ -265,7 +267,7 @@ class AternosConnect:
url, method, url, method,
params, data, params, data,
headers, reqcookies, headers, reqcookies,
sendtoken, retry - 1 sendtoken, retries - 1
) )
log.debug('AternosConnect received: %s', req.text[:65]) log.debug('AternosConnect received: %s', req.text[:65])

View file

@ -84,6 +84,7 @@ class NodeInterpreter(Interpreter):
server_js = file_dir / 'data' / 'server.js' server_js = file_dir / 'data' / 'server.js'
self.url = f'http://{host}:{port}' self.url = f'http://{host}:{port}'
self.timeout = 2
# pylint: disable=consider-using-with # pylint: disable=consider-using-with
self.proc = subprocess.Popen( self.proc = subprocess.Popen(
@ -100,11 +101,11 @@ class NodeInterpreter(Interpreter):
log.debug('Received from server.js: %s', ok_msg) log.debug('Received from server.js: %s', ok_msg)
def exec_js(self, func: str) -> None: def exec_js(self, func: str) -> None:
resp = requests.post(self.url, data=func) resp = requests.post(self.url, data=func, timeout=self.timeout)
resp.raise_for_status() resp.raise_for_status()
def get_var(self, name: str) -> Any: def get_var(self, name: str) -> Any:
resp = requests.post(self.url, data=name) resp = requests.post(self.url, data=name, timeout=self.timeout)
resp.raise_for_status() resp.raise_for_status()
log.debug('NodeJS response: %s', resp.content) log.debug('NodeJS response: %s', resp.content)
return json.loads(resp.content) return json.loads(resp.content)

View file

@ -1,4 +1,31 @@
"""Creates a logger""" """Creates a logger"""
import logging import logging
log = logging.getLogger('aternos') log = logging.getLogger('aternos')
handler = logging.StreamHandler()
fmt = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
handler.setFormatter(fmt)
log.addHandler(handler)
def is_debug() -> bool:
"""Is debug logging enabled"""
return log.level == logging.DEBUG
def set_debug(state: bool) -> None:
"""Enable debug logging"""
if state:
set_level(logging.DEBUG)
else:
set_level(logging.WARNING)
def set_level(level: int) -> None:
log.setLevel(level)
handler.setLevel(level)

View file

@ -4,11 +4,8 @@ import re
import json import json
import enum import enum
from typing import List
from typing import Optional from functools import partial
from typing import List, Dict, Any
import requests
from .atconnect import BASE_URL, AJAX_URL from .atconnect import BASE_URL, AJAX_URL
from .atconnect import AternosConnect from .atconnect import AternosConnect
@ -24,6 +21,7 @@ from .aterrors import AternosError
from .aterrors import ServerStartError from .aterrors import ServerStartError
SERVER_URL = f'{AJAX_URL}/server'
status_re = re.compile( status_re = re.compile(
r'<script>\s*var lastStatus\s*?=\s*?(\{.+?\});?\s*<\/script>' r'<script>\s*var lastStatus\s*?=\s*?(\{.+?\});?\s*<\/script>'
) )
@ -58,27 +56,35 @@ class Status(enum.IntEnum):
class AternosServer: class AternosServer:
"""Class for controlling your Aternos Minecraft server""" """Class for controlling your Aternos Minecraft server"""
def __init__( def __init__(
self, servid: str, self, servid: str,
atconn: AternosConnect, atconn: AternosConnect,
reqinfo: bool = False) -> None: autofetch: bool = False) -> None:
"""Class for controlling your Aternos Minecraft server """Class for controlling your Aternos Minecraft server
Args: Args:
servid (str): Unique server IDentifier servid (str): Unique server IDentifier
atconn (AternosConnect): atconn (AternosConnect):
AternosConnect instance with initialized Aternos session AternosConnect instance with initialized Aternos session
reqinfo (bool, optional): Automatically call autofetch (bool, optional): Automatically call
`fetch()` to get all info `fetch()` to get all info
""" """
self.servid = servid self.servid = servid
self.atconn = atconn self.atconn = atconn
if reqinfo: self._info = {}
self.atserver_request = partial(
self.atconn.request_cloudflare,
reqcookies={
'ATERNOS_SERVER': self.servid,
}
)
if autofetch:
self.fetch() self.fetch()
def fetch(self) -> None: def fetch(self) -> None:
@ -113,6 +119,7 @@ class AternosServer:
def start( def start(
self, self,
headstart: bool = False, headstart: bool = False,
access_credits: bool = False,
accepteula: bool = True) -> None: accepteula: bool = True) -> None:
"""Starts a server """Starts a server
@ -120,6 +127,9 @@ class AternosServer:
headstart (bool, optional): Start a server in headstart (bool, optional): Start a server in
the headstart mode which allows the headstart mode which allows
you to skip all queue you to skip all queue
access_credits (bool, optional):
Some new parameter in Aternos API,
I don't know what it is
accepteula (bool, optional): accepteula (bool, optional):
Automatically accept the Mojang EULA Automatically accept the Mojang EULA
@ -129,9 +139,12 @@ class AternosServer:
""" """
startreq = self.atserver_request( startreq = self.atserver_request(
f'{AJAX_URL}/start.php', f'{SERVER_URL}/start',
'GET', params={'headstart': int(headstart)}, 'GET', params={
sendtoken=True 'headstart': int(headstart),
'access-credits': int(access_credits),
},
sendtoken=True,
) )
startresult = startreq.json() startresult = startreq.json()
@ -151,40 +164,40 @@ class AternosServer:
"""Confirms server launching""" """Confirms server launching"""
self.atserver_request( self.atserver_request(
f'{AJAX_URL}/confirm.php', f'{SERVER_URL}/confirm',
'GET', sendtoken=True 'GET', sendtoken=True,
) )
def stop(self) -> None: def stop(self) -> None:
"""Stops the server""" """Stops the server"""
self.atserver_request( self.atserver_request(
f'{AJAX_URL}/stop.php', f'{SERVER_URL}/stop',
'GET', sendtoken=True 'GET', sendtoken=True,
) )
def cancel(self) -> None: def cancel(self) -> None:
"""Cancels server launching""" """Cancels server launching"""
self.atserver_request( self.atserver_request(
f'{AJAX_URL}/cancel.php', f'{SERVER_URL}/cancel',
'GET', sendtoken=True 'GET', sendtoken=True,
) )
def restart(self) -> None: def restart(self) -> None:
"""Restarts the server""" """Restarts the server"""
self.atserver_request( self.atserver_request(
f'{AJAX_URL}/restart.php', f'{SERVER_URL}/restart',
'GET', sendtoken=True 'GET', sendtoken=True,
) )
def eula(self) -> None: def eula(self) -> None:
"""Accepts the Mojang EULA""" """Sends a request to accept the Mojang EULA"""
self.atserver_request( self.atserver_request(
f'{AJAX_URL}/eula.php', f'{SERVER_URL}/accept-eula',
'GET', sendtoken=True 'GET', sendtoken=True,
) )
def files(self) -> FileManager: def files(self) -> FileManager:
@ -222,43 +235,40 @@ class AternosServer:
return PlayersList(lst, self) return PlayersList(lst, self)
def atserver_request( def set_subdomain(self, value: str) -> None:
self, url: str, method: str, """Set a new subdomain for your server
params: Optional[Dict[Any, Any]] = None, (the part before `.aternos.me`)
data: Optional[Dict[Any, Any]] = None,
headers: Optional[Dict[Any, Any]] = None,
sendtoken: bool = False) -> requests.Response:
"""Sends a request to Aternos API
with server IDenitfier parameter
Args: Args:
url (str): Request URL value (str): Subdomain
method (str): Request method, must be GET or POST
params (Optional[Dict[Any, Any]], optional): URL parameters
data (Optional[Dict[Any, Any]], optional): POST request data,
if the method is GET, this dict
will be combined with params
headers (Optional[Dict[Any, Any]], optional): Custom headers
sendtoken (bool, optional): If the ajax and SEC token should be sent
Returns:
API response
""" """
return self.atconn.request_cloudflare( self.atserver_request(
url=url, method=method, f'{SERVER_URL}/options/set-subdomain',
params=params, data=data, 'GET', params={'subdomain': value},
headers=headers, sendtoken=True,
reqcookies={ )
'ATERNOS_SERVER': self.servid
}, def set_motd(self, value: str) -> None:
sendtoken=sendtoken """Set new Message of the Day
(shown below the name in the Minecraft servers list).
Formatting with "paragraph sign + code" is supported,
see https://minecraft.tools/color-code.php
Args:
value (str): MOTD
"""
self.atserver_request(
f'{SERVER_URL}/options/set-motd',
'POST', data={'motd': value},
sendtoken=True,
) )
@property @property
def subdomain(self) -> str: def subdomain(self) -> str:
"""Server subdomain """Get the server subdomain
(the part of domain before `.aternos.me`) (the part before `.aternos.me`)
Returns: Returns:
Subdomain Subdomain
@ -267,25 +277,10 @@ class AternosServer:
atdomain = self.domain atdomain = self.domain
return atdomain[:atdomain.find('.')] return atdomain[:atdomain.find('.')]
@subdomain.setter
def subdomain(self, value: str) -> None:
"""Set a new subdomain for your server
Args:
value (str): Subdomain
"""
self.atserver_request(
f'{AJAX_URL}/options/subdomain.php',
'GET', params={'subdomain': value},
sendtoken=True
)
@property @property
def motd(self) -> str: def motd(self) -> str:
"""Server message of the day """Get the server message of the day
which is shown below its name (shown below its name in Minecraft servers list)
in the Minecraft servers list
Returns: Returns:
MOTD MOTD
@ -293,20 +288,6 @@ class AternosServer:
return self._info['motd'] return self._info['motd']
@motd.setter
def motd(self, value: str) -> None:
"""Set a new message of the day
Args:
value (str): New MOTD
"""
self.atserver_request(
f'{AJAX_URL}/options/motd.php',
'POST', data={'motd': value},
sendtoken=True
)
@property @property
def address(self) -> str: def address(self) -> str:
"""Full server address """Full server address
@ -392,11 +373,11 @@ class AternosServer:
@property @property
def css_class(self) -> str: def css_class(self) -> str:
"""CSS class for """CSS class for the server status element
server status block on official web site: offline, online, loading, etc.
on official web site See https://aternos.dc09.ru/howto/server/#server-info
(offline, loading,
loading starting, queueing) In most cases you need `AternosServer.status` instead of this
Returns: Returns:
CSS class CSS class

View file

@ -4,6 +4,7 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "data",
"dependencies": { "dependencies": {
"vm2": "^3.9.13" "vm2": "^3.9.13"
} }
@ -28,9 +29,9 @@
} }
}, },
"node_modules/vm2": { "node_modules/vm2": {
"version": "3.9.13", "version": "3.9.19",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.13.tgz", "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.19.tgz",
"integrity": "sha512-0rvxpB8P8Shm4wX2EKOiMp7H2zq+HUE/UwodY0pCZXs9IffIKZq6vUti5OgkVCTakKo9e/fgO4X1fkwfjWxE3Q==", "integrity": "sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg==",
"dependencies": { "dependencies": {
"acorn": "^8.7.0", "acorn": "^8.7.0",
"acorn-walk": "^8.2.0" "acorn-walk": "^8.2.0"
@ -55,9 +56,9 @@
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA=="
}, },
"vm2": { "vm2": {
"version": "3.9.13", "version": "3.9.19",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.13.tgz", "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.19.tgz",
"integrity": "sha512-0rvxpB8P8Shm4wX2EKOiMp7H2zq+HUE/UwodY0pCZXs9IffIKZq6vUti5OgkVCTakKo9e/fgO4X1fkwfjWxE3Q==", "integrity": "sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg==",
"requires": { "requires": {
"acorn": "^8.7.0", "acorn": "^8.7.0",
"acorn-walk": "^8.2.0" "acorn-walk": "^8.2.0"

View file

@ -18,17 +18,12 @@ with mock:
) )
mock.get( mock.get(
f'{BASE_URL}/server/', f'{BASE_URL}/server',
content=files.read_html('aternos_server1'), content=files.read_html('aternos_server1'),
) )
mock.get(
f'{AJAX_URL}/status.php',
content=files.read_html('aternos_status'),
)
mock.post( mock.post(
f'{AJAX_URL}/account/login.php', f'{AJAX_URL}/account/login',
json={ json={
'success': True, 'success': True,
'error': None, 'error': None,

View file

@ -1 +0,0 @@
{"brand":"aternos","status":0,"change":1667483582,"slots":20,"problems":0,"players":0,"playerlist":[],"message":{"text":"","class":"blue"},"dynip":null,"bedrock":false,"host":"","port":18713,"headstarts":null,"ram":0,"lang":"offline","label":"Offline","class":"offline","countdown":null,"queue":null,"id":"S0m3DGvTlbv8FfIM","name":"world35v","software":"Vanilla","softwareId":"NJcwtD9vj2X7udfa","type":"vanilla","version":"1.19.3","deprecated":false,"ip":"world35v.aternos.me","displayAddress":"world35v.aternos.me","motd":"\u00a77\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438\u0433\u0440\u043e\u043a\u0430 \u00a79world35v\u00a77!","onlineMode":true,"icon":"fa-stop-circle","dns":{"type":"DEFAULT","domains":["world35v.aternos.me"],"host":null,"port":null}}

View file

@ -25,6 +25,7 @@ class TestHttp(unittest.TestCase):
at = Client() at = Client()
at.login('test', '') at.login('test', '')
srv = at.account.list_servers(cache=False)[0] srv = at.account.list_servers(cache=False)[0]
srv.fetch()
self.assertEqual( self.assertEqual(
srv.subdomain, srv.subdomain,
'world35v', 'world35v',