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

210 lines
5.8 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
from typing import Optional, List
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.remote.webelement import WebElement
from python_aternos.atserver import PartialServerInfo
2022-06-16 15:40:10 +04:00
2023-08-08 10:57:41 +04:00
from .atselenium import SeleniumHelper, Remote
2023-05-24 17:41:33 +04:00
2023-08-08 10:57:41 +04:00
from .atlog import log, is_debug, set_debug
2022-06-16 15:40:10 +04:00
from .aterrors import CredentialsError
2022-06-23 15:13:56 +04:00
2022-06-16 15:40:10 +04:00
class Client:
2022-09-23 16:53:47 +04:00
"""Aternos API Client class, object
of which contains user's auth data"""
2022-06-23 15:13:56 +04:00
2023-08-08 10:57:41 +04:00
def __init__(self, driver: Remote) -> None:
self.se = SeleniumHelper(driver)
# Config
self.sessions_dir = '~'
# ###
self.saved_session = '~/.aternos' # will be rewritten by login()
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
"""
2023-08-08 10:57:41 +04:00
self.se.load_page('/go')
2022-06-23 15:13:56 +04:00
err_block = self.se.find_element(By.CLASS_NAME, 'login-error')
err_alert = self.se.find_element(By.CLASS_NAME, 'alert-wrapper')
self.se.exec_js(f'''
document.getElementById('user').value = '{username}'
document.getElementById('password').value = '{password}'
document.getElementById('twofactor-code').value = '{code}'
login()
''')
2022-06-23 15:13:56 +04:00
2023-08-08 10:57:41 +04:00
def logged_in_or_error(driver: Remote):
return (
driver.current_url.find('/servers') != -1 or
err_block.is_displayed() or
err_alert.is_displayed()
)
2022-09-23 17:21:17 +04:00
2023-08-08 10:57:41 +04:00
self.se.wait.until(logged_in_or_error)
2022-06-23 15:13:56 +04:00
if self.se.driver.current_url.find('/go') != -1:
if err_block.is_displayed():
raise CredentialsError(err_block.text)
if err_alert.is_displayed():
raise CredentialsError(err_alert.text)
self.se.wait.until(lambda d: d.title.find('Cloudflare') == -1)
if not self.se.get_cookie('ATERNOS_SESSION'):
raise CredentialsError('Session cookie is empty')
print(self.se.get_cookie('ATERNOS_SESSION')) # TODO: remove, this is for debug
2023-05-29 12:02:45 +04:00
def login_with_session(self, session: str) -> None:
"""Log in using ATERNOS_SESSION cookie
Args:
session (str): Session cookie value
"""
self.se.set_cookie('ATERNOS_SESSION', session)
def logout(self) -> None:
"""Log out from the Aternos account"""
2022-06-23 15:13:56 +04:00
self.se.load_page('/servers')
self.se.find_element(By.CLASS_NAME, 'logout').click()
2022-06-23 15:13:56 +04:00
self.remove_session(self.saved_session)
def list_servers(self) -> List[PartialServerInfo]:
CARD_CLASS = 'servercard'
self.se.load_page('/servers')
def create_obj(s: WebElement) -> PartialServerInfo:
return PartialServerInfo(
id=s.get_dom_attribute('data-id'),
name=s.get_dom_attribute('title'),
software='',
status=(
s
.get_dom_attribute('class')
.replace(CARD_CLASS, '')
.split()[0]
),
players=0,
se=self.se,
)
return list(map(
create_obj,
self.se.find_elements(By.CLASS_NAME, CARD_CLASS),
))
2022-06-23 15:13:56 +04:00
def restore_session(self, file: str = '~/.aternos') -> None:
"""Restores ATERNOS_SESSION cookie from a session file
2022-06-23 15:13:56 +04:00
Args:
file (str, optional): Filename
Raises:
FileNotFoundError: If the file cannot be found
2022-06-23 15:13:56 +04:00
"""
file = os.path.expanduser(file)
log.debug('Restoring session from %s', file)
if not os.path.exists(file):
raise FileNotFoundError()
with open(file, 'rt', encoding='utf-8') as f:
session = f.readline().strip()
self.login_with_session(session)
self.saved_session = file
def save_session(self, file: str = '~/.aternos') -> None:
2022-06-23 15:13:56 +04:00
"""Saves an ATERNOS_SESSION cookie to a file
Args:
file (str, optional):
File where the session cookie must be saved
2022-06-23 15:13:56 +04:00
"""
file = os.path.expanduser(file)
log.debug('Saving session to %s', file)
with open(file, 'wt', encoding='utf-8') as f:
f.write(self.se.get_cookie('ATERNOS_SESSION') + '\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 15:40:10 +04:00
Returns:
Filename
2022-09-29 19:17:38 +04:00
"""
# unsafe symbols replacement
repl = '_'
2022-09-23 17:21:17 +04:00
secure = re.sub(
r'[^A-Za-z0-9_-]',
repl, username,
2022-09-23 17:21:17 +04:00
)
return f'{sessions_dir}/.at_{secure}'
@property
def debug(self) -> bool:
return is_debug()
@debug.setter
def debug(self, state: bool) -> None:
return set_debug(state)