Module docstrings, pylint, pep8
This commit is contained in:
parent
b80defe9b3
commit
074e8fd6ed
15 changed files with 603 additions and 83 deletions
212
pylintrc
Normal file
212
pylintrc
Normal file
|
@ -0,0 +1,212 @@
|
|||
[MAIN]
|
||||
analyse-fallback-blocks=no
|
||||
extension-pkg-allow-list=
|
||||
extension-pkg-whitelist=
|
||||
fail-on=
|
||||
fail-under=10.0
|
||||
ignore=CVS
|
||||
ignore-paths=
|
||||
ignore-patterns=^\.#
|
||||
ignored-modules=
|
||||
jobs=4
|
||||
limit-inference-results=100
|
||||
load-plugins=
|
||||
persistent=yes
|
||||
py-version=3.10
|
||||
recursive=no
|
||||
suggestion-mode=yes
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
|
||||
[REPORTS]
|
||||
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
|
||||
msg-template=
|
||||
reports=no
|
||||
score=yes
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
confidence=HIGH,
|
||||
CONTROL_FLOW,
|
||||
INFERENCE,
|
||||
INFERENCE_FAILURE,
|
||||
UNDEFINED
|
||||
disable=raw-checker-failed,
|
||||
bad-inline-option,
|
||||
locally-disabled,
|
||||
file-ignored,
|
||||
suppressed-message,
|
||||
useless-suppression,
|
||||
deprecated-pragma,
|
||||
use-symbolic-message-instead,
|
||||
wrong-import-order,
|
||||
unspecified-encoding,
|
||||
logging-not-lazy,
|
||||
logging-fstring-interpolation,
|
||||
no-member,
|
||||
too-many-branches,
|
||||
too-many-arguments,
|
||||
too-many-public-methods,
|
||||
too-many-instance-attributes
|
||||
enable=c-extension-no-member
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
ignore-comments=yes
|
||||
ignore-docstrings=yes
|
||||
ignore-imports=yes
|
||||
ignore-signatures=yes
|
||||
min-similarity-lines=4
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
notes=FIXME,
|
||||
XXX,
|
||||
TODO
|
||||
notes-rgx=
|
||||
|
||||
|
||||
[DESIGN]
|
||||
exclude-too-few-public-methods=
|
||||
ignored-parents=
|
||||
max-args=5
|
||||
max-attributes=7
|
||||
max-bool-expr=5
|
||||
max-branches=12
|
||||
max-locals=15
|
||||
max-parents=7
|
||||
max-public-methods=20
|
||||
max-returns=6
|
||||
max-statements=50
|
||||
min-public-methods=2
|
||||
|
||||
|
||||
[STRING]
|
||||
check-quote-consistency=no
|
||||
check-str-concat-over-line-jumps=no
|
||||
|
||||
|
||||
[CLASSES]
|
||||
check-protected-access-in-special-methods=no
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp,
|
||||
__post_init__
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
valid-classmethod-first-arg=cls
|
||||
valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
|
||||
[FORMAT]
|
||||
expected-line-ending-format=
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
indent-after-paren=4
|
||||
indent-string=' '
|
||||
max-line-length=100
|
||||
max-module-lines=1000
|
||||
single-line-class-stmt=no
|
||||
single-line-if-stmt=no
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
allow-any-import-level=
|
||||
allow-wildcard-with-all=no
|
||||
deprecated-modules=
|
||||
ext-import-graph=
|
||||
import-graph=
|
||||
int-import-graph=
|
||||
known-standard-library=
|
||||
known-third-party=enchant
|
||||
preferred-modules=
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
additional-builtins=
|
||||
allow-global-unused-variables=yes
|
||||
allowed-redefined-builtins=
|
||||
callbacks=cb_,
|
||||
_cb
|
||||
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
||||
ignored-argument-names=_.*|^ignored_|^unused_
|
||||
init-import=no
|
||||
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
|
||||
|
||||
|
||||
[LOGGING]
|
||||
logging-format-style=new
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
overgeneral-exceptions=BaseException,
|
||||
Exception
|
||||
|
||||
|
||||
[BASIC]
|
||||
argument-naming-style=snake_case
|
||||
attr-naming-style=snake_case
|
||||
bad-names=foo,
|
||||
bar,
|
||||
baz,
|
||||
toto,
|
||||
tutu,
|
||||
tata
|
||||
bad-names-rgxs=
|
||||
class-attribute-naming-style=any
|
||||
class-const-naming-style=any
|
||||
class-naming-style=PascalCase
|
||||
const-naming-style=UPPER_CASE
|
||||
docstring-min-length=-1
|
||||
function-naming-style=snake_case
|
||||
good-names=i,
|
||||
j,
|
||||
k,
|
||||
f,
|
||||
s,
|
||||
ex,
|
||||
Run,
|
||||
_
|
||||
good-names-rgxs=
|
||||
include-naming-hint=no
|
||||
inlinevar-naming-style=any
|
||||
method-naming-style=snake_case
|
||||
module-naming-style=snake_case
|
||||
name-group=
|
||||
no-docstring-rgx=^_
|
||||
property-classes=abc.abstractproperty
|
||||
variable-naming-style=snake_case
|
||||
|
||||
|
||||
[SPELLING]
|
||||
max-spelling-suggestions=4
|
||||
spelling-dict=
|
||||
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
|
||||
spelling-ignore-words=
|
||||
spelling-private-dict-file=
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
contextmanager-decorators=contextlib.contextmanager
|
||||
generated-members=
|
||||
ignore-none=yes
|
||||
ignore-on-opaque-inference=yes
|
||||
ignored-checks-for-mixins=no-member,
|
||||
not-async-context-manager,
|
||||
not-context-manager,
|
||||
attribute-defined-outside-init
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
|
||||
missing-member-hint=yes
|
||||
missing-member-hint-distance=1
|
||||
missing-member-max-choices=1
|
||||
mixin-class-rgx=.*[Mm]ixin
|
||||
signature-mutators=
|
||||
|
||||
|
||||
[REFACTORING]
|
||||
max-nested-blocks=5
|
||||
never-returning-functions=sys.exit,argparse.parse_error
|
|
@ -1,3 +1,7 @@
|
|||
"""
|
||||
Unofficial Aternos API module written in Python.
|
||||
It uses Aternos' private API and html parsing"""
|
||||
|
||||
from .atclient import Client
|
||||
from .atserver import AternosServer
|
||||
from .atserver import Edition
|
||||
|
@ -23,8 +27,8 @@ from .aterrors import TokenError
|
|||
from .aterrors import ServerError
|
||||
from .aterrors import ServerStartError
|
||||
from .aterrors import FileError
|
||||
from .aterrors import PermissionError
|
||||
from .atjsparse import exec, atob
|
||||
from .aterrors import AternosPermissionError
|
||||
from .atjsparse import exec_js, atob
|
||||
from .atjsparse import to_ecma5_function
|
||||
|
||||
__all__ = [
|
||||
|
@ -39,8 +43,8 @@ __all__ = [
|
|||
'FileManager', 'AternosFile', 'AternosError',
|
||||
'CloudflareError', 'CredentialsError', 'TokenError',
|
||||
'ServerError', 'ServerStartError', 'FileError',
|
||||
'PermissionError',
|
||||
'exec', 'atob', 'to_ecma5_function',
|
||||
'AternosPermissionError',
|
||||
'exec_js', 'atob', 'to_ecma5_function',
|
||||
|
||||
'Edition', 'Status', 'Lists',
|
||||
'ServerOpts', 'WorldOpts', 'WorldRules',
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
"""Entry point. Authorizes on Aternos
|
||||
and allows to manage your account"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import hashlib
|
||||
|
@ -11,7 +14,7 @@ from .aterrors import CredentialsError
|
|||
|
||||
class Client:
|
||||
|
||||
"""Aternos API Client class whose object contains user's auth data
|
||||
"""Aternos API Client class object of which contains user's auth data
|
||||
|
||||
:param atconn: :class:`python_aternos.atconnect.AternosConnect`
|
||||
instance with initialized Aternos session
|
||||
|
@ -47,7 +50,7 @@ class Client:
|
|||
}
|
||||
|
||||
loginreq = atconn.request_cloudflare(
|
||||
f'https://aternos.org/panel/ajax/account/login.php',
|
||||
'https://aternos.org/panel/ajax/account/login.php',
|
||||
'POST', data=credentials, sendtoken=True
|
||||
)
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"""Modifying server and world options"""
|
||||
|
||||
import enum
|
||||
import re
|
||||
import lxml.html
|
||||
|
@ -302,10 +304,25 @@ class AternosConfig:
|
|||
|
||||
def set_world_props(
|
||||
self,
|
||||
props: Dict[Union[WorldOpts, WorldRules], Any]) -> None:
|
||||
props: Dict[Union[WorldOpts, WorldRules], Any],
|
||||
world: str = 'world') -> None:
|
||||
|
||||
"""Sets level.dat options from
|
||||
the dictionary for the specified world
|
||||
|
||||
:param props: Level.dat options
|
||||
:type props: Dict[Union[WorldOpts, WorldRules], Any]
|
||||
:param world: name of the world which
|
||||
level.dat must be edited, defaults to 'world'
|
||||
:type world: str
|
||||
"""
|
||||
|
||||
for key in props:
|
||||
self.set_world_prop(key, props[key])
|
||||
self.set_world_prop(
|
||||
option=key,
|
||||
value=props[key],
|
||||
world=world
|
||||
)
|
||||
|
||||
#
|
||||
# helpers
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"""Stores API connection session and sends requests"""
|
||||
|
||||
import re
|
||||
import random
|
||||
import logging
|
||||
|
@ -6,9 +8,13 @@ from cloudscraper import CloudScraper
|
|||
from typing import Optional, Union
|
||||
|
||||
from . import atjsparse
|
||||
from .aterrors import TokenError, CloudflareError
|
||||
from .aterrors import TokenError
|
||||
from .aterrors import CloudflareError
|
||||
from .aterrors import AternosPermissionError
|
||||
|
||||
REQUA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36 OPR/85.0.4341.47'
|
||||
REQUA = \
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' \
|
||||
'(KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36 OPR/85.0.4341.47'
|
||||
|
||||
|
||||
class AternosConnect:
|
||||
|
@ -21,6 +27,8 @@ class AternosConnect:
|
|||
|
||||
self.session = CloudScraper()
|
||||
self.atsession = ''
|
||||
self.sec = ''
|
||||
self.token = ''
|
||||
|
||||
def parse_token(self) -> str:
|
||||
|
||||
|
@ -36,7 +44,7 @@ class AternosConnect:
|
|||
"""
|
||||
|
||||
loginpage = self.request_cloudflare(
|
||||
f'https://aternos.org/go/', 'GET'
|
||||
'https://aternos.org/go/', 'GET'
|
||||
).content
|
||||
|
||||
# Using the standard string methods
|
||||
|
@ -61,13 +69,13 @@ class AternosConnect:
|
|||
js_code = re.findall(r'\(\(\)(.*?)\)\(\);', text)
|
||||
token_func = js_code[1] if len(js_code) > 1 else js_code[0]
|
||||
|
||||
ctx = atjsparse.exec(token_func)
|
||||
ctx = atjsparse.exec_js(token_func)
|
||||
self.token = ctx.window['AJAX_TOKEN']
|
||||
|
||||
except (IndexError, TypeError):
|
||||
except (IndexError, TypeError) as err:
|
||||
raise TokenError(
|
||||
'Unable to parse TOKEN from the page'
|
||||
)
|
||||
) from err
|
||||
|
||||
return self.token
|
||||
|
||||
|
@ -104,12 +112,12 @@ class AternosConnect:
|
|||
# a list with randlen+1 empty strings:
|
||||
# generate a string with spaces,
|
||||
# then split it by space
|
||||
rand_arr = (' ' * (randlen+1)).split(' ')
|
||||
rand_arr = (' ' * (randlen + 1)).split(' ')
|
||||
|
||||
rand = random.random()
|
||||
rand_alphanum = self.convert_num(rand, 36) + ('0' * 17)
|
||||
|
||||
return (rand_alphanum[:18].join(rand_arr)[:randlen])
|
||||
return rand_alphanum[:18].join(rand_arr)[:randlen]
|
||||
|
||||
def convert_num(
|
||||
self, num: Union[int, float, str],
|
||||
|
@ -214,12 +222,12 @@ class AternosConnect:
|
|||
reqcookies['ATERNOS_SESSION'] = self.atsession
|
||||
del self.session.cookies['ATERNOS_SESSION']
|
||||
|
||||
logging.debug(f'Requesting({method})' + url)
|
||||
logging.debug('headers=' + str(headers))
|
||||
logging.debug('params=' + str(params))
|
||||
logging.debug('data=' + str(data))
|
||||
logging.debug('req-cookies=' + str(reqcookies))
|
||||
logging.debug('session-cookies=' + str(self.session.cookies))
|
||||
logging.debug(f'Requesting({method}){url}')
|
||||
logging.debug(f'headers={headers}')
|
||||
logging.debug(f'params={params}')
|
||||
logging.debug(f'data={data}')
|
||||
logging.debug(f'req-cookies={reqcookies}')
|
||||
logging.debug(f'session-cookies={self.session.cookies}')
|
||||
|
||||
if method == 'POST':
|
||||
req = self.session.post(
|
||||
|
@ -249,6 +257,8 @@ class AternosConnect:
|
|||
f'{method} completed with {req.status_code} status'
|
||||
)
|
||||
|
||||
req.raise_for_status()
|
||||
if req.status_code == 402:
|
||||
raise AternosPermissionError
|
||||
|
||||
req.raise_for_status()
|
||||
return req
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"""Exceptions classes"""
|
||||
|
||||
from typing import Final
|
||||
|
||||
|
||||
|
@ -81,7 +83,9 @@ class FileError(AternosError):
|
|||
by Aternos file operation"""
|
||||
|
||||
|
||||
class PermissionError(AternosError):
|
||||
# PermissionError is a built-in,
|
||||
# so this exception called AternosPermissionError
|
||||
class AternosPermissionError(AternosError):
|
||||
|
||||
"""Raised when trying to execute a disallowed command,
|
||||
usually because of shared access rights"""
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"""File info object used by `python_aternos.atfm`"""
|
||||
|
||||
import enum
|
||||
import lxml.html
|
||||
from typing import Union
|
||||
|
@ -86,7 +88,7 @@ class AternosFile:
|
|||
"""
|
||||
|
||||
self.atserv.atserver_request(
|
||||
f'https://aternos.org/panel/ajax/save.php',
|
||||
'https://aternos.org/panel/ajax/save.php',
|
||||
'POST', data={
|
||||
'file': self._full,
|
||||
'content': value
|
||||
|
@ -123,29 +125,74 @@ class AternosFile:
|
|||
self.set_content(value.encode('utf-8'))
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
def path(self) -> str:
|
||||
|
||||
"""Path to a directory which
|
||||
contains the file, without leading slash
|
||||
|
||||
:return: Full path to directory
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
||||
"""Filename including extension
|
||||
|
||||
:return: Filename
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def full(self) -> str:
|
||||
|
||||
"""Absolute path to the file,
|
||||
without leading slash
|
||||
|
||||
:return: Full path
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._full
|
||||
|
||||
@property
|
||||
def is_dir(self) -> bool:
|
||||
|
||||
"""Check if the file object is a directory
|
||||
|
||||
:return: `True` if the file
|
||||
is a directory, otherwise `False`
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
if self._ftype == FileType.directory:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_file(self) -> bool:
|
||||
|
||||
"""Check if the file object is not a directory
|
||||
|
||||
:return: `True` if it is a file, otherwise `False`
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
if self._ftype == FileType.file:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def size(self) -> float:
|
||||
|
||||
"""File size in bytes
|
||||
|
||||
:return: File size
|
||||
:rtype: float
|
||||
"""
|
||||
|
||||
return self._size
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Exploring files in your server directory"""
|
||||
|
||||
import lxml.html
|
||||
from typing import Union, Optional, List
|
||||
from typing import Union, Optional, Any, List
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .atfile import AternosFile, FileType
|
||||
|
@ -32,10 +34,12 @@ class FileManager:
|
|||
"""
|
||||
|
||||
path = path.lstrip('/')
|
||||
|
||||
filesreq = self.atserv.atserver_request(
|
||||
f'https://aternos.org/files/{path}', 'GET'
|
||||
)
|
||||
filestree = lxml.html.fromstring(filesreq.content)
|
||||
|
||||
fileslist = filestree.xpath(
|
||||
'//div[contains(concat(" ",normalize-space(@class)," ")," file ")]'
|
||||
)
|
||||
|
@ -48,18 +52,9 @@ class FileManager:
|
|||
if ftype_raw == 'file' \
|
||||
else FileType.directory
|
||||
|
||||
fsize_raw = f.xpath('./div[@class="filesize"]')
|
||||
fsize = 0.0
|
||||
if len(fsize_raw) > 0:
|
||||
|
||||
fsize_text = fsize_raw[0].text.strip()
|
||||
fsize_num = fsize_text[:fsize_text.rfind(' ')]
|
||||
fsize_msr = fsize_text[fsize_text.rfind(' ')+1:]
|
||||
|
||||
try:
|
||||
fsize = self.convert_size(float(fsize_num), fsize_msr)
|
||||
except ValueError:
|
||||
fsize = -1
|
||||
fsize = self.extract_size(
|
||||
f.xpath('./div[@class="filesize"]')
|
||||
)
|
||||
|
||||
fullpath = f.xpath('@data-path')[0]
|
||||
filepath = fullpath[:fullpath.rfind('/')]
|
||||
|
@ -74,7 +69,33 @@ class FileManager:
|
|||
|
||||
return files
|
||||
|
||||
def convert_size(self, num: Union[int, float], measure: str) -> float:
|
||||
def extract_size(self, fsize_raw: List[Any]) -> float:
|
||||
|
||||
"""Parses file size from the LXML tree
|
||||
|
||||
:param fsize_raw: XPath method result
|
||||
:type fsize_raw: List[Any]
|
||||
:return: File size in bytes
|
||||
:rtype: float
|
||||
"""
|
||||
|
||||
if len(fsize_raw) > 0:
|
||||
|
||||
fsize_text = fsize_raw[0].text.strip()
|
||||
fsize_num = fsize_text[:fsize_text.rfind(' ')]
|
||||
fsize_msr = fsize_text[fsize_text.rfind(' ') + 1:]
|
||||
|
||||
try:
|
||||
return self.convert_size(float(fsize_num), fsize_msr)
|
||||
except ValueError:
|
||||
return -1.0
|
||||
|
||||
return 0.0
|
||||
|
||||
def convert_size(
|
||||
self,
|
||||
num: Union[int, float],
|
||||
measure: str) -> float:
|
||||
|
||||
"""Converts "human" file size to size in bytes
|
||||
|
||||
|
@ -92,10 +113,7 @@ class FileManager:
|
|||
'MB': 1000000,
|
||||
'GB': 1000000000
|
||||
}
|
||||
try:
|
||||
return num * measure_match[measure]
|
||||
except KeyError:
|
||||
return -1
|
||||
return measure_match.get(measure, -1) * num
|
||||
|
||||
def get_file(self, path: str) -> Optional[AternosFile]:
|
||||
|
||||
|
@ -128,7 +146,7 @@ class FileManager:
|
|||
:rtype: bytes
|
||||
"""
|
||||
|
||||
file = self.atserv.atserver_request(
|
||||
file = self.atserv.atserver_request( # type: ignore
|
||||
'https://aternos.org/panel/ajax/files/download.php'
|
||||
'GET', params={
|
||||
'file': path.replace('/', '%2F')
|
||||
|
@ -148,11 +166,11 @@ class FileManager:
|
|||
:rtype: bytes
|
||||
"""
|
||||
|
||||
world = self.atserv.atserver_request(
|
||||
resp = self.atserv.atserver_request( # type: ignore
|
||||
'https://aternos.org/panel/ajax/worlds/download.php'
|
||||
'GET', params={
|
||||
'world': world.replace('/', '%2F')
|
||||
}
|
||||
)
|
||||
|
||||
return world.content
|
||||
return resp.content
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"""Parsing and executing JavaScript code"""
|
||||
|
||||
import regex
|
||||
import base64
|
||||
import js2py
|
||||
|
@ -27,10 +29,19 @@ def to_ecma5_function(f: str) -> str:
|
|||
|
||||
|
||||
def atob(s: str) -> str:
|
||||
|
||||
"""Decodes base64 string
|
||||
|
||||
:param s: Encoded data
|
||||
:type s: str
|
||||
:return: Decoded string
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return base64.standard_b64decode(str(s)).decode('utf-8')
|
||||
|
||||
|
||||
def exec(f: str) -> Any:
|
||||
def exec_js(f: str) -> Any:
|
||||
|
||||
"""Executes a JavaScript function
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
"""Operators, whitelist and banned players lists"""
|
||||
|
||||
import enum
|
||||
import lxml.html
|
||||
from typing import List, Union
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .atserver import Edition
|
||||
if TYPE_CHECKING:
|
||||
from .atserver import AternosServer
|
||||
|
||||
|
@ -31,13 +32,18 @@ class PlayersList:
|
|||
:type atserv: python_aternos.atserver.AternosServer
|
||||
"""
|
||||
|
||||
def __init__(self, lst: Union[str, Lists], atserv: 'AternosServer') -> None:
|
||||
def __init__(
|
||||
self,
|
||||
lst: Union[str, Lists],
|
||||
atserv: 'AternosServer') -> None:
|
||||
|
||||
self.atserv = atserv
|
||||
self.lst = Lists(lst)
|
||||
|
||||
common_whl = (self.lst == Lists.whl)
|
||||
bedrock = (atserv.edition == Edition.bedrock)
|
||||
# 1 is atserver.Edition.bedrock
|
||||
bedrock = (atserv.edition == 1)
|
||||
|
||||
if common_whl and bedrock:
|
||||
self.lst = Lists.whl_be
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"""Aternos Minecraft server"""
|
||||
|
||||
import enum
|
||||
import json
|
||||
from requests import Response
|
||||
|
@ -85,7 +87,10 @@ class AternosServer:
|
|||
|
||||
return AternosWss(self, autoconfirm)
|
||||
|
||||
def start(self, headstart: bool = False, accepteula: bool = True) -> None:
|
||||
def start(
|
||||
self,
|
||||
headstart: bool = False,
|
||||
accepteula: bool = True) -> None:
|
||||
|
||||
"""Starts a server
|
||||
|
||||
|
@ -113,7 +118,8 @@ class AternosServer:
|
|||
|
||||
if error == 'eula' and accepteula:
|
||||
self.eula()
|
||||
return self.start(accepteula=False)
|
||||
self.start(accepteula=False)
|
||||
return
|
||||
|
||||
raise ServerStartError(error)
|
||||
|
||||
|
@ -238,11 +244,25 @@ class AternosServer:
|
|||
|
||||
@property
|
||||
def subdomain(self) -> str:
|
||||
|
||||
"""Server subdomain (part of domain before `.aternos.me`)
|
||||
|
||||
:return: Subdomain
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
atdomain = self.domain
|
||||
return atdomain[:atdomain.find('.')]
|
||||
|
||||
@subdomain.setter
|
||||
def subdomain(self, value: str) -> None:
|
||||
|
||||
"""Set new subdomain for your server
|
||||
|
||||
:param value: Subdomain
|
||||
:type value: str
|
||||
"""
|
||||
|
||||
self.atserver_request(
|
||||
'https://aternos.org/panel/ajax/options/subdomain.php',
|
||||
'GET', params={'subdomain': value},
|
||||
|
@ -251,10 +271,25 @@ class AternosServer:
|
|||
|
||||
@property
|
||||
def motd(self) -> str:
|
||||
|
||||
"""Server message of the day,
|
||||
which is shown below its name in the servers list
|
||||
|
||||
:return: MOTD
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._info['motd']
|
||||
|
||||
@motd.setter
|
||||
def motd(self, value: str) -> None:
|
||||
|
||||
"""Set new message of the day
|
||||
|
||||
:param value: MOTD
|
||||
:type value: str
|
||||
"""
|
||||
|
||||
self.atserver_request(
|
||||
'https://aternos.org/panel/ajax/options/motd.php',
|
||||
'POST', data={'motd': value},
|
||||
|
@ -263,49 +298,135 @@ class AternosServer:
|
|||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
|
||||
"""Full server address including domain and port
|
||||
|
||||
:return: Server address
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._info['displayAddress']
|
||||
|
||||
@property
|
||||
def domain(self) -> str:
|
||||
|
||||
"""Server domain (test.aternos.me),
|
||||
address without port number
|
||||
|
||||
:return: Domain
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._info['ip']
|
||||
|
||||
@property
|
||||
def port(self) -> int:
|
||||
|
||||
"""Server port number
|
||||
|
||||
:return: Port
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
return self._info['port']
|
||||
|
||||
@property
|
||||
def edition(self) -> int:
|
||||
def edition(self) -> Edition:
|
||||
|
||||
"""Server software edition: Java or Bedrock
|
||||
|
||||
:return: Software edition
|
||||
:rtype: Edition
|
||||
"""
|
||||
|
||||
soft_type = self._info['bedrock']
|
||||
return int(soft_type)
|
||||
return Edition(soft_type)
|
||||
|
||||
@property
|
||||
def software(self) -> str:
|
||||
|
||||
"""Server software name (e.g. `Vanilla`)
|
||||
|
||||
:return: Software name
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._info['software']
|
||||
|
||||
@property
|
||||
def version(self) -> str:
|
||||
|
||||
"""Server software version (e.g. `1.16.5`)
|
||||
|
||||
:return: Software version
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._info['version']
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
|
||||
"""Server status string (offline, loading)
|
||||
|
||||
:return: Status string
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._info['class']
|
||||
|
||||
@property
|
||||
def status_num(self) -> int:
|
||||
return int(self._info['status'])
|
||||
|
||||
"""Server numeric status. It is highly recommended
|
||||
to use status string instead of a number.
|
||||
|
||||
:return: Status code
|
||||
:rtype: Status
|
||||
"""
|
||||
|
||||
return Status(self._info['status'])
|
||||
|
||||
@property
|
||||
def players_list(self) -> List[str]:
|
||||
|
||||
"""List of connected players nicknames
|
||||
|
||||
:return: Connected players
|
||||
:rtype: List[str]
|
||||
"""
|
||||
|
||||
return self._info['playerlist']
|
||||
|
||||
@property
|
||||
def players_count(self) -> int:
|
||||
|
||||
"""How many connected players
|
||||
|
||||
:return: Connected players count
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
return int(self._info['players'])
|
||||
|
||||
@property
|
||||
def slots(self) -> int:
|
||||
|
||||
"""Server slots, how many players can connect
|
||||
|
||||
:return: Slots count
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
return int(self._info['slots'])
|
||||
|
||||
@property
|
||||
def ram(self) -> int:
|
||||
|
||||
"""Server used RAM in MB
|
||||
|
||||
:return: Used RAM
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
return int(self._info['ram'])
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
"""Connects to Aternos API websocket
|
||||
for real-time information"""
|
||||
|
||||
import enum
|
||||
import json
|
||||
import asyncio
|
||||
import logging
|
||||
import websockets
|
||||
from typing import Union, Any, Dict, Callable, Coroutine, Tuple
|
||||
from typing import Union, Any
|
||||
from typing import Dict, Tuple
|
||||
from typing import Callable, Coroutine
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .atconnect import REQUA
|
||||
if TYPE_CHECKING:
|
||||
from .atserver import AternosServer
|
||||
|
||||
FunctionT = Callable[[Any], Coroutine[Any, Any, None]]
|
||||
OneArgT = Callable[[Any], Coroutine[Any, Any, None]]
|
||||
TwoArgT = Callable[[Any, Tuple[Any, ...]], Coroutine[Any, Any, None]]
|
||||
FunctionT = Union[OneArgT, TwoArgT]
|
||||
ArgsTuple = Tuple[FunctionT, Tuple[Any, ...]]
|
||||
|
||||
|
||||
class Streams(enum.Enum):
|
||||
|
@ -22,6 +30,7 @@ class Streams(enum.Enum):
|
|||
console = (2, 'console')
|
||||
ram = (3, 'heap')
|
||||
tps = (4, 'tick')
|
||||
none = (-1, None)
|
||||
|
||||
def __init__(self, num: int, stream: str) -> None:
|
||||
self.num = num
|
||||
|
@ -40,24 +49,35 @@ class AternosWss:
|
|||
:type autoconfirm: bool, optional
|
||||
"""
|
||||
|
||||
def __init__(self, atserv: 'AternosServer', autoconfirm: bool = False) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
atserv: 'AternosServer',
|
||||
autoconfirm: bool = False) -> None:
|
||||
|
||||
self.atserv = atserv
|
||||
self.cookies = atserv.atconn.session.cookies
|
||||
self.session = self.cookies['ATERNOS_SESSION']
|
||||
self.servid = atserv.servid
|
||||
recvtype = Dict[Streams, Tuple[FunctionT, Tuple[Any]]]
|
||||
|
||||
recvtype = Dict[Streams, ArgsTuple]
|
||||
self.recv: recvtype = {}
|
||||
self.autoconfirm = autoconfirm
|
||||
self.confirmed = False
|
||||
|
||||
self.socket: Any
|
||||
self.keep: asyncio.Task
|
||||
self.msgs: asyncio.Task
|
||||
|
||||
async def confirm(self) -> None:
|
||||
|
||||
"""Simple way to call AternosServer.confirm from this class"""
|
||||
|
||||
self.atserv.confirm()
|
||||
|
||||
def wssreceiver(self, stream: Streams, *args: Any) -> Callable[[FunctionT], Any]:
|
||||
def wssreceiver(
|
||||
self,
|
||||
stream: Streams,
|
||||
*args: Any) -> Callable[[FunctionT], Any]:
|
||||
|
||||
"""Decorator that marks your function as a stream receiver.
|
||||
When websocket receives message from the specified stream,
|
||||
|
@ -88,7 +108,7 @@ class AternosWss:
|
|||
f'ATERNOS_SERVER={self.servid}'
|
||||
)
|
||||
]
|
||||
self.socket = await websockets.connect(
|
||||
self.socket = await websockets.connect( # type: ignore
|
||||
'wss://aternos.org/hermes/',
|
||||
origin='https://aternos.org',
|
||||
extra_headers=headers
|
||||
|
@ -192,11 +212,11 @@ class AternosWss:
|
|||
"""Receives messages from websocket servers
|
||||
and calls user's streams listeners"""
|
||||
|
||||
try:
|
||||
while True:
|
||||
while True:
|
||||
try:
|
||||
data = await self.socket.recv()
|
||||
obj = json.loads(data)
|
||||
msgtype = -1
|
||||
msgtype = Streams.none
|
||||
|
||||
if obj['type'] == 'line':
|
||||
msgtype = Streams.console
|
||||
|
@ -217,18 +237,19 @@ class AternosWss:
|
|||
|
||||
if msgtype in self.recv:
|
||||
|
||||
# function info tuple:
|
||||
# (function, arguments)
|
||||
func = self.recv[msgtype]
|
||||
# function info tuple
|
||||
func: ArgsTuple = self.recv[msgtype]
|
||||
|
||||
# if arguments is not empty
|
||||
if func[1]:
|
||||
# call the function with args
|
||||
coro = func[0](msg, func[1])
|
||||
coro = func[0](msg, func[1]) # type: ignore
|
||||
else:
|
||||
coro = func[0](msg)
|
||||
# mypy error: Too few arguments
|
||||
# looks like bug, so it is ignored
|
||||
coro = func[0](msg) # type: ignore
|
||||
# run
|
||||
await asyncio.create_task(coro)
|
||||
asyncio.create_task(coro)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
|
|
5
setup.cfg
Normal file
5
setup.cfg
Normal file
|
@ -0,0 +1,5 @@
|
|||
[mypy]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[pycodestyle]
|
||||
ignore = E501
|
49
test.sh
49
test.sh
|
@ -1,9 +1,40 @@
|
|||
failed=()
|
||||
|
||||
title () {
|
||||
|
||||
RESET='\033[0m'
|
||||
COLOR='\033[1;36m'
|
||||
|
||||
echo
|
||||
echo "***"
|
||||
echo "$1"
|
||||
echo "***"
|
||||
echo
|
||||
echo -e "$COLOR[#] $1$RESET"
|
||||
}
|
||||
|
||||
error_msg () {
|
||||
|
||||
RESET='\033[0m'
|
||||
OK='\033[1;32m'
|
||||
ERR='\033[1;31m'
|
||||
|
||||
if (( $1 )); then
|
||||
failed+=$2
|
||||
echo -e "$ERR[X] Found errors$RESET"
|
||||
else
|
||||
echo -e "$OK[V] Passed successfully$RESET"
|
||||
fi
|
||||
}
|
||||
|
||||
display_failed() {
|
||||
|
||||
RESET='\033[0m'
|
||||
FAILED='\033[1;33m'
|
||||
SUCCESS='\033[1;32m'
|
||||
|
||||
local IFS=', '
|
||||
if [[ ${#failed[@]} > 0 ]]; then
|
||||
echo -e "$FAILED[!] View output for: ${failed[*]}$RESET"
|
||||
else
|
||||
echo -e "$SUCCESS[V] All tests are passed successfully$RESET"
|
||||
fi
|
||||
}
|
||||
|
||||
title 'Checking needed modules...'
|
||||
|
@ -11,9 +42,19 @@ pip install pycodestyle mypy pylint
|
|||
|
||||
title 'Running unit tests...'
|
||||
python -m unittest discover -v ./tests
|
||||
error_msg $? 'unittest'
|
||||
|
||||
title 'Running pep8 checker...'
|
||||
python -m pycodestyle .
|
||||
error_msg $? 'pep8'
|
||||
|
||||
title 'Running mypy checker...'
|
||||
python -m mypy .
|
||||
error_msg $? 'mypy'
|
||||
|
||||
title 'Running pylint checker...'
|
||||
python -m pylint ./python_aternos
|
||||
error_msg $? 'pylint'
|
||||
|
||||
display_failed
|
||||
echo
|
||||
|
|
|
@ -5,7 +5,7 @@ import unittest
|
|||
from python_aternos import atjsparse
|
||||
|
||||
CONV_TOKEN_ARROW = '''(() => {window["AJAX_TOKEN"]=("2r" + "KO" + "A1" + "IFdBcHhEM" + "61" + "6cb");})();'''
|
||||
CONV_TOKEN_FUNC = '(function(){window["AJAX_TOKEN"]=("2r" + "KO" + "A1" + "IFdBcHhEM" + "61" + "6cb");})()'
|
||||
CONV_TOKEN_FUNC = '''(function(){window["AJAX_TOKEN"]=("2r" + "KO" + "A1" + "IFdBcHhEM" + "61" + "6cb");})()'''
|
||||
|
||||
|
||||
class TestJs2Py(unittest.TestCase):
|
||||
|
@ -53,10 +53,10 @@ class TestJs2Py(unittest.TestCase):
|
|||
part2 = '''window.t2 = Boolean(!window[["p","Ma"].reverse().join('')]);'''
|
||||
part3 = '''window.t3 = Boolean(!window[["ut","meo","i","etT","s"].reverse().join('')]);'''
|
||||
|
||||
ctx0 = atjsparse.exec(code)
|
||||
ctx1 = atjsparse.exec(part1)
|
||||
ctx2 = atjsparse.exec(part2)
|
||||
ctx3 = atjsparse.exec(part3)
|
||||
ctx0 = atjsparse.exec_js(code)
|
||||
ctx1 = atjsparse.exec_js(part1)
|
||||
ctx2 = atjsparse.exec_js(part2)
|
||||
ctx3 = atjsparse.exec_js(part3)
|
||||
|
||||
self.assertEqual(ctx0.window['t0'], False)
|
||||
self.assertEqual(ctx1.window['t1'], True)
|
||||
|
@ -66,7 +66,7 @@ class TestJs2Py(unittest.TestCase):
|
|||
def test_exec(self) -> None:
|
||||
|
||||
for i, f in enumerate(self.tests):
|
||||
ctx = atjsparse.exec(f)
|
||||
ctx = atjsparse.exec_js(f)
|
||||
res = ctx.window['AJAX_TOKEN']
|
||||
self.assertEqual(res, self.results[i])
|
||||
|
||||
|
|
Reference in a new issue