Module docstrings, pylint, pep8

This commit is contained in:
DarkCat09 2022-07-01 14:28:39 +04:00
parent b80defe9b3
commit 074e8fd6ed
15 changed files with 603 additions and 83 deletions

212
pylintrc Normal file
View 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

View file

@ -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',

View file

@ -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
)

View file

@ -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

View file

@ -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

View file

@ -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"""

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'])

View file

@ -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
View file

@ -0,0 +1,5 @@
[mypy]
ignore_missing_imports = True
[pycodestyle]
ignore = E501

49
test.sh
View file

@ -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

View file

@ -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])