This repository has been archived on 2024-07-30. You can view files and clone it, but cannot push or open issues or pull requests.
python-aternos/python_aternos/atclient.py

378 lines
9.6 KiB
Python
Raw Normal View History

2022-07-01 14:28:39 +04:00
"""Entry point. Authorizes on Aternos
and allows to manage your account"""
2022-06-16 15:40:10 +04:00
import os
import re
import hashlib
import logging
from typing import List, Optional
2022-06-16 15:40:10 +04:00
import lxml.html
2022-06-16 15:40:10 +04:00
from .atserver import AternosServer
from .atconnect import AternosConnect
from .aterrors import CredentialsError
2022-06-23 15:13:56 +04:00
2022-06-16 15:40:10 +04:00
class Client:
"""Aternos API Client class object
of which contains user's auth data"""
2022-06-23 15:13:56 +04:00
def __init__(
self,
atconn: AternosConnect,
servers: Optional[List[str]] = None) -> None:
2022-06-23 15:13:56 +04:00
"""Aternos API Client class object
of which contains user's auth data
Args:
atconn (AternosConnect):
AternosConnect instance with initialized Aternos session
servers (Optional[List[str]], optional):
List with servers IDs
"""
2022-06-23 15:13:56 +04:00
self.atconn = atconn
self.parsed = False
self.servers: List[AternosServer] = []
if servers:
self.refresh_servers(servers)
2022-06-23 15:13:56 +04:00
@classmethod
def from_hashed(
cls,
username: str,
md5: str,
sessions_dir: str = '~'):
"""Log in to an Aternos account with
a username and a hashed password
Args:
username (str): Your username
md5 (str): Your password hashed with MD5
sessions_dir (str): Path to the directory
where session will be automatically saved
Raises:
CredentialsError: If the API didn't
return a valid session cookie
2022-06-23 15:13:56 +04:00
"""
atconn = AternosConnect()
atconn.parse_token()
atconn.generate_sec()
secure = cls.secure_name(username)
filename = f'{sessions_dir}/.at_{secure}'
try:
return cls.restore_session(filename)
except (OSError, CredentialsError):
pass
2022-06-23 15:13:56 +04:00
credentials = {
'user': username,
'password': md5
}
loginreq = atconn.request_cloudflare(
2022-07-01 14:28:39 +04:00
'https://aternos.org/panel/ajax/account/login.php',
2022-06-23 15:13:56 +04:00
'POST', data=credentials, sendtoken=True
)
if 'ATERNOS_SESSION' not in loginreq.cookies:
raise CredentialsError(
'Check your username and password'
)
obj = cls(atconn)
try:
obj.save_session(filename)
except OSError:
pass
return obj
2022-06-23 15:13:56 +04:00
@classmethod
def from_credentials(
cls,
username: str,
password: str,
sessions_dir: str = '~'):
2022-06-23 15:13:56 +04:00
"""Log in to Aternos with a username and a plain password
Args:
username (str): Your username
password (str): Your password without any encryption
sessions_dir (str): Path to the directory
where session will be automatically saved
2022-06-23 15:13:56 +04:00
"""
md5 = Client.md5encode(password)
return cls.from_hashed(
username, md5,
sessions_dir
)
2022-06-23 15:13:56 +04:00
@classmethod
def from_session(
cls,
session: str,
servers: Optional[List[str]] = None):
2022-06-23 15:13:56 +04:00
"""Log in to Aternos using a session cookie value
Args:
session (str): Value of ATERNOS_SESSION cookie
2022-06-23 15:13:56 +04:00
"""
atconn = AternosConnect()
atconn.session.cookies['ATERNOS_SESSION'] = session
atconn.parse_token()
atconn.generate_sec()
return cls(atconn, servers)
2022-06-23 15:13:56 +04:00
@classmethod
def restore_session(cls, file: str = '~/.aternos'):
"""Log in to Aternos using a saved ATERNOS_SESSION cookie
Args:
file (str, optional): File where a session cookie was saved
2022-06-23 15:13:56 +04:00
"""
file = os.path.expanduser(file)
logging.debug(f'Restoring session from {file}')
if not os.path.exists(file):
raise FileNotFoundError()
with open(file, 'rt', encoding='utf-8') as f:
saved = f.read() \
.strip() \
.replace('\r\n', '\n') \
.split('\n')
session = saved[0].strip()
if session == '':
raise CredentialsError(
'Unable to read session cookie, '
'the first line is empty'
)
if len(saved) > 1:
return cls.from_session(
session=session,
servers=saved[1:]
)
2022-06-23 15:13:56 +04:00
return cls.from_session(session)
@staticmethod
def md5encode(passwd: str) -> str:
"""Encodes the given string with MD5
Args:
passwd (str): String to encode
Returns:
Hexdigest hash of the string in lowercase
2022-06-23 15:13:56 +04:00
"""
encoded = hashlib.md5(passwd.encode('utf-8'))
return encoded.hexdigest().lower()
@staticmethod
def secure_name(filename: str, repl: str = '_') -> str:
"""Replaces unsecure characters
in filename to underscore or `repl`
Args:
filename (str): Filename
repl (str, optional): Replacement
for unsafe characters
Returns:
str: Secure filename
"""
return re.sub(
r'[^A-Za-z0-9_-]',
repl, filename
)
def save_session(
self,
file: str = '~/.aternos',
incl_servers: bool = True) -> None:
2022-06-23 15:13:56 +04:00
"""Saves an ATERNOS_SESSION cookie to a file
Args:
file (str, optional): File where a session cookie must be saved
incl_servers (bool, optional): If the function
should include the servers IDs to
reduce API requests count (recommended)
2022-06-23 15:13:56 +04:00
"""
file = os.path.expanduser(file)
logging.debug(f'Saving session to {file}')
with open(file, 'wt', encoding='utf-8') as f:
2022-06-23 15:13:56 +04:00
f.write(self.atconn.atsession + '\n')
if not incl_servers:
return
for s in self.servers:
f.write(s.servid + '\n')
def list_servers(self, cache: bool = True) -> List[AternosServer]:
2022-06-23 15:13:56 +04:00
"""Parses a list of your servers from Aternos website
Args:
cache (bool, optional): If the function should use
cached servers list (recommended)
Returns:
List of AternosServer objects
2022-06-23 15:13:56 +04:00
"""
if cache and self.parsed:
return self.servers
2022-06-23 15:13:56 +04:00
serverspage = self.atconn.request_cloudflare(
'https://aternos.org/servers/', 'GET'
)
serverstree = lxml.html.fromstring(serverspage.content)
servers = serverstree.xpath(
'//div[@class="server-body"]/@data-id'
2022-06-23 15:13:56 +04:00
)
self.refresh_servers(servers)
return self.servers
def refresh_servers(self, ids: List[str]) -> None:
"""Replaces cached servers list creating
AternosServer objects by given IDs
Args:
ids (List[str]): Servers unique identifiers
"""
self.servers = []
for s in ids:
servid = s.strip()
if servid == '':
continue
2022-06-23 15:13:56 +04:00
logging.debug(f'Adding server {servid}')
srv = AternosServer(servid, self.atconn)
self.servers.append(srv)
2022-06-23 15:13:56 +04:00
self.parsed = True
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
def get_server(self, servid: str) -> AternosServer:
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
"""Creates a server object from the server ID.
Use this instead of list_servers
if you know the ID to save some time.
2022-06-16 15:40:10 +04:00
Returns:
AternosServer object
2022-06-23 15:13:56 +04:00
"""
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
return AternosServer(servid, self.atconn)
def logout(self) -> None:
"""Log out from Aternos account"""
self.atconn.request_cloudflare(
'https://aternos.org/panel/ajax/account/logout.php',
'GET', sendtoken=True
)
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
def change_username(self, value: str) -> None:
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
"""Changes a username in your Aternos account
2022-06-16 15:40:10 +04:00
Args:
value (str): New username
2022-06-23 15:13:56 +04:00
"""
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
self.atconn.request_cloudflare(
'https://aternos.org/panel/ajax/account/username.php',
'POST', data={'username': value}, sendtoken=True
2022-06-23 15:13:56 +04:00
)
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
def change_email(self, value: str) -> None:
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
"""Changes an e-mail in your Aternos account
2022-06-16 15:40:10 +04:00
Args:
value (str): New e-mail
Raises:
ValueError: If an invalid e-mail address
was passed to the function
2022-06-23 15:13:56 +04:00
"""
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
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!')
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
self.atconn.request_cloudflare(
'https://aternos.org/panel/ajax/account/email.php',
'POST', data={'email': value}, sendtoken=True
2022-06-23 15:13:56 +04:00
)
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
def change_password(self, old: str, new: str) -> None:
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
"""Changes a password in your Aternos account
2022-06-16 15:40:10 +04:00
Args:
old (str): Old password
new (str): New password
"""
self.change_password_hashed(
Client.md5encode(old),
Client.md5encode(new),
)
def change_password_hashed(self, old: str, new: str) -> None:
"""Changes a password in your Aternos account.
Unlike `change_password`, this function
takes hashed passwords as arguments
Args:
old (str): Old password hashed with MD5
new (str): New password hashed with MD5
2022-06-23 15:13:56 +04:00
"""
2022-06-16 15:40:10 +04:00
2022-06-23 15:13:56 +04:00
self.atconn.request_cloudflare(
'https://aternos.org/panel/ajax/account/password.php',
'POST', data={
'oldpassword': old,
'newpassword': new
}, sendtoken=True
2022-06-23 15:13:56 +04:00
)