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

234 lines
6.3 KiB
Python
Raw Normal View History

2022-07-01 13:28:39 +03:00
"""Entry point. Authorizes on Aternos
and allows to manage your account"""
2022-06-16 14:40:10 +03:00
import os
import re
from typing import Optional, Type
2022-06-16 14:40:10 +03:00
from .atlog import log
from .atmd5 import md5encode
from .ataccount import AternosAccount
2022-06-16 14:40:10 +03:00
from .atconnect import AternosConnect
from .atconnect import AJAX_URL
2023-05-24 16:41:33 +03:00
2022-06-16 14:40:10 +03:00
from .aterrors import CredentialsError
2022-09-23 16:21:17 +03:00
from .aterrors import TwoFactorAuthError
2022-06-16 14:40:10 +03:00
from . import atjsparse
from .atjsparse import Interpreter
from .atjsparse import Js2PyInterpreter
2022-06-23 14:13:56 +03:00
2022-06-16 14:40:10 +03:00
class Client:
2022-09-23 15:53:47 +03:00
"""Aternos API Client class, object
of which contains user's auth data"""
2022-06-23 14:13:56 +03:00
def __init__(self) -> None:
# Config
self.debug = False
self.sessions_dir = '~'
self.js: Type[Interpreter] = Js2PyInterpreter
# ###
self.saved_session = ''
self.atconn = AternosConnect()
self.account = AternosAccount(self)
def login(
self,
username: str,
password: str,
code: Optional[int] = None) -> None:
"""Log in to your Aternos account
with a username and a plain password
Args:
username (str): Username
password (str): Plain-text password
code (Optional[int], optional): 2FA code
"""
self.login_hashed(
username,
md5encode(password),
code,
)
2022-06-23 14:13:56 +03:00
def login_hashed(
self,
username: str,
md5: str,
code: Optional[int] = None) -> None:
"""Log in to your Aternos account
with a username and a hashed password
Args:
username (str): Username
md5 (str): Password hashed with MD5
code (int): 2FA code
Raises:
TwoFactorAuthError: If the 2FA is enabled,
but `code` argument was not passed or is incorrect
CredentialsError: If the Aternos backend
returned empty session cookie
(usually because of incorrect credentials)
ValueError: _description_
2022-06-23 14:13:56 +03:00
"""
filename = self.session_filename(
username, self.sessions_dir
)
try:
self.restore_session(filename)
except (OSError, CredentialsError):
pass
atjsparse.get_interpreter(create=self.js)
self.atconn.parse_token()
self.atconn.generate_sec()
2022-09-29 17:55:37 +03:00
2022-06-23 14:13:56 +03:00
credentials = {
'user': username,
2022-09-23 16:21:17 +03:00
'password': md5,
2022-06-23 14:13:56 +03:00
}
2022-09-23 16:21:17 +03:00
if code is not None:
2022-09-29 17:18:15 +03:00
credentials['code'] = str(code)
2022-09-23 16:21:17 +03:00
loginreq = self.atconn.request_cloudflare(
2023-05-24 16:41:33 +03:00
f'{AJAX_URL}/account/login.php',
'POST', data=credentials, sendtoken=True,
2022-06-23 14:13:56 +03:00
)
2022-09-23 16:21:17 +03:00
if b'"show2FA":true' in loginreq.content:
raise TwoFactorAuthError('2FA code is required')
2022-06-23 14:13:56 +03:00
if 'ATERNOS_SESSION' not in loginreq.cookies:
raise CredentialsError(
'Check your username and password'
)
self.saved_session = filename
try:
self.save_session(filename)
except OSError:
pass
def logout(self) -> None:
"""Log out from the Aternos account"""
2022-06-23 14:13:56 +03:00
self.atconn.request_cloudflare(
f'{AJAX_URL}/account/logout.php',
'GET', sendtoken=True,
)
2022-06-23 14:13:56 +03:00
self.remove_session(self.saved_session)
2022-06-23 14:13:56 +03:00
def restore_session(self, filename: str = '~/.aternos') -> None:
"""Restores ATERNOS_SESSION cookie and,
if included, servers list, from a session file
2022-06-23 14:13:56 +03:00
Args:
filename (str, optional): Filename
Raises:
FileNotFoundError: If the file cannot be found
CredentialsError: If the session cookie
(or the file at all) has incorrect format
2022-06-23 14:13:56 +03:00
"""
filename = os.path.expanduser(filename)
log.debug('Restoring session from %s', filename)
if not os.path.exists(filename):
raise FileNotFoundError()
with open(filename, 'rt', encoding='utf-8') as f:
saved = f.read() \
.strip() \
.replace('\r\n', '\n') \
.split('\n')
session = saved[0].strip()
if session == '' or not session.isalnum():
raise CredentialsError(
'Session cookie is invalid or the file is empty'
)
if len(saved) > 1:
self.account.refresh_servers(saved[1:])
self.atconn.session.cookies['ATERNOS_SESSION'] = session
self.saved_session = filename
def save_session(
self,
file: str = '~/.aternos',
incl_servers: bool = True) -> None:
2022-06-23 14:13:56 +03: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 in this file
to reduce API requests count on the next restoration
(recommended)
2022-06-23 14:13:56 +03:00
"""
file = os.path.expanduser(file)
log.debug('Saving session to %s', file)
with open(file, 'wt', encoding='utf-8') as f:
2022-06-23 14:13:56 +03:00
f.write(self.atconn.atsession + '\n')
if not incl_servers:
return
for s in self.account.servers:
f.write(s.servid + '\n')
def remove_session(self, file: str = '~/.aternos') -> None:
"""Removes a file which contains
ATERNOS_SESSION cookie saved
with `save_session()`
Args:
file (str, optional): Filename
"""
file = os.path.expanduser(file)
log.debug('Removing session file: %s', file)
try:
os.remove(file)
except OSError as err:
log.warning('Unable to delete session file: %s', err)
@staticmethod
def session_filename(username: str, sessions_dir: str = '~') -> str:
"""Generates a session file name
Args:
username (str): Authenticated user
sessions_dir (str, optional): Path to directory
with automatically saved sessions
2022-06-16 14:40:10 +03:00
Returns:
Filename
2022-09-29 18:17:38 +03:00
"""
# unsafe symbols replacement
repl = '_'
2022-09-23 16:21:17 +03:00
secure = re.sub(
r'[^A-Za-z0-9_-]',
repl, username,
2022-09-23 16:21:17 +03:00
)
return f'{sessions_dir}/.at_{secure}'