First commit

This commit is contained in:
DarkCat09 2021-09-17 11:21:51 +04:00
parent 030b56985d
commit 4f1b069aab
10 changed files with 430 additions and 2 deletions

13
NOTICE Normal file
View file

@ -0,0 +1,13 @@
Copyright 2021 Chechkenev Andrey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,2 +1,54 @@
# python-aternos
An unofficial Aternos API written in Python
# 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/).
## Using
First you need to install the module:
```bash
pip install python-aternos
```
To use Aternos API in your Python script, import it and
login with your username and password (or MD5 hash of password).
> Note: Logging in by Google or Facebook account is not supported yet.
Then get the servers list using get_servers method.
You can start/stop your Aternos server now, calling `start()` or `stop()`.
```python
# Import
from python_aternos import Client
# Log in
aternos = Client('USERNAME', password='PASSWORD')
# or
aternos = Client('USERNAME', md5='HASHED_PASSWORD')
# get_servers returns AternosServer list
atservers = aternos.get_servers()
# If you have only one server, get it by 0 index
myserv = atservers[0]
# Start
myserv.start()
# Stop
myserv.stop()
```
You can find full documentation on the [Project Wiki](https://github.com/DarkCat09/python-aternos/wiki).
## License
[License Notice](NOTICE):
```
Copyright 2021 Chechkenev Andrey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```

6
pyproject.toml Normal file
View file

@ -0,0 +1,6 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"

View file

146
python_aternos/atconnect.py Normal file
View file

@ -0,0 +1,146 @@
import re
import time
import random
import lxml.html
from cloudscraper import CloudScraper
from aterrors import AternosCredentialsError
class AternosConnect:
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'
def __init__(self):
pass
def get_token(self, response=None):
if response == None:
loginpage = self.request_cloudflare(
f'https://aternos.org/go/',
self.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 AternosCredentialsError(
'Unable to parse TOKEN from the page'
)
return self.token
def generate_sec(self):
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'
)
return self.sec
def generate_aternos_rand(self, randlen=16):
rand_arr = []
for i in range(randlen+1):
rand_arr.append('')
rand_alphanum = \
self.convert_num(random.random(),36) + \
'00000000000000000'
return (rand_alphanum[2:18].join(rand_arr)[:randlen])
def convert_num(self, num, base):
result = ''
while num > 0:
result = str(num % base) + result
num //= base
return result
def request_cloudflare(
self, url, method, retries=10,
params=None, data=None, headers=None,
reqcookies=None, sendtoken=False):
cftitle = '<title>Please Wait... | Cloudflare</title>'
if sendtoken:
if params == None:
params = {}
params['SEC'] = self.sec
params['TOKEN'] = self.token
if headers == None:
headers = {}
headers['User-Agent'] = self.REQUA
try:
cookies = self.session.cookies
except AttributeError:
cookies = None
self.session = CloudScraper()
if cookies != None:
self.session.cookies = cookies
if method == self.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 == self.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
def get_session(self):
return self.session

View file

@ -0,0 +1,11 @@
class AternosError(Exception):
pass
class AternosCredentialsError(AternosError):
pass
class AternosServerStartError(AternosError):
pass

110
python_aternos/atserver.py Normal file
View file

@ -0,0 +1,110 @@
import lxml.html
import aterrors
class AternosServer:
def __init__(self, servid, atconn):
self.servid = servid
self.atconn = atconn
servreq = self._atserver_request(
'https://aternos.org/server',
self.atconn.REQGET
)
servtree = lxml.html.fromstring(servreq.content)
servinfo = servtree.xpath(
'//div[@class="server-bottom-info server-info"]' + \
'/div[@class="server-info-container"]' + \
'/div[@class="server-info-box"]' + \
'/div[@class="server-info-box-body"]' + \
'/div[@class="server-info-box-value"]/span'
)
self._address = servinfo[0].text
self._software = servinfo[1].text
self._version = servinfo[2].text
self.atconn.get_token(servreq.content)
self.atconn.generate_sec()
def start(self, accepteula=True):
startreq = self._atserver_request(
'https://aternos.org/panel/ajax/start.php',
self.atconn.REQGET, sendtoken=True
)
startresult = startreq.json()
if startresult['success']:
return
error = startresult['error']
if error == 'eula' and accepteula:
self.eula()
elif error == 'eula':
raise aterrors.AternosServerStartError(
'EULA was not accepted. Use start(accepteula=True)'
)
elif error == 'already':
raise aterrors.AternosServerStartError(
'Server is already running'
)
else:
raise aterrors.AternosServerStartError(
f'Unable to start server. Code: {error}'
)
def stop(self):
self._atserver_request(
'https://aternos.org/panel/ajax/stop.php',
self.atconn.REQGET, sendtoken=True
)
def cancel(self):
self._atserver_request(
'https://aternos.org/panel/ajax/cancel.php',
self.atconn.REQGET, sendtoken=True
)
def restart(self):
self._atserver_request(
'https://aternos.org/panel/ajax/restart.php',
self.atconn.REQGET, sendtoken=True
)
def eula(self):
self._atserver_request(
'https://aternos.org/panel/ajax/eula.php',
self.atconn.REQGET, sendtoken=True
)
def _atserver_request(
self, url, method, params=None,
data=None, headers=None, sendtoken=False):
return self.atconn.request_cloudflare(
url=url, method=method,
params=params, data=data,
headers=headers,
reqcookies={
'ATERNOS_SERVER': self.servid
},
sendtoken=sendtoken
)
@property
def address(self):
return self._address
@property
def software(self):
return self._software
@property
def version(self):
return self._version

58
python_aternos/client.py Normal file
View file

@ -0,0 +1,58 @@
import hashlib
import lxml.html
from atserver import AternosServer
from atconnect import AternosConnect
from aterrors import AternosCredentialsError
class Client:
def __init__(self, username, md5=None, password=None):
if (password == None) and (md5 == None):
raise AttributeError('Password was not specified')
if (password != None):
self.__init__(
username,
md5=hashlib.md5(password.encode('utf-8'))\
.hexdigest().lower()
)
return
self.atconn = AternosConnect()
self.token = self.atconn.get_token()
self.sec = self.atconn.generate_sec()
self.credentials = {
'user': username,
'password': md5
}
loginreq = self.atconn.request_cloudflare(
f'https://aternos.org/panel/ajax/account/login.php?' + \
f'SEC={self.sec}&TOKEN={self.token}',
self.atconn.REQPOST, data=self.credentials
)
if loginreq.cookies.get('ATERNOS_SESSION', None) == None:
raise AternosCredentialsError(
'Check your username and password'
)
def get_servers(self):
serverspage = self.atconn.request_cloudflare(
'https://aternos.org/servers/',
self.atconn.REQGET
)
serverstree = lxml.html.fromstring(serverspage.content)
serverslist = serverstree.xpath('//div[@class="servers"]/div')
servers = []
for server in serverslist:
servid = server.xpath('./div[@class="server-body"]/@data-id')[0]
servers.append(AternosServer(servid, self.atconn))
return servers

3
requirements.txt Normal file
View file

@ -0,0 +1,3 @@
lxml==4.6.2
requests==2.25.1
cloudscraper==1.2.58

29
setup.py Normal file
View file

@ -0,0 +1,29 @@
import setuptools
with open('README.md', 'rt') as readme:
long_description = readme.read()
setuptools.setup(
name='python-aternos',
version='0.1',
author='Chechkenev Andrey (@DarkCat09)',
author_email='aacd0709@mail.ru',
description='An unofficial Aternos API',
long_description=long_description,
long_description_content_type='text/markdown',
url='https://github.com/DarkCat09/python-aternos',
project_urls={
'Bug Tracker': 'https://github.com/DarkCat09/python-aternos/issues',
},
classifiers=[
'Programming Language :: Python :: 3',
'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent'
],
install_requires=[
'lxml==4.6.2',
'requests==2.25.1',
'cloudscraper==1.2.58'
],
python_requires=">=3.6",
)