Finally fixed 400 by updating URLs, improved logging
This commit is contained in:
parent
8ae655a34e
commit
4baf4ea1a7
17 changed files with 174 additions and 267 deletions
74
aternos.txt
74
aternos.txt
|
@ -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)
|
|
|
@ -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: ')
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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]
|
||||||
|
|
4
pylintrc
4
pylintrc
|
@ -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
|
||||||
|
|
|
@ -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',
|
|
||||||
]
|
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
13
python_aternos/data/package-lock.json
generated
13
python_aternos/data/package-lock.json
generated
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}}
|
|
|
@ -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',
|
||||||
|
|
Reference in a new issue