2023-05-24 17:41:33 +04:00
|
|
|
"""Stores API session and sends requests"""
|
2022-07-01 14:28:39 +04:00
|
|
|
|
2021-10-08 19:35:20 +04:00
|
|
|
import re
|
2022-08-22 09:55:08 +04:00
|
|
|
import time
|
2023-05-24 17:12:34 +04:00
|
|
|
|
|
|
|
import string
|
2022-10-05 19:24:00 +04:00
|
|
|
import secrets
|
2023-05-24 17:12:34 +04:00
|
|
|
|
2022-08-22 09:55:08 +04:00
|
|
|
from functools import partial
|
|
|
|
|
2022-10-05 19:24:00 +04:00
|
|
|
from typing import Optional
|
2022-10-31 17:24:00 +04:00
|
|
|
from typing import List, Dict, Any
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
|
|
|
|
import requests
|
2022-08-22 09:55:08 +04:00
|
|
|
|
2021-10-08 19:35:20 +04:00
|
|
|
from cloudscraper import CloudScraper
|
|
|
|
|
2023-05-29 11:44:19 +04:00
|
|
|
from .atlog import log, is_debug
|
2023-05-24 18:15:18 +04:00
|
|
|
|
2021-11-01 18:04:19 +04:00
|
|
|
from . import atjsparse
|
2022-07-01 14:28:39 +04:00
|
|
|
from .aterrors import TokenError
|
|
|
|
from .aterrors import CloudflareError
|
|
|
|
from .aterrors import AternosPermissionError
|
2021-10-27 19:42:41 +03:00
|
|
|
|
2023-05-24 17:41:33 +04:00
|
|
|
|
|
|
|
BASE_URL = 'https://aternos.org'
|
|
|
|
AJAX_URL = f'{BASE_URL}/ajax'
|
|
|
|
|
2022-07-01 14:28:39 +04:00
|
|
|
REQUA = \
|
|
|
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' \
|
|
|
|
'(KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36 OPR/85.0.4341.47'
|
2021-10-08 19:35:20 +04:00
|
|
|
|
2022-12-27 16:55:19 +04:00
|
|
|
ARROW_FN_REGEX = r'\(\(\).*?\)\(\);'
|
2022-10-31 17:24:00 +04:00
|
|
|
SCRIPT_TAG_REGEX = (
|
|
|
|
rb'<script type=([\'"]?)text/javascript\1>.+?</script>'
|
|
|
|
)
|
|
|
|
|
2023-05-24 17:12:34 +04:00
|
|
|
SEC_ALPHABET = string.ascii_lowercase + string.digits
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
|
2021-10-14 17:56:01 +04:00
|
|
|
class AternosConnect:
|
2022-09-29 18:18:15 +04:00
|
|
|
"""Class for sending API requests,
|
|
|
|
bypassing Cloudflare and parsing responses"""
|
2022-06-17 12:30:58 +04:00
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
def __init__(self) -> None:
|
2021-11-01 18:04:19 +04:00
|
|
|
|
2023-06-30 10:12:22 +04:00
|
|
|
self.session = CloudScraper()
|
2022-07-01 14:28:39 +04:00
|
|
|
self.sec = ''
|
|
|
|
self.token = ''
|
2022-11-03 18:01:53 +04:00
|
|
|
self.atcookie = ''
|
2021-11-01 18:04:19 +04:00
|
|
|
|
2022-10-05 19:44:00 +04:00
|
|
|
def refresh_session(self) -> None:
|
|
|
|
"""Creates a new CloudScraper
|
|
|
|
session object and copies all cookies.
|
|
|
|
Required for bypassing Cloudflare"""
|
|
|
|
|
|
|
|
old_cookies = self.session.cookies
|
2023-06-30 10:12:22 +04:00
|
|
|
captcha_kwarg = self.session.captcha
|
|
|
|
self.session = CloudScraper(captcha=captcha_kwarg)
|
2022-10-05 19:44:00 +04:00
|
|
|
self.session.cookies.update(old_cookies)
|
|
|
|
del old_cookies
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
def parse_token(self) -> str:
|
|
|
|
"""Parses Aternos ajax token that
|
|
|
|
is needed for most requests
|
2022-06-17 12:30:58 +04:00
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
Raises:
|
|
|
|
TokenError: If the parser is unable
|
|
|
|
to extract ajax token from HTML
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Aternos ajax token
|
2022-06-23 15:13:56 +04:00
|
|
|
"""
|
2022-06-17 12:30:58 +04:00
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
loginpage = self.request_cloudflare(
|
2023-05-24 17:41:33 +04:00
|
|
|
f'{BASE_URL}/go/', 'GET'
|
2022-06-23 15:13:56 +04:00
|
|
|
).content
|
2021-11-01 18:04:19 +04:00
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
# Using the standard string methods
|
|
|
|
# instead of the expensive xml parsing
|
|
|
|
head = b'<head>'
|
|
|
|
headtag = loginpage.find(head)
|
|
|
|
headend = loginpage.find(b'</head>', headtag + len(head))
|
|
|
|
|
|
|
|
# Some checks
|
|
|
|
if headtag < 0 or headend < 0:
|
|
|
|
pagehead = loginpage
|
2023-05-24 18:15:18 +04:00
|
|
|
log.warning(
|
2022-06-23 15:13:56 +04:00
|
|
|
'Unable to find <head> tag, parsing the whole page'
|
|
|
|
)
|
|
|
|
|
2022-10-31 17:24:00 +04:00
|
|
|
else:
|
|
|
|
# Extracting <head> content
|
|
|
|
headtag = headtag + len(head)
|
|
|
|
pagehead = loginpage[headtag:headend]
|
2022-06-23 15:13:56 +04:00
|
|
|
|
2022-10-31 17:24:00 +04:00
|
|
|
js_code: Optional[List[Any]] = None
|
2023-05-24 20:03:09 +04:00
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
try:
|
|
|
|
text = pagehead.decode('utf-8', 'replace')
|
2022-10-31 17:24:00 +04:00
|
|
|
js_code = re.findall(ARROW_FN_REGEX, text)
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
|
|
|
|
token_func = js_code[0]
|
|
|
|
if len(js_code) > 1:
|
|
|
|
token_func = js_code[1]
|
2022-06-23 15:13:56 +04:00
|
|
|
|
2022-12-25 17:51:29 +04:00
|
|
|
js = atjsparse.get_interpreter()
|
|
|
|
js.exec_js(token_func)
|
|
|
|
self.token = js['AJAX_TOKEN']
|
2022-06-23 15:13:56 +04:00
|
|
|
|
2022-07-01 14:28:39 +04:00
|
|
|
except (IndexError, TypeError) as err:
|
2022-10-31 17:24:00 +04:00
|
|
|
|
2023-05-24 18:15:18 +04:00
|
|
|
log.warning('---')
|
|
|
|
log.warning('Unable to parse AJAX_TOKEN!')
|
|
|
|
log.warning('Please, insert the info below')
|
|
|
|
log.warning('to the GitHub issue description:')
|
|
|
|
log.warning('---')
|
2022-10-31 17:24:00 +04:00
|
|
|
|
2023-05-24 18:15:18 +04:00
|
|
|
log.warning('JavaScript: %s', js_code)
|
|
|
|
log.warning(
|
2022-10-31 17:24:00 +04:00
|
|
|
'All script tags: %s',
|
|
|
|
re.findall(SCRIPT_TAG_REGEX, pagehead)
|
|
|
|
)
|
2023-05-24 18:15:18 +04:00
|
|
|
log.warning('---')
|
2022-10-31 17:24:00 +04:00
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
raise TokenError(
|
|
|
|
'Unable to parse TOKEN from the page'
|
2022-07-01 14:28:39 +04:00
|
|
|
) from err
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
return self.token
|
|
|
|
|
|
|
|
def generate_sec(self) -> str:
|
|
|
|
"""Generates Aternos SEC token which
|
|
|
|
is also needed for most API requests
|
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
Returns:
|
|
|
|
Random SEC `key:value` string
|
2022-06-23 15:13:56 +04:00
|
|
|
"""
|
|
|
|
|
2023-05-24 17:12:34 +04:00
|
|
|
randkey = self.generate_sec_part()
|
|
|
|
randval = self.generate_sec_part()
|
2022-06-23 15:13:56 +04:00
|
|
|
self.sec = f'{randkey}:{randval}'
|
|
|
|
self.session.cookies.set(
|
|
|
|
f'ATERNOS_SEC_{randkey}', randval,
|
|
|
|
domain='aternos.org'
|
|
|
|
)
|
|
|
|
|
|
|
|
return self.sec
|
2023-05-24 20:03:09 +04:00
|
|
|
|
2023-05-24 17:12:34 +04:00
|
|
|
def generate_sec_part(self) -> str:
|
|
|
|
"""Generates a part for SEC token"""
|
|
|
|
|
2023-05-24 20:03:09 +04:00
|
|
|
return ''.join(
|
|
|
|
secrets.choice(SEC_ALPHABET)
|
|
|
|
for _ in range(11)
|
|
|
|
) + ('0' * 5)
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
def request_cloudflare(
|
|
|
|
self, url: str, method: str,
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
params: Optional[Dict[Any, Any]] = None,
|
|
|
|
data: Optional[Dict[Any, Any]] = None,
|
|
|
|
headers: Optional[Dict[Any, Any]] = None,
|
|
|
|
reqcookies: Optional[Dict[Any, Any]] = None,
|
2022-06-23 15:13:56 +04:00
|
|
|
sendtoken: bool = False,
|
2023-05-29 11:44:19 +04:00
|
|
|
retries: int = 5,
|
|
|
|
timeout: int = 4) -> requests.Response:
|
2022-06-23 15:13:56 +04:00
|
|
|
"""Sends a request to Aternos API bypass Cloudflare
|
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
Args:
|
|
|
|
url (str): Request URL
|
|
|
|
method (str): Request method, must be GET or POST
|
|
|
|
params (Optional[Dict[Any, Any]], optional): URL parameters
|
|
|
|
data (Optional[Dict[Any, Any]], optional): POST request data,
|
|
|
|
if the method is GET, this dict will be combined with params
|
|
|
|
headers (Optional[Dict[Any, Any]], optional): Custom headers
|
|
|
|
reqcookies (Optional[Dict[Any, Any]], optional):
|
|
|
|
Cookies only for this request
|
|
|
|
sendtoken (bool, optional): If the ajax and SEC token
|
|
|
|
should be sent
|
2023-05-29 11:44:19 +04:00
|
|
|
retries (int, optional): How many times parser must retry
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
connection to API bypass Cloudflare
|
2023-05-29 11:44:19 +04:00
|
|
|
timeout (int, optional): Request timeout in seconds
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
|
|
|
|
Raises:
|
|
|
|
CloudflareError: When the parser has exceeded retries count
|
|
|
|
NotImplementedError: When the specified method is not GET or POST
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
API response
|
2022-06-23 15:13:56 +04:00
|
|
|
"""
|
|
|
|
|
2023-05-29 11:44:19 +04:00
|
|
|
if retries <= 0:
|
2022-06-23 15:13:56 +04:00
|
|
|
raise CloudflareError('Unable to bypass Cloudflare protection')
|
|
|
|
|
2022-11-03 18:01:53 +04:00
|
|
|
try:
|
|
|
|
self.atcookie = self.session.cookies['ATERNOS_SESSION']
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
2022-10-05 19:44:00 +04:00
|
|
|
self.refresh_session()
|
2022-08-22 09:55:08 +04:00
|
|
|
|
|
|
|
params = params or {}
|
|
|
|
data = data or {}
|
|
|
|
headers = headers or {}
|
|
|
|
reqcookies = reqcookies or {}
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
method = method or 'GET'
|
|
|
|
method = method.upper().strip()
|
|
|
|
if method not in ('GET', 'POST'):
|
|
|
|
raise NotImplementedError('Only GET and POST are available')
|
|
|
|
|
|
|
|
if sendtoken:
|
|
|
|
params['TOKEN'] = self.token
|
|
|
|
params['SEC'] = self.sec
|
|
|
|
headers['X-Requested-With'] = 'XMLHttpRequest'
|
|
|
|
|
|
|
|
# requests.cookies.CookieConflictError bugfix
|
2022-11-03 18:01:53 +04:00
|
|
|
reqcookies['ATERNOS_SESSION'] = self.atcookie
|
2022-06-23 15:13:56 +04:00
|
|
|
del self.session.cookies['ATERNOS_SESSION']
|
|
|
|
|
2023-05-29 11:44:19 +04:00
|
|
|
if is_debug():
|
2023-05-24 20:03:09 +04:00
|
|
|
|
|
|
|
reqcookies_dbg = {
|
|
|
|
k: str(v or '')[:3]
|
|
|
|
for k, v in reqcookies.items()
|
|
|
|
}
|
|
|
|
|
|
|
|
session_cookies_dbg = {
|
|
|
|
k: str(v or '')[:3]
|
|
|
|
for k, v in self.session.cookies.items()
|
|
|
|
}
|
|
|
|
|
|
|
|
log.debug('Requesting(%s)%s', method, url)
|
|
|
|
log.debug('headers=%s', headers)
|
|
|
|
log.debug('params=%s', params)
|
|
|
|
log.debug('data=%s', data)
|
|
|
|
log.debug('req-cookies=%s', reqcookies_dbg)
|
|
|
|
log.debug('session-cookies=%s', session_cookies_dbg)
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
if method == 'POST':
|
2022-08-22 09:55:08 +04:00
|
|
|
sendreq = partial(
|
|
|
|
self.session.post,
|
|
|
|
params=params,
|
2023-05-29 11:44:19 +04:00
|
|
|
data=data,
|
2022-06-23 15:13:56 +04:00
|
|
|
)
|
|
|
|
else:
|
2022-08-22 09:55:08 +04:00
|
|
|
sendreq = partial(
|
|
|
|
self.session.get,
|
2023-05-29 11:44:19 +04:00
|
|
|
params={**params, **data},
|
2022-06-23 15:13:56 +04:00
|
|
|
)
|
|
|
|
|
2022-08-22 09:55:08 +04:00
|
|
|
req = sendreq(
|
|
|
|
url,
|
|
|
|
headers=headers,
|
2023-05-29 11:44:19 +04:00
|
|
|
cookies=reqcookies,
|
|
|
|
timeout=timeout,
|
2022-08-22 09:55:08 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
resp_type = req.headers.get('content-type', '')
|
|
|
|
html_type = resp_type.find('text/html') != -1
|
|
|
|
cloudflare = req.status_code == 403
|
|
|
|
|
|
|
|
if html_type and cloudflare:
|
2023-05-24 18:15:18 +04:00
|
|
|
log.info('Retrying to bypass Cloudflare')
|
2022-11-03 18:04:28 +04:00
|
|
|
time.sleep(0.3)
|
2022-07-01 09:36:52 +04:00
|
|
|
return self.request_cloudflare(
|
2022-06-23 15:13:56 +04:00
|
|
|
url, method,
|
|
|
|
params, data,
|
|
|
|
headers, reqcookies,
|
2023-05-29 11:44:19 +04:00
|
|
|
sendtoken, retries - 1
|
2022-06-23 15:13:56 +04:00
|
|
|
)
|
|
|
|
|
2023-05-24 18:15:18 +04:00
|
|
|
log.debug('AternosConnect received: %s', req.text[:65])
|
|
|
|
log.info(
|
2022-09-30 14:39:16 +04:00
|
|
|
'%s completed with %s status',
|
|
|
|
method, req.status_code
|
2022-06-23 15:13:56 +04:00
|
|
|
)
|
|
|
|
|
2022-07-01 14:28:39 +04:00
|
|
|
if req.status_code == 402:
|
|
|
|
raise AternosPermissionError
|
2022-07-01 10:31:23 +04:00
|
|
|
|
2022-07-01 14:28:39 +04:00
|
|
|
req.raise_for_status()
|
2022-06-23 15:13:56 +04:00
|
|
|
return req
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
|
|
|
|
@property
|
|
|
|
def atsession(self) -> str:
|
|
|
|
"""Aternos session cookie,
|
|
|
|
empty string if not logged in
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Session cookie
|
|
|
|
"""
|
|
|
|
|
|
|
|
return self.session.cookies.get(
|
|
|
|
'ATERNOS_SESSION', ''
|
|
|
|
)
|