Account actions, session, examples and readme

This commit is contained in:
DarkCat09 2022-04-01 17:00:02 +04:00
parent 75b10e6e8c
commit 03ca4098bc
8 changed files with 155 additions and 62 deletions

View file

@ -1,42 +1,37 @@
# Python Aternos API # Python Aternos API
An unofficial Aternos API written in Python. An unofficial Aternos API written in Python.
It uses requests, cloudscraper and lxml to parse data from [aternos.org](https://aternos.org/). It uses [aternos](https://aternos.org/)' private API and html parsing.
> Note for vim: if you have a problem like `IndentationError: unindent does not match any outer indentation level`, try out `retab`.
## Using ## Installation
First you need to install the module:
```bash ```bash
pip install python-aternos pip install python-aternos
``` ```
> Note for Windows users:
Install `lxml` package from [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml) if you have a problem with it,
and then execute `pip install --no-deps python-aternos`
To use Aternos API in your Python script, import it and ## Usage
login with your username and password (or MD5 hash of password). To use Aternos API in your Python script, import it
> Note: Logging in with Google or Facebook account is not supported yet. and login with your username and password/MD5.
Then get the servers list using the `servers` field. Then request the servers list using `list_servers()`.
You can start/stop your Aternos server now, calling `start()` or `stop()`. You can start/stop your Aternos server now, calling `start()` or `stop()`.
Here is an example how to use the Aternos API: Here is an example how to use the API:
```python ```python
# Import # Import
from python_aternos import Client from python_aternos import Client
# Log in # Log in
#aternos = Client('USERNAME', password='PASSWORD') aternos = Client.from_credentials('example', 'test123')
aternos = Client('example', password='test123')
# ----OR---- # ----OR----
# password is the 1st parameter, aternos = Client.from_hashed('example', 'cc03e747a6afbbcbf8be7668acfebee5')
# so you don't have to specify its name
aternos = Client('example', 'test123')
# ----OR----
#aternos = Client('USERNAME', md5='HASHED_PASSWORD')
aternos = Client('example', md5='cc03e747a6afbbcbf8be7668acfebee5')
# Returns AternosServer list # Returns AternosServer list
atservers = aternos.servers servs = aternos.list_servers()
# If you have only one server, get it by the 0 index # Get the first server by the 0 index
myserv = atservers[0] myserv = servs[0]
# Start # Start
myserv.start() myserv.start()
@ -45,7 +40,7 @@ myserv.stop()
# You can also find server by IP # You can also find server by IP
testserv = None testserv = None
for serv in atservers: for serv in servs:
if serv.address == 'test.aternos.org': if serv.address == 'test.aternos.org':
testserv = serv testserv = serv
if testserv != None: if testserv != None:
@ -55,21 +50,11 @@ if testserv != None:
# Starts server # Starts server
testserv.start() testserv.start()
``` ```
You can find full documentation on the [Project Wiki](https://github.com/DarkCat09/python-aternos/wiki). ~~You can find full documentation on the [Project Wiki](https://github.com/DarkCat09/python-aternos/wiki).~~
## [More examples](/examples)
## Changelog ## Changelog
<!--
* v0.1 - the first release.
* v0.2 - fixed import problem.
* v0.3 - implemented files API, added typization.
* v0.4 - implemented configuration API, some bugfixes.
* v0.5 - the API was updated corresponding to new Aternos security methods.
Huge thanks to [lusm554](https://github.com/lusm554).
* v0.6 - implementation of Google Drive backups API is planned.
* v0.7 - full implementation of config API is planned.
* v0.8 - shared access API and permission management is planned.
* v0.9.x - a long debugging before stable release, SemVer version code.
-->
|Version|Description| |Version|Description|
|:-----:|:-----------| |:-----:|:-----------|
|v0.1|The first release.| |v0.1|The first release.|
@ -77,7 +62,7 @@ You can find full documentation on the [Project Wiki](https://github.com/DarkCat
|v0.3|Implemented files API, added typization.| |v0.3|Implemented files API, added typization.|
|v0.4|Implemented configuration API, some bugfixes.| |v0.4|Implemented configuration API, some bugfixes.|
|v0.5|The API was updated corresponding to new Aternos security methods. Huge thanks to [lusm554](https://github.com/lusm554).| |v0.5|The API was updated corresponding to new Aternos security methods. Huge thanks to [lusm554](https://github.com/lusm554).|
|v0.6|Preventing detecting automated access is planned.| |v0.6|Code refactoring, unit-tests, websocket API and session saving to prevent detecting automation access.|
|v0.7|Full implementation of config API and Google Drive backups is planned.| |v0.7|Full implementation of config API and Google Drive backups is planned.|
|v0.8|Shared access API and permission management is planned.| |v0.8|Shared access API and permission management is planned.|
|v0.9.x|A long debugging before stable release, SemVer version code.| |v0.9.x|A long debugging before stable release, SemVer version code.|
@ -99,4 +84,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
``` ```
You **don't** need to attribute me, if you are just using this module installed from PIP. You **don't** need to attribute me, if you are just using this module installed from PIP or wheel.

38
examples/files_example.py Normal file
View file

@ -0,0 +1,38 @@
from getpass import getpass
from python_aternos import Client, atserver
user = input('Username: ')
pswd = getpass('Password: ')
aternos = Client.from_credentials(user, pswd)
s = aternos.list_servers()[0]
files = s.files()
while True:
cmd = input('> ').strip().lower()
if cmd == 'help':
print(
'''Commands list:
help - show this message
quit - exit from the script
world - download the world
list [path] - show directory (or root) contents'''
)
if cmd == 'quit':
break
if cmd.startswith('list'):
path = cmd.removeprefix('list').strip()
directory = files.listdir(path)
print(path, 'contains:')
for file in directory:
print('\t' + file.name)
if cmd == 'world':
file = files.get_file('/world')
with open('world.zip', 'wb') as f:
f.write(file.get_content())

18
examples/info_example.py Normal file
View file

@ -0,0 +1,18 @@
from getpass import getpass
from python_aternos import Client, atserver
user = input('Username: ')
pswd = getpass('Password: ')
aternos = Client.from_credentials(user, pswd)
srvs = aternos.list_servers()
for srv in srvs:
print('*** ' + srv.domain + ' ***')
print(srv.motd)
print('*** Status:', srv.status)
print('*** Full address:', srv.address)
print('*** Port:', srv.port)
print('*** Name:', srv.subdomain)
print('*** Minecraft:', srv.software, srv.version)
print('*** IsBedrock:', srv.edition == atserver.Edition.bedrock)
print('*** IsJava:', srv.edition == atserver.Edition.java)

View file

@ -5,7 +5,7 @@ user = input('Username: ')
pswd = getpass('Password: ') pswd = getpass('Password: ')
aternos = Client.from_credentials(user, pswd) aternos = Client.from_credentials(user, pswd)
srvs = aternos.servers srvs = aternos.list_servers()
print(srvs) print(srvs)
s = srvs[0] s = srvs[0]

View file

@ -0,0 +1,15 @@
from getpass import getpass
from python_aternos import Client, atwss
user = input('Username: ')
pswd = getpass('Password: ')
aternos = Client.from_credentials(user, pswd)
s = aternos.list_servers()[0]
socket = s.wss()
@socket.wssreceiver(atwss.Streams.console)
def console(msg):
print('Received: ' + msg)
s.start()

View file

@ -1,3 +1,5 @@
import os
import re
import hashlib import hashlib
import lxml.html import lxml.html
from typing import List from typing import List
@ -28,11 +30,10 @@ class Client:
loginreq = atconn.request_cloudflare( loginreq = atconn.request_cloudflare(
f'https://aternos.org/panel/ajax/account/login.php', f'https://aternos.org/panel/ajax/account/login.php',
'POST', data=credentials, 'POST', data=credentials, sendtoken=True
sendtoken=True
) )
if loginreq.cookies.get('ATERNOS_SESSION', None) == None: if 'ATERNOS_SESSION' not in loginreq.cookies:
raise CredentialsError( raise CredentialsError(
'Check your username and password' 'Check your username and password'
) )
@ -42,9 +43,7 @@ class Client:
@classmethod @classmethod
def from_credentials(cls, username:str, password:str): def from_credentials(cls, username:str, password:str):
pswd_bytes = password.encode('utf-8') md5 = Client.md5encode(password)
md5 = hashlib.md5(pswd_bytes).hexdigest().lower()
return cls.from_hashed(username, md5) return cls.from_hashed(username, md5)
@classmethod @classmethod
@ -57,17 +56,28 @@ class Client:
return cls(atconn) return cls(atconn)
@classmethod
def restore_session(cls, file:str='~/.aternos'):
file = os.path.expanduser(file)
with open(file, 'rt') as f:
session = f.read().strip()
return cls.from_session(session)
@staticmethod @staticmethod
def google() -> str: def md5encode(passwd:str) -> str:
atconn = AternosConnect() encoded = hashlib.md5(passwd.encode('utf-8'))
auth = atconn.request_cloudflare( return encoded.hexdigest().lower()
'https://aternos.org/auth/google-login',
'GET', redirect=False def save_session(self, file:str='~/.aternos') -> None:
)
return auth.headers['Location'] file = os.path.expanduser(file)
with open(file, 'wt') as f:
f.write(self.atconn.atsession)
def list_servers(self) -> List[AternosServer]:
def list_servers(self) -> List[atserver.AternosServer]:
serverspage = self.atconn.request_cloudflare( serverspage = self.atconn.request_cloudflare(
'https://aternos.org/servers/', 'GET' 'https://aternos.org/servers/', 'GET'
) )
@ -80,3 +90,37 @@ class Client:
servers.append(AternosServer(servid, self.atconn)) servers.append(AternosServer(servid, self.atconn))
return servers return servers
def get_server(self, servid:str) -> AternosServer:
return AternosServer(servid, self.atconn)
def change_username(self, value:str) -> None:
self.atconn.request_cloudflare(
'https://aternos.org/panel/ajax/account/username.php',
'POST', data={'username': value}
)
def change_email(self, value:str) -> None:
email = re.compile(r'^[A-Za-z0-9\-_+.]+@[A-Za-z0-9\-_+.]+\.[A-Za-z0-9\-]+$|^$')
if not email.match(value):
raise ValueError('Invalid e-mail!')
self.atconn.request_cloudflare(
'https://aternos.org/panel/ajax/account/email.php',
'POST', data={'email': value}
)
def change_password(self, old:str, new:str) -> None:
old = Client.md5encode(old)
new = Client.md5encode(new)
self.atconn.request_cloudflare(
'https://aternos.org/panel/ajax/account/password.php',
'POST', data={
'oldpassword': old,
'newpassword': new
}
)

View file

@ -138,7 +138,4 @@ class AternosConnect:
f'{method} completed with {req.status_code} status' f'{method} completed with {req.status_code} status'
) )
with open('debug.html', 'wb') as f:
f.write(req.content)
return req return req

View file

@ -59,16 +59,12 @@ class AternosFile:
def get_text(self) -> str: def get_text(self) -> str:
editor = self.atserv.atserver_request( editor = self.atserv.atserver_request(
f'https://aternos.org/files/{self._full}', 'GET' f'https://aternos.org/files/{self._full.lstrip("/")}', 'GET'
) )
edittree = lxml.html.fromstring(editor.content) edittree = lxml.html.fromstring(editor.content)
editlines = edittree.xpath('//div[@class="ace_line"]') editblock = edittree.xpath('//div[@id="editor"]')[0]
rawlines = [] return editblock.text_content()
for line in editlines:
rawlines.append(line.text)
return rawlines
def set_text(self, value:str) -> None: def set_text(self, value:str) -> None: