From 35aa2996894935f6504c9db45158c619cab8d011 Mon Sep 17 00:00:00 2001 From: loveyousomuch Date: Wed, 27 Oct 2021 10:59:42 +0300 Subject: [PATCH 1/8] ignore __pycache__ --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51e3c2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Python +__pycache__ From 74f4b2f4297b456b031b4515c62d6a8a239b1da6 Mon Sep 17 00:00:00 2001 From: loveyousomuch Date: Wed, 27 Oct 2021 11:11:02 +0300 Subject: [PATCH 2/8] ignore .swp --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 51e3c2f..cd5b93f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ # Python __pycache__ + +#Vim +*.swp From 9cc5877ee92a97b302899700e95b70cedf60d2f2 Mon Sep 17 00:00:00 2001 From: loveyousomuch Date: Wed, 27 Oct 2021 11:15:02 +0300 Subject: [PATCH 3/8] notes for devs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7378410..2af6664 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Python Aternos API An unofficial Aternos API written in Python. It uses requests, cloudscraper and lxml to parse data from [aternos.org](https://aternos.org/). +> Note vim for devs: if u have problem like this `IndentationError: unindent does not match any outer indentation level`, try out `retab`. ## Using First you need to install the module: From dc102142d17bb3d42165e8d30b598c2cde83ae4d Mon Sep 17 00:00:00 2001 From: loveyousomuch Date: Wed, 27 Oct 2021 11:18:21 +0300 Subject: [PATCH 4/8] Revert "notes for devs" This reverts commit 9cc5877ee92a97b302899700e95b70cedf60d2f2. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2af6664..7378410 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Python Aternos API An unofficial Aternos API written in Python. It uses requests, cloudscraper and lxml to parse data from [aternos.org](https://aternos.org/). -> Note vim for devs: if u have problem like this `IndentationError: unindent does not match any outer indentation level`, try out `retab`. ## Using First you need to install the module: From d00762ae9ef3e92d36e9ae443d7203eec7054576 Mon Sep 17 00:00:00 2001 From: loveyousomuch Date: Wed, 27 Oct 2021 11:21:41 +0300 Subject: [PATCH 5/8] notes for vim --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7378410..93403f0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Python Aternos API An unofficial Aternos API written in Python. It uses requests, cloudscraper and lxml to parse data from [aternos.org](https://aternos.org/). +> Note for vim: if u have problem like this `IndentationError: unindent does not match any outer indentation level`, try out `retab`. ## Using First you need to install the module: From 671a71d5cd034d7120001efeabfc1b8c23f8c63c Mon Sep 17 00:00:00 2001 From: loveyousomuch Date: Wed, 27 Oct 2021 19:42:41 +0300 Subject: [PATCH 6/8] login done, but don't detects servers --- connect_test.py | 11 ++ python_aternos/atconnect.py | 262 +++++++++++++++++++++--------------- racer_test.py | 45 +++++++ requirements.txt | 1 + 4 files changed, 211 insertions(+), 108 deletions(-) create mode 100644 connect_test.py create mode 100755 racer_test.py diff --git a/connect_test.py b/connect_test.py new file mode 100644 index 0000000..38ac8ad --- /dev/null +++ b/connect_test.py @@ -0,0 +1,11 @@ +from python_aternos import Client as AternosClient + +aternos = AternosClient('', password='') + +srvs = aternos.servers + +print(srvs) + +s = srvs[0] + +s.start() diff --git a/python_aternos/atconnect.py b/python_aternos/atconnect.py index 38cf54b..d349244 100644 --- a/python_aternos/atconnect.py +++ b/python_aternos/atconnect.py @@ -8,141 +8,187 @@ from typing import Optional, Union from . import aterrors +#TEST +from py_mini_racer import MiniRacer +import base64 + +presettings = """ +let window = {1: null, 2: null, AJAX_TOKEN: null}; +let i = 1; +function __log() { return {win_var: window["AJAX_TOKEN"], 1: window[1], 2: window[2]} }; +function atob(arg) {window[i++] = arg;}; +""" +postsettings = """__log();""" + + REQGET = 0 REQPOST = 1 REQUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Goanna/4.8 Firefox/68.0 PaleMoon/29.4.0.2' class AternosConnect: - def __init__(self) -> None: + def __init__(self) -> None: - pass + pass - def parse_token(self, response:Optional[Union[str,bytes]]=None) -> str: + def parse_token(self, response:Optional[Union[str,bytes]]=None) -> str: - if response == None: - loginpage = self.request_cloudflare( - f'https://aternos.org/go/', REQGET - ).content - pagetree = lxml.html.fromstring(loginpage) - else: - pagetree = lxml.html.fromstring(response) + if response == None: + loginpage = self.request_cloudflare( + f'https://aternos.org/go/', REQGET + ).content + pagetree = lxml.html.fromstring(loginpage) + else: + pagetree = lxml.html.fromstring(response) - try: - pagehead = pagetree.head - self.token = re.search( - r'const\s+AJAX_TOKEN\s*=\s*["\'](\w+)["\']', - pagehead.text_content() - )[1] - except (IndexError, TypeError): - raise aterrors.AternosCredentialsError( - 'Unable to parse TOKEN from the page' - ) + try: + # fetch text + pagehead = pagetree.head + text = pagehead.text_content() + #print(text) - return self.token + #search + token_js_func = text[ + text.index("const COOKIE_PREFIX = \"ATERNOS\";") + + len("const COOKIE_PREFIX = \"ATERNOS\";") : + text.index("(function(i,s,o,g,r,a,m)") + ].strip() + print(token_js_func) - def generate_sec(self) -> str: - randkey = self.generate_aternos_rand() - randval = self.generate_aternos_rand() - self.sec = f'{randkey}:{randval}' - self.session.cookies.set( - f'ATERNOS_SEC_{randkey}', randval, - domain='aternos.org' - ) + # run js + ctx = MiniRacer() + result = ctx.eval(presettings + token_js_func) + result = ctx.call('__log') + + print(result) + + if 'win_var' in result and result['win_var']: + result = result['win_var'] + elif '1' in result and ('2' in result and not result['2']): + result = base64.standard_b64decode(result['1']) + else: + result = base64.standard_b64decode(result['2']) - return self.sec - def generate_aternos_rand(self, randlen:int=16) -> str: + print(result) + self.token = result + + """ + self.token = re.search( + r'const\s+AJAX_TOKEN\s*=\s*["\'](\w+)["\']', + text + )[1] + """ + except (IndexError, TypeError): + raise aterrors.AternosCredentialsError( + 'Unable to parse TOKEN from the page' + ) - rand_arr = [] - for i in range(randlen+1): - rand_arr.append('') + return self.token - rand_alphanum = \ - self.convert_num(random.random(),36) + \ - '00000000000000000' - return (rand_alphanum[2:18].join(rand_arr)[:randlen]) + def generate_sec(self) -> str: - def convert_num(self, num:Union[int,float], base:int) -> str: + randkey = self.generate_aternos_rand() + randval = self.generate_aternos_rand() + self.sec = f'{randkey}:{randval}' + self.session.cookies.set( + f'ATERNOS_SEC_{randkey}', randval, + domain='aternos.org' + ) - result = '' - while num > 0: - result = str(num % base) + result - num //= base - return result + return self.sec - def request_cloudflare( - self, url:str, method:int, - retries:int=10, - params:Optional[dict]=None, - data:Optional[dict]=None, - headers:Optional[dict]=None, - reqcookies:Optional[dict]=None, - sendtoken:bool=False) -> Response: + def generate_aternos_rand(self, randlen:int=16) -> str: - cftitle = 'Please Wait... | Cloudflare' + rand_arr = [] + for i in range(randlen+1): + rand_arr.append('') - if sendtoken: - if params == None: - params = {} - params['SEC'] = self.sec - params['TOKEN'] = self.token + rand_alphanum = \ + self.convert_num(random.random(),36) + \ + '00000000000000000' + return (rand_alphanum[2:18].join(rand_arr)[:randlen]) - if headers == None: - headers = {} - headers['User-Agent'] = REQUA + def convert_num(self, num:Union[int,float], base:int) -> str: - try: - cookies = self.session.cookies - except AttributeError: - cookies = None + result = '' + while num > 0: + result = str(num % base) + result + num //= base + return result - self.session = CloudScraper() - if cookies != None: - self.session.cookies = cookies + def request_cloudflare( + self, url:str, method:int, + retries:int=10, + params:Optional[dict]=None, + data:Optional[dict]=None, + headers:Optional[dict]=None, + reqcookies:Optional[dict]=None, + sendtoken:bool=False) -> Response: - if method == REQPOST: - req = self.session.post( - url, - data=data, - headers=headers, - cookies=reqcookies - ) - else: - req = self.session.get( - url, - params=params, - headers=headers, - cookies=reqcookies - ) + cftitle = 'Please Wait... | Cloudflare' - countdown = retries - while cftitle in req.text \ - and (countdown > 0): + if sendtoken: + if params == None: + params = {} + params['SEC'] = self.sec + params['TOKEN'] = self.token - self.session = CloudScraper() - if cookies != None: - self.session.cookies = cookies - if reqcookies != None: - for cookiekey in reqcookies: - self.session.cookies.set(cookiekey, reqcookies[cookiekey]) + if headers == None: + headers = {} + headers['User-Agent'] = REQUA - time.sleep(1) - if method == REQPOST: - req = self.session.post( - url, - data=data, - headers=headers, - cookies=reqcookies - ) - else: - req = self.session.get( - url, - params=params, - headers=headers, - cookies=reqcookies - ) - countdown -= 1 + try: + cookies = self.session.cookies + except AttributeError: + cookies = None - return req + self.session = CloudScraper() + if cookies != None: + self.session.cookies = cookies + + if method == REQPOST: + req = self.session.post( + url, + data=data, + headers=headers, + cookies=reqcookies + ) + else: + req = self.session.get( + url, + params=params, + headers=headers, + cookies=reqcookies + ) + + countdown = retries + while cftitle in req.text \ + and (countdown > 0): + + self.session = CloudScraper() + if cookies != None: + self.session.cookies = cookies + if reqcookies != None: + for cookiekey in reqcookies: + self.session.cookies.set(cookiekey, reqcookies[cookiekey]) + + time.sleep(1) + if method == REQPOST: + req = self.session.post( + url, + data=data, + headers=headers, + cookies=reqcookies + ) + else: + req = self.session.get( + url, + params=params, + headers=headers, + cookies=reqcookies + ) + countdown -= 1 + + return req diff --git a/racer_test.py b/racer_test.py new file mode 100755 index 0000000..23b50ec --- /dev/null +++ b/racer_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +from py_mini_racer import MiniRacer +import base64 + +# Set function for manage global vars +presettings = """ +let window = {1: null, 2: null, AJAX_TOKEN: null}; +let i = 1; +function __log() { return {win_var: window["AJAX_TOKEN"], 1: window[1], 2: window[2]} }; +function atob(arg) {window[i++] = arg;}; +""" + +# Test cases +tests = [ + """(() => {window[("A" + "J" + "AX_T" + "OKE" + "N")]=("2iXh5W5u" + "EYq" + "5fWJIa" + "zQ6");})();""", + """ (() => {window[["N","TOKE","AJAX_"].reverse().join('')]=["IazQ6","fWJ","h5W5uEYq5","2iX"].reverse().join('');})();""", + """(() => {window["AJAX_TOKEN"] = atob("SGVsbG8sIHdvcmxk")})();""", + """(() => {window[atob('QUpBWF9UT0tFTg==')]=atob('MmlYaDVXNXVFWXE1ZldKSWF6UTY=');})();""", + """(() => {window["AJAX_TOKEN"] = "1234" })();""", + """(() => {window[atob('QUpBWF9UT0tFTg==')]="2iXh5W5uEYq5fWJIazQ6";})();""", +] + +# Emulate 'atob' function +#print(base64.standard_b64decode('MmlYaDVXNXVFWXE1ZldKSWF6UTY=')) + +for js in tests: + ctx = MiniRacer() + result = ctx.eval(presettings + js) + result = ctx.call('__log') + + print(result) + ''' + if 'win_var' in result and result['win_var']: + result = result['win_var'] + elif '1' in result and ('2' in result and not result['2']): + result = base64.standard_b64decode(result['1']) + else: + result = base64.standard_b64decode(result['2']) + ''' + print('Case:\n', js, '\n') + print('Result: \n', result, '\n') + print('-' * 30, '\n') + + diff --git a/requirements.txt b/requirements.txt index 9d74376..8c37f68 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ lxml==4.6.2 requests==2.25.1 cloudscraper==1.2.58 +py-mini-racer==0.6.0 From 414f63b40b47d0ed3879b0a31a6874b93eea9a0b Mon Sep 17 00:00:00 2001 From: loveyousomuch Date: Thu, 28 Oct 2021 00:06:18 +0300 Subject: [PATCH 7/8] WORK! fetch token, start and stop server --- js2py_test.py | 44 +++++++++++++++++++++++++++ python_aternos/atconnect.py | 60 +++++++++++++------------------------ racer_test.py | 45 ---------------------------- requirements.txt | 2 +- 4 files changed, 65 insertions(+), 86 deletions(-) create mode 100755 js2py_test.py delete mode 100755 racer_test.py diff --git a/js2py_test.py b/js2py_test.py new file mode 100755 index 0000000..48f2699 --- /dev/null +++ b/js2py_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +import base64 +import js2py + +# Emulate 'atob' function +#print(base64.standard_b64decode('MmlYaDVXNXVFWXE1ZldKSWF6UTY=')) + +# Test cases +tests = [ + """(() => {window[("A" + "J" + "AX_T" + "OKE" + "N")]=("2iXh5W5u" + "EYq" + "5fWJIa" + "zQ6");})();""", + """ (() => {window[["N","TOKE","AJAX_"].reverse().join('')]=["IazQ6","fWJ","h5W5uEYq5","2iX"].reverse().join('');})();""", + """(() => {window["AJAX_TOKEN"] = atob("SGVsbG8sIHdvcmxk")})();""", + """(() => {window[atob('QUpBWF9UT0tFTg==')]=atob('MmlYaDVXNXVFWXE1ZldKSWF6UTY=');})();""", + """(() => {window["AJAX_TOKEN"] = "1234" })();""", + """(() => {window[atob('QUpBWF9UT0tFTg==')]="2iXh5W5uEYq5fWJIazQ6";})();""", +] + +# Array function to ECMAScript 5.1 +def code(f): + return "(function() { " + f[f.index("{")+1 : f.index("}")] + "})();" + +# Emulation atob V8 +def atob(arg): + return base64.standard_b64decode(str(arg)).decode("utf-8") + +presettings = """ +let window = {}; +""" + +ctx = js2py.EvalJs({ 'atob': atob }) + +''' +ctx.execute(presettings + code(tests[3])) +print(ctx.window) +''' + +for f in tests: + try: + c = code(f) + ctx.execute(presettings + c) + print(ctx.window['AJAX_TOKEN']) + except Exception as e: + print(c, '\n', e) + diff --git a/python_aternos/atconnect.py b/python_aternos/atconnect.py index d349244..a05f948 100644 --- a/python_aternos/atconnect.py +++ b/python_aternos/atconnect.py @@ -8,18 +8,22 @@ from typing import Optional, Union from . import aterrors -#TEST -from py_mini_racer import MiniRacer +# TEST +import js2py import base64 -presettings = """ -let window = {1: null, 2: null, AJAX_TOKEN: null}; -let i = 1; -function __log() { return {win_var: window["AJAX_TOKEN"], 1: window[1], 2: window[2]} }; -function atob(arg) {window[i++] = arg;}; -""" -postsettings = """__log();""" +# Set obj for js +presettings = """ +let window = {}; +""" +# Convert array function to CMAScript 5 function +def toECMAScript5Function(f): + return "(function() { " + f[f.index("{")+1 : f.index("}")] + "})();" + +# Emulation of atob - https://developer.mozilla.org/en-US/docs/Web/API/atob +def atob(s): + return base64.standard_b64decode(str(s)).decode("utf-8") REQGET = 0 REQPOST = 1 @@ -45,41 +49,17 @@ class AternosConnect: # fetch text pagehead = pagetree.head text = pagehead.text_content() - #print(text) #search - token_js_func = text[ - text.index("const COOKIE_PREFIX = \"ATERNOS\";") + - len("const COOKIE_PREFIX = \"ATERNOS\";") : - text.index("(function(i,s,o,g,r,a,m)") - ].strip() - print(token_js_func) - + js_funcs = re.findall(r"\(\(\)(.*?)\)\(\);", text) + token_js_func = js_funcs[1] if len(js_funcs) > 1 else js_funcs[0] # run js - ctx = MiniRacer() - result = ctx.eval(presettings + token_js_func) - result = ctx.call('__log') - - print(result) - - if 'win_var' in result and result['win_var']: - result = result['win_var'] - elif '1' in result and ('2' in result and not result['2']): - result = base64.standard_b64decode(result['1']) - else: - result = base64.standard_b64decode(result['2']) - - - print(result) - self.token = result - - """ - self.token = re.search( - r'const\s+AJAX_TOKEN\s*=\s*["\'](\w+)["\']', - text - )[1] - """ + ctx = js2py.EvalJs({ 'atob': atob }) + jsf = toECMAScript5Function(token_js_func) + ctx.execute(presettings + jsf) + + self.token = ctx.window['AJAX_TOKEN'] except (IndexError, TypeError): raise aterrors.AternosCredentialsError( 'Unable to parse TOKEN from the page' diff --git a/racer_test.py b/racer_test.py deleted file mode 100755 index 23b50ec..0000000 --- a/racer_test.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 - -from py_mini_racer import MiniRacer -import base64 - -# Set function for manage global vars -presettings = """ -let window = {1: null, 2: null, AJAX_TOKEN: null}; -let i = 1; -function __log() { return {win_var: window["AJAX_TOKEN"], 1: window[1], 2: window[2]} }; -function atob(arg) {window[i++] = arg;}; -""" - -# Test cases -tests = [ - """(() => {window[("A" + "J" + "AX_T" + "OKE" + "N")]=("2iXh5W5u" + "EYq" + "5fWJIa" + "zQ6");})();""", - """ (() => {window[["N","TOKE","AJAX_"].reverse().join('')]=["IazQ6","fWJ","h5W5uEYq5","2iX"].reverse().join('');})();""", - """(() => {window["AJAX_TOKEN"] = atob("SGVsbG8sIHdvcmxk")})();""", - """(() => {window[atob('QUpBWF9UT0tFTg==')]=atob('MmlYaDVXNXVFWXE1ZldKSWF6UTY=');})();""", - """(() => {window["AJAX_TOKEN"] = "1234" })();""", - """(() => {window[atob('QUpBWF9UT0tFTg==')]="2iXh5W5uEYq5fWJIazQ6";})();""", -] - -# Emulate 'atob' function -#print(base64.standard_b64decode('MmlYaDVXNXVFWXE1ZldKSWF6UTY=')) - -for js in tests: - ctx = MiniRacer() - result = ctx.eval(presettings + js) - result = ctx.call('__log') - - print(result) - ''' - if 'win_var' in result and result['win_var']: - result = result['win_var'] - elif '1' in result and ('2' in result and not result['2']): - result = base64.standard_b64decode(result['1']) - else: - result = base64.standard_b64decode(result['2']) - ''' - print('Case:\n', js, '\n') - print('Result: \n', result, '\n') - print('-' * 30, '\n') - - diff --git a/requirements.txt b/requirements.txt index 8c37f68..defa9b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ lxml==4.6.2 requests==2.25.1 cloudscraper==1.2.58 -py-mini-racer==0.6.0 +Js2Py==0.71 From 9a3de1e9b4160f2f7c91f89a54dc9751f50730a3 Mon Sep 17 00:00:00 2001 From: loveyousomuch Date: Fri, 29 Oct 2021 15:36:55 +0300 Subject: [PATCH 8/8] info about contributor --- NOTICE | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NOTICE b/NOTICE index bbf5773..cd76f9d 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ -Copyright 2021 Chechkenev Andrey +Copyright 2021 Chechkenev Andrey, lusm554 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 93403f0..63bc378 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ You can find full documentation on the [Project Wiki](https://github.com/DarkCat ## License [License Notice](NOTICE): ``` -Copyright 2021 Chechkenev Andrey +Copyright 2021 Chechkenev Andrey, lusm554 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.