Node.js interpreter integration
This commit is contained in:
parent
ac5f306991
commit
a770df7334
3 changed files with 138 additions and 50 deletions
|
@ -28,7 +28,8 @@ from .aterrors import ServerError
|
|||
from .aterrors import ServerStartError
|
||||
from .aterrors import FileError
|
||||
from .aterrors import AternosPermissionError
|
||||
from .atjsparse import exec_js
|
||||
from .atjsparse import Js2PyInterpreter
|
||||
from .atjsparse import NodeInterpreter
|
||||
|
||||
__all__ = [
|
||||
|
||||
|
@ -42,7 +43,8 @@ __all__ = [
|
|||
'FileManager', 'AternosFile', 'AternosError',
|
||||
'CloudflareError', 'CredentialsError', 'TokenError',
|
||||
'ServerError', 'ServerStartError', 'FileError',
|
||||
'AternosPermissionError', 'exec_js',
|
||||
'AternosPermissionError',
|
||||
'Js2PyInterpreter', 'NodeInterpreter',
|
||||
|
||||
'Edition', 'Status', 'Lists',
|
||||
'ServerOpts', 'WorldOpts', 'WorldRules',
|
||||
|
|
|
@ -1,36 +1,134 @@
|
|||
"""Parsing and executing JavaScript code"""
|
||||
|
||||
import abc
|
||||
import base64
|
||||
import subprocess
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union, Any
|
||||
from typing import Type
|
||||
|
||||
import regex
|
||||
import js2py
|
||||
|
||||
# Thanks to http://regex.inginf.units.it/
|
||||
arrowexp = regex.compile(r'\w[^\}]*+')
|
||||
js: Optional['Interpreter'] = None
|
||||
|
||||
|
||||
def to_ecma5_function(f: str) -> str:
|
||||
"""Converts a ECMA6 function
|
||||
to ECMA5 format (without arrow expressions)
|
||||
class Interpreter(abc.ABC):
|
||||
"""Base JS interpreter class"""
|
||||
|
||||
Args:
|
||||
f (str): ECMA6 function
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
Returns:
|
||||
ECMA5 function
|
||||
"""
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
return self.get_var(name)
|
||||
|
||||
f = regex.sub(r'/\*.+?\*/', '', f)
|
||||
match = arrowexp.search(f)
|
||||
conv = '(function(){' + match.group(0) + '})()'
|
||||
return regex.sub(
|
||||
r'(?:s|\(s\)) => s.split\([\'"]{2}\).reverse\(\).join\([\'"]{2}\)',
|
||||
'function(s){return s.split(\'\').reverse().join(\'\')}',
|
||||
conv
|
||||
)
|
||||
@abc.abstractmethod
|
||||
def exec_js(self, func: str) -> None:
|
||||
"""Executes JavaScript code
|
||||
|
||||
Args:
|
||||
func (str): JS function
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_var(self, name: str) -> Any:
|
||||
"""Returns JS variable value
|
||||
from the interpreter
|
||||
|
||||
Args:
|
||||
name (str): Variable name
|
||||
|
||||
Returns:
|
||||
Variable value
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NodeInterpreter(Interpreter):
|
||||
|
||||
def __init__(self, node: Union[str, Path]) -> None:
|
||||
super().__init__()
|
||||
self.proc = subprocess.Popen(
|
||||
node,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
|
||||
def exec_js(self, func: str) -> None:
|
||||
self.proc.communicate(func.encode('utf-8'))
|
||||
|
||||
def get_var(self, name: str) -> Any:
|
||||
assert self.proc.stdout is not None
|
||||
self.proc.stdout.read()
|
||||
self.proc.communicate(name.encode('utf-8'))
|
||||
return self.proc.stdout.read().decode('utf-8')
|
||||
|
||||
def __del__(self) -> None:
|
||||
self.proc.terminate()
|
||||
|
||||
|
||||
class Js2PyInterpreter(Interpreter):
|
||||
|
||||
# Thanks to http://regex.inginf.units.it
|
||||
arrowexp = regex.compile(r'\w[^\}]*+')
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
super().__init__()
|
||||
|
||||
ctx = js2py.EvalJs({'atob': atob})
|
||||
ctx.execute('window.document = { };')
|
||||
ctx.execute('window.Map = function(_i){ };')
|
||||
ctx.execute('window.setTimeout = function(_f,_t){ };')
|
||||
ctx.execute('window.setInterval = function(_f,_t){ };')
|
||||
ctx.execute('window.encodeURIComponent = function(_s){ };')
|
||||
|
||||
self.ctx = ctx
|
||||
|
||||
def exec_js(self, func: str) -> None:
|
||||
self.ctx.execute(self.to_ecma5(func))
|
||||
|
||||
def get_var(self, name: str) -> Any:
|
||||
return self.ctx[name]
|
||||
|
||||
def to_ecma5(self, func: str) -> str:
|
||||
"""Converts from ECMA6 format to ECMA5
|
||||
(replacing arrow expressions)
|
||||
and removes comment blocks
|
||||
|
||||
Args:
|
||||
func (str): ECMA6 function
|
||||
|
||||
Returns:
|
||||
ECMA5 function
|
||||
"""
|
||||
|
||||
# Delete anything between /* and */
|
||||
func = regex.sub(r'/\*.+?\*/', '', func)
|
||||
|
||||
# Search for arrow expressions
|
||||
match = self.arrowexp.search(func)
|
||||
if match is None:
|
||||
return func
|
||||
|
||||
# Convert the function
|
||||
conv = '(function(){' + match[0] + '})()'
|
||||
|
||||
# Convert 1 more expression.
|
||||
# It doesn't change,
|
||||
# so it was hardcoded
|
||||
# as a regexp
|
||||
return regex.sub(
|
||||
r'(?:s|\(s\)) => s.split\([\'"]{2}\).reverse\(\).join\([\'"]{2}\)',
|
||||
'function(s){return s.split(\'\').reverse().join(\'\')}',
|
||||
conv
|
||||
)
|
||||
|
||||
|
||||
def atob(s: str) -> str:
|
||||
"""Decodes base64 string
|
||||
"""Wrapper for the built-in library function.
|
||||
Decodes base64 string
|
||||
|
||||
Args:
|
||||
s (str): Encoded data
|
||||
|
@ -42,21 +140,8 @@ def atob(s: str) -> str:
|
|||
return base64.standard_b64decode(str(s)).decode('utf-8')
|
||||
|
||||
|
||||
def exec_js(f: str) -> js2py.EvalJs:
|
||||
"""Executes a JavaScript function
|
||||
|
||||
Args:
|
||||
f (str): ECMA6 function
|
||||
|
||||
Returns:
|
||||
JavaScript interpreter context
|
||||
"""
|
||||
|
||||
ctx = js2py.EvalJs({'atob': atob})
|
||||
ctx.execute('window.document = { };')
|
||||
ctx.execute('window.Map = function(_i){ };')
|
||||
ctx.execute('window.setTimeout = function(_f,_t){ };')
|
||||
ctx.execute('window.setInterval = function(_f,_t){ };')
|
||||
ctx.execute('window.encodeURIComponent = function(_s){ };')
|
||||
ctx.execute(to_ecma5_function(f))
|
||||
return ctx
|
||||
def get_interpreter(create: Type[Interpreter] = Js2PyInterpreter) -> 'Interpreter':
|
||||
global js
|
||||
if js is None:
|
||||
js = create()
|
||||
return js
|
||||
|
|
Reference in a new issue