diff --git a/pyproject.toml b/pyproject.toml
index 374b58c..236c211 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
-[build-system]
-requires = [
- "setuptools>=42",
- "wheel"
-]
-build-backend = "setuptools.build_meta"
+[build-system]
+requires = [
+ "setuptools>=42",
+ "wheel"
+]
+build-backend = "setuptools.build_meta"
diff --git a/python_aternos/__init__.py b/python_aternos/__init__.py
index e69de29..0b99400 100644
--- a/python_aternos/__init__.py
+++ b/python_aternos/__init__.py
@@ -0,0 +1,58 @@
+import hashlib
+import lxml.html
+
+from . import atserver
+from . import atconnect
+from . import aterrors
+
+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 = atconnect.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 aterrors.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(atserver.AternosServer(servid, self.atconn))
+
+ return servers
diff --git a/python_aternos/atconnect.py b/python_aternos/atconnect.py
index 500507e..c13515f 100644
--- a/python_aternos/atconnect.py
+++ b/python_aternos/atconnect.py
@@ -1,146 +1,147 @@
-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 = '
Please Wait... | Cloudflare'
-
- 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
+import re
+import time
+import random
+import lxml.html
+from cloudscraper import CloudScraper
+
+from . import aterrors
+
+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 aterrors.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 = 'Please Wait... | Cloudflare'
+
+ 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
diff --git a/python_aternos/aterrors.py b/python_aternos/aterrors.py
index 765e402..fc91984 100644
--- a/python_aternos/aterrors.py
+++ b/python_aternos/aterrors.py
@@ -1,11 +1,11 @@
-class AternosError(Exception):
-
- pass
-
-class AternosCredentialsError(AternosError):
-
- pass
-
-class AternosServerStartError(AternosError):
-
- pass
+class AternosError(Exception):
+
+ pass
+
+class AternosCredentialsError(AternosError):
+
+ pass
+
+class AternosServerStartError(AternosError):
+
+ pass
diff --git a/python_aternos/atfiles.py b/python_aternos/atfiles.py
new file mode 100644
index 0000000..ef29cbb
--- /dev/null
+++ b/python_aternos/atfiles.py
@@ -0,0 +1,28 @@
+from . import atconnect
+
+class AternosFileManager:
+
+ def __init__(atserv):
+
+ self.atserv = atserv
+
+ def listdir(self, path=''):
+
+ self.atserv.atserver_request(
+ f'https://aternos.org/files/{path}',
+ atconnect.AternosConnect.REQGET
+ )
+
+ def get_file(self, path):
+
+ self.atserv.atserver_request(
+ f'https://aternos.org/panel/ajax/files/download.php?file={path}',
+ atconnect.AternosConnect.REQGET
+ )
+
+ def get_world(self, world):
+
+ self.atserv.atserver_request(
+ f'https://aternos.org/panel/ajax/worlds/download.php?world={world}',
+ atconnect.AternosConnect.REQGET
+ )
diff --git a/python_aternos/atserver.py b/python_aternos/atserver.py
index 259532b..701d0a1 100644
--- a/python_aternos/atserver.py
+++ b/python_aternos/atserver.py
@@ -1,110 +1,136 @@
-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
+import lxml.html
+
+from . import aterrors
+from . import atfiles
+
+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'
+ )
+
+ fullip = servinfo[0].text
+ self._address = fullip
+ self._domain = fullip[:fullip.rfind(':')]
+ self._port = fullip[fullip.rfind(':')+1:]
+
+ 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 confirm(self):
+
+ self.atserver_request(
+ 'https://aternos.org/panel/ajax/confirm.php',
+ self.atconn.REQGET, sendtoken=True
+ )
+
+ 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 files(self):
+
+ return AternosFileManager(self)
+
+ 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 domain(self):
+ return self._domain
+
+ @property
+ def port(self):
+ return self._port
+
+ @property
+ def software(self):
+ return self._software
+
+ @property
+ def version(self):
+ return self._version
diff --git a/requirements.txt b/requirements.txt
index cb9d1db..9d74376 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
-lxml==4.6.2
-requests==2.25.1
-cloudscraper==1.2.58
+lxml==4.6.2
+requests==2.25.1
+cloudscraper==1.2.58
diff --git a/setup.py b/setup.py
index b737d77..80cc66e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,29 +1,31 @@
-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",
-)
+import setuptools
+
+with open('README.md', 'rt') as readme:
+ long_description = readme.read()
+
+setuptools.setup(
+ name='python-aternos',
+ version='0.2',
+ 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',
+ 'Documentation': 'https://github.com/DarkCat09/python-aternos/wiki/Client-(entry-point)',
+ },
+ 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'
+ ],
+ packages=['python_aternos'],
+ python_requires=">=3.6",
+)