Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
9aae685b21 | |||
cae94d7aa8 | |||
fae7b0fd37 | |||
b7771b4da2 | |||
e3a10af101 | |||
33a5bcacf1 | |||
342ca2e1cc | |||
580240a015 | |||
1517103ba3 | |||
3dac8c5e74 | |||
8cf86fb9d3 | |||
cf927df6e6 | |||
a6f5dbd05f | |||
967afa472f | |||
a65568cd0c | |||
6e45334d99 | |||
936a1010a6 | |||
acc528cb1d | |||
614d273104 | |||
95966764e8 | |||
f52b078e6a | |||
3af27f6512 | |||
6276242260 | |||
45623de97b | |||
25f932121c | |||
61be2b1edf | |||
ce9034ad24 | |||
69b2073b74 | |||
eb048bf9f8 | |||
fe1206dc84 | |||
a2a1ed3dad | |||
39c8f6913e | |||
d7b15b3708 | |||
4fe3efa045 | |||
14bcb46735 | |||
b04cc896d8 | |||
f07062788b | |||
4504bdaa97 | |||
ac135614cc | |||
41752e1f2e | |||
a1739e627e | |||
12ff11deea | |||
4e3a16b8d4 | |||
5f56f59ab8 | |||
50c66efbda | |||
47b3154c6a | |||
632e4e8ad2 | |||
7c0d66f126 | |||
842fb64dae | |||
b22349cb1a | |||
a9ced91741 | |||
e374562189 | |||
e0b64e09b1 | |||
c51337d249 | |||
f4896e0b79 | |||
13f5c673ad | |||
e95ffbd505 | |||
5e638806b5 | |||
7860fa3669 | |||
e38f603e8b | |||
0e1c42a8fb | |||
ddc412b348 |
@ -21,6 +21,7 @@ insert_final_newline = false
|
|||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
[*.py]
|
[*.py]
|
||||||
|
max_line_length = 240
|
||||||
line_length=120
|
line_length=120
|
||||||
multi_line_output=0
|
multi_line_output=0
|
||||||
balanced_wrapping=True
|
balanced_wrapping=True
|
||||||
|
@ -10,4 +10,4 @@ repos:
|
|||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
|
|
||||||
default_language_version:
|
default_language_version:
|
||||||
python: python3.7
|
python: python3.8
|
||||||
|
@ -36,14 +36,6 @@ erepublik.constants module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
erepublik.types module
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
.. automodule:: erepublik.types
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
erepublik.utils module
|
erepublik.utils module
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
@ -52,6 +44,14 @@ erepublik.utils module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
erepublik.ws module
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: erepublik.ws
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -4,9 +4,8 @@
|
|||||||
|
|
||||||
__author__ = """Eriks Karls"""
|
__author__ = """Eriks Karls"""
|
||||||
__email__ = 'eriks@72.lv'
|
__email__ = 'eriks@72.lv'
|
||||||
__version__ = '0.24.0.3'
|
__version__ = '0.25.0.3'
|
||||||
|
|
||||||
from erepublik import classes, constants, utils
|
|
||||||
from erepublik.citizen import Citizen
|
from erepublik.citizen import Citizen
|
||||||
|
|
||||||
__all__ = ["classes", "utils", "Citizen", 'constants']
|
__all__ = ['Citizen', '__version__']
|
||||||
|
201
erepublik/_logging.py
Normal file
201
erepublik/_logging.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import base64
|
||||||
|
import datetime
|
||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import weakref
|
||||||
|
from logging import LogRecord, handlers
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, Union
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from erepublik.classes import Reporter
|
||||||
|
from erepublik.constants import erep_tz
|
||||||
|
from erepublik.utils import json, json_dumps, json_loads, slugify
|
||||||
|
|
||||||
|
|
||||||
|
class ErepublikFileHandler(handlers.TimedRotatingFileHandler):
|
||||||
|
_file_path: Path
|
||||||
|
|
||||||
|
def __init__(self, filename: str = 'log/erepublik.log', *args, **kwargs):
|
||||||
|
log_path = Path(filename)
|
||||||
|
self._file_path = log_path
|
||||||
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
at_time = erep_tz.localize(datetime.datetime.now()).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
kwargs.update(atTime=at_time)
|
||||||
|
super().__init__(filename, when='d', *args, **kwargs)
|
||||||
|
|
||||||
|
def doRollover(self) -> None:
|
||||||
|
self._file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
super().doRollover()
|
||||||
|
|
||||||
|
def emit(self, record: LogRecord) -> None:
|
||||||
|
self._file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
super().emit(record)
|
||||||
|
|
||||||
|
|
||||||
|
class ErepublikLogConsoleHandler(logging.StreamHandler):
|
||||||
|
def __init__(self, *_):
|
||||||
|
super().__init__(sys.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
class ErepublikFormatter(logging.Formatter):
|
||||||
|
"""override logging.Formatter to use an aware datetime object"""
|
||||||
|
|
||||||
|
dbg_fmt = "[%(asctime)s] DEBUG: %(module)s: %(lineno)d: %(msg)s"
|
||||||
|
info_fmt = "[%(asctime)s] %(msg)s"
|
||||||
|
default_fmt = "[%(asctime)s] %(levelname)s: %(msg)s"
|
||||||
|
|
||||||
|
def converter(self, timestamp: Union[int, float]) -> datetime.datetime:
|
||||||
|
return datetime.datetime.fromtimestamp(timestamp).astimezone(erep_tz)
|
||||||
|
|
||||||
|
def format(self, record: logging.LogRecord) -> str:
|
||||||
|
"""
|
||||||
|
Format the specified record as text.
|
||||||
|
|
||||||
|
The record's attribute dictionary is used as the operand to a
|
||||||
|
string formatting operation which yields the returned string.
|
||||||
|
Before formatting the dictionary, a couple of preparatory steps
|
||||||
|
are carried out. The message attribute of the record is computed
|
||||||
|
using LogRecord.getMessage(). If the formatting string uses the
|
||||||
|
time (as determined by a call to usesTime(), formatTime() is
|
||||||
|
called to format the event time. If there is exception information,
|
||||||
|
it is formatted using formatException() and appended to the message.
|
||||||
|
"""
|
||||||
|
if record.levelno == logging.DEBUG:
|
||||||
|
self._fmt = self.dbg_fmt
|
||||||
|
elif record.levelno == logging.INFO:
|
||||||
|
self._fmt = self.info_fmt
|
||||||
|
else:
|
||||||
|
self._fmt = self.default_fmt
|
||||||
|
self._style = logging.PercentStyle(self._fmt)
|
||||||
|
|
||||||
|
record.message = record.getMessage()
|
||||||
|
if self.usesTime():
|
||||||
|
record.asctime = self.formatTime(record, self.datefmt)
|
||||||
|
s = self.formatMessage(record)
|
||||||
|
if record.exc_info:
|
||||||
|
# Cache the traceback text to avoid converting it multiple times
|
||||||
|
# (it's constant anyway)
|
||||||
|
if not record.exc_text:
|
||||||
|
record.exc_text = self.formatException(record.exc_info)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def formatTime(self, record, datefmt=None):
|
||||||
|
dt = self.converter(record.created)
|
||||||
|
if datefmt:
|
||||||
|
s = dt.strftime(datefmt)
|
||||||
|
else:
|
||||||
|
s = dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
return s
|
||||||
|
|
||||||
|
def usesTime(self):
|
||||||
|
return self._style.usesTime()
|
||||||
|
|
||||||
|
|
||||||
|
class ErepublikErrorHTTTPHandler(handlers.HTTPHandler):
|
||||||
|
def __init__(self, reporter: Reporter):
|
||||||
|
logging.Handler.__init__(self, level=logging.ERROR)
|
||||||
|
self._reporter = weakref.ref(reporter)
|
||||||
|
self.host = 'erep.lv'
|
||||||
|
self.url = '/ebot/error/'
|
||||||
|
self.method = 'POST'
|
||||||
|
self.secure = True
|
||||||
|
self.credentials = (str(reporter.citizen_id), reporter.key)
|
||||||
|
self.context = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def reporter(self):
|
||||||
|
return self._reporter()
|
||||||
|
|
||||||
|
def _get_last_response(self) -> Dict[str, str]:
|
||||||
|
response = self.reporter.citizen.r
|
||||||
|
url = response.url
|
||||||
|
last_index = url.index("?") if "?" in url else len(response.url)
|
||||||
|
|
||||||
|
name = slugify(response.url[len(self.reporter.citizen.url):last_index])
|
||||||
|
html = response.text
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_loads(html)
|
||||||
|
ext = 'json'
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
ext = 'html'
|
||||||
|
try:
|
||||||
|
resp_time = datetime.datetime.strptime(
|
||||||
|
response.headers.get('date'), '%a, %d %b %Y %H:%M:%S %Z'
|
||||||
|
).replace(tzinfo=datetime.timezone.utc).astimezone(erep_tz).strftime('%F_%H-%M-%S')
|
||||||
|
except:
|
||||||
|
resp_time = slugify(response.headers.get('date'))
|
||||||
|
return dict(name=f"{resp_time}_{name}.{ext}", content=html.encode('utf-8'),
|
||||||
|
mimetype="application/json" if ext == 'json' else "text/html")
|
||||||
|
|
||||||
|
def _get_local_vars(self) -> str:
|
||||||
|
trace = inspect.trace()
|
||||||
|
local_vars = {}
|
||||||
|
if trace:
|
||||||
|
local_vars = trace[-1][0].f_locals
|
||||||
|
if local_vars.get('__name__') == '__main__':
|
||||||
|
local_vars.update(commit_id=local_vars.get('COMMIT_ID'), interactive=local_vars.get('INTERACTIVE'),
|
||||||
|
version=local_vars.get('__version__'), config=local_vars.get('CONFIG'))
|
||||||
|
else:
|
||||||
|
stack = inspect.stack()
|
||||||
|
report_error_caller_found = False
|
||||||
|
for frame in stack:
|
||||||
|
if report_error_caller_found:
|
||||||
|
local_vars = frame.frame.f_locals
|
||||||
|
break
|
||||||
|
if 'report_error' in str(frame.frame):
|
||||||
|
report_error_caller_found = True
|
||||||
|
|
||||||
|
if 'state_thread' in local_vars:
|
||||||
|
local_vars.pop('state_thread', None)
|
||||||
|
from erepublik import Citizen
|
||||||
|
if isinstance(local_vars.get('self'), Citizen):
|
||||||
|
local_vars['self'] = repr(local_vars['self'])
|
||||||
|
if isinstance(local_vars.get('player'), Citizen):
|
||||||
|
local_vars['player'] = repr(local_vars['player'])
|
||||||
|
if isinstance(local_vars.get('citizen'), Citizen):
|
||||||
|
local_vars['citizen'] = repr(local_vars['citizen'])
|
||||||
|
return json_dumps(local_vars)
|
||||||
|
|
||||||
|
def _get_instance_json(self) -> str:
|
||||||
|
if self.reporter:
|
||||||
|
return self.reporter.citizen.to_json(False)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def mapLogRecord(self, record: logging.LogRecord) -> Dict[str, Any]:
|
||||||
|
data = super().mapLogRecord(record)
|
||||||
|
|
||||||
|
# Log last response
|
||||||
|
resp = self._get_last_response()
|
||||||
|
files = [('file', (resp.get('name'), resp.get('content'), resp.get('mimetype'))), ]
|
||||||
|
|
||||||
|
files += list(('file', (f, open(f'log/{f}', 'rb'))) for f in os.listdir('log') if f.endswith('.log'))
|
||||||
|
local_vars_json = self._get_local_vars()
|
||||||
|
if local_vars_json:
|
||||||
|
files.append(('file', ('local_vars.json', local_vars_json, "application/json")))
|
||||||
|
instance_json = self._get_instance_json()
|
||||||
|
if instance_json:
|
||||||
|
files.append(('file', ('instance.json', instance_json, "application/json")))
|
||||||
|
data.update(files=files)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
"""
|
||||||
|
Emit a record.
|
||||||
|
|
||||||
|
Send the record to the Web server as a percent-encoded dictionary
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
proto = 'https' if self.secure else 'http'
|
||||||
|
u, p = self.credentials
|
||||||
|
s = 'Basic ' + base64.b64encode(f'{u}:{p}'.encode('utf-8')).strip().decode('ascii')
|
||||||
|
headers = {'Authorization': s}
|
||||||
|
data = self.mapLogRecord(record)
|
||||||
|
files = data.pop('files') if 'files' in data else None
|
||||||
|
requests.post(f"{proto}://{self.host}{self.url}", headers=headers, data=data, files=files)
|
||||||
|
except Exception:
|
||||||
|
self.handleError(record)
|
@ -1,11 +1,13 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import hashlib
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict, List, Mapping, Union
|
from typing import Any, Dict, List, Mapping, Union
|
||||||
|
|
||||||
from requests import Response, Session
|
from requests import Response, Session
|
||||||
|
from requests_toolbelt.utils import dump
|
||||||
|
|
||||||
from . import constants, utils
|
from erepublik import constants, utils
|
||||||
|
|
||||||
__all__ = ['SlowRequests', 'CitizenAPI']
|
__all__ = ['SlowRequests', 'CitizenAPI']
|
||||||
|
|
||||||
@ -45,6 +47,7 @@ class SlowRequests(Session):
|
|||||||
self.request_log_name = utils.get_file(utils.now().strftime("debug/requests_%Y-%m-%d.log"))
|
self.request_log_name = utils.get_file(utils.now().strftime("debug/requests_%Y-%m-%d.log"))
|
||||||
self.last_time = utils.now()
|
self.last_time = utils.now()
|
||||||
self.headers.update({'User-Agent': user_agent})
|
self.headers.update({'User-Agent': user_agent})
|
||||||
|
self.hooks["response"] = [self._log_response]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
@ -55,7 +58,7 @@ class SlowRequests(Session):
|
|||||||
self._slow_down_requests()
|
self._slow_down_requests()
|
||||||
self._log_request(url, method, **kwargs)
|
self._log_request(url, method, **kwargs)
|
||||||
resp = super().request(method, url, *args, **kwargs)
|
resp = super().request(method, url, *args, **kwargs)
|
||||||
self._log_response(url, resp)
|
# self._log_response(resp)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def _slow_down_requests(self):
|
def _slow_down_requests(self):
|
||||||
@ -84,30 +87,33 @@ class SlowRequests(Session):
|
|||||||
body = f"[{utils.now().strftime('%F %T')}]\tURL: '{url}'\tMETHOD: {method}\tARGS: {args}\n"
|
body = f"[{utils.now().strftime('%F %T')}]\tURL: '{url}'\tMETHOD: {method}\tARGS: {args}\n"
|
||||||
with open(self.request_log_name, 'ab') as file:
|
with open(self.request_log_name, 'ab') as file:
|
||||||
file.write(body.encode("UTF-8"))
|
file.write(body.encode("UTF-8"))
|
||||||
pass
|
|
||||||
|
|
||||||
def _log_response(self, url, resp, redirect: bool = False):
|
def _log_response(self, response: Response, *args, **kwargs):
|
||||||
from erepublik import Citizen
|
redirect = kwargs.get('redirect')
|
||||||
|
url = response.request.url
|
||||||
if self.debug:
|
if self.debug:
|
||||||
if resp.history and not redirect:
|
if response.history and not redirect:
|
||||||
for hist_resp in resp.history:
|
for hist_resp in response.history:
|
||||||
self._log_request(hist_resp.request.url, 'REDIRECT')
|
self._log_request(hist_resp.request.url, 'REDIRECT')
|
||||||
self._log_response(hist_resp.request.url, hist_resp, redirect=True)
|
self._log_response(hist_resp, redirect=True)
|
||||||
|
|
||||||
fd_path = 'debug/requests'
|
fd_path = 'debug/requests'
|
||||||
fd_time = self.last_time.strftime('%Y/%m/%d/%H-%M-%S')
|
fd_time = self.last_time.strftime('%Y/%m/%d/%H-%M-%S')
|
||||||
fd_name = utils.slugify(url[len(Citizen.url):])
|
fd_name = utils.slugify(url[len(CitizenBaseAPI.url):])
|
||||||
fd_extra = '_REDIRECT' if redirect else ""
|
fd_extra = '_REDIRECT' if redirect else ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.json.loads(resp.text)
|
utils.json.loads(response.text)
|
||||||
fd_ext = 'json'
|
fd_ext = 'json'
|
||||||
except utils.json.JSONDecodeError:
|
except utils.json.JSONDecodeError:
|
||||||
fd_ext = 'html'
|
fd_ext = 'html'
|
||||||
|
|
||||||
filename = f'{fd_path}/{fd_time}_{fd_name}{fd_extra}.{fd_ext}'
|
filename = f'{fd_path}/{fd_time}_{fd_name}{fd_extra}.{fd_ext}'
|
||||||
utils.write_file(filename, resp.text)
|
utils.write_file(filename, response.text)
|
||||||
pass
|
|
||||||
|
if not redirect:
|
||||||
|
data = dump.dump_all(response)
|
||||||
|
utils.write_file(f'debug/dumps/{fd_time}_{fd_name}{fd_extra}.{fd_ext}.dump', data.decode('utf8'))
|
||||||
|
|
||||||
|
|
||||||
class CitizenBaseAPI:
|
class CitizenBaseAPI:
|
||||||
@ -138,7 +144,7 @@ class CitizenBaseAPI:
|
|||||||
self._req.proxies = dict(http=url, https=url)
|
self._req.proxies = dict(http=url, https=url)
|
||||||
|
|
||||||
def set_http_proxy(self, host: str, port: int, username: str = None, password: str = None):
|
def set_http_proxy(self, host: str, port: int, username: str = None, password: str = None):
|
||||||
url = f'http://{username}:{password}@{host}:{port}' if username and password else f'socks5://{host}:{port}'
|
url = f'http://{username}:{password}@{host}:{port}' if username and password else f'http://{host}:{port}'
|
||||||
self._req.proxies = dict(http=url)
|
self._req.proxies = dict(http=url)
|
||||||
|
|
||||||
def _get_main_session_captcha(self) -> Response:
|
def _get_main_session_captcha(self) -> Response:
|
||||||
@ -147,18 +153,33 @@ class CitizenBaseAPI:
|
|||||||
def _get_main_session_unlock_popup(self) -> Response:
|
def _get_main_session_unlock_popup(self) -> Response:
|
||||||
return self.get(f'{self.url}/main/sessionUnlockPopup')
|
return self.get(f'{self.url}/main/sessionUnlockPopup')
|
||||||
|
|
||||||
def _post_main_session_get_challenge(self, captcha_id: int) -> Response:
|
def _post_main_session_get_challenge(self, captcha_id: int, image_id: str = "") -> Response:
|
||||||
env = dict(l=['tets', ], s=[], c=["l_chathwe", "l_chatroom"], m=0)
|
c = [cookie.name for cookie in self._req.cookies if not cookie.has_nonstandard_attr('HttpOnly')]
|
||||||
|
env = dict(l=['tets'], s=[], c=c, m=0)
|
||||||
data = dict(_token=self.token, captchaId=captcha_id, env=utils.b64json(env))
|
data = dict(_token=self.token, captchaId=captcha_id, env=utils.b64json(env))
|
||||||
|
if image_id:
|
||||||
|
data.update(imageId=image_id, isRefresh=True)
|
||||||
return self.post(f'{self.url}/main/sessionGetChallenge', data=data)
|
return self.post(f'{self.url}/main/sessionGetChallenge', data=data)
|
||||||
|
|
||||||
def _post_main_session_unlock(
|
def _post_main_session_unlock(
|
||||||
self, captcha: int, image: str, challenge: str, coords: List[Dict[str, int]], src: str
|
self, captcha_id: int, image_id: str, challenge_id: str, coords: List[Dict[str, int]], src: str
|
||||||
) -> Response:
|
) -> Response:
|
||||||
env = dict(l=['tets', ], s=[], c=["l_chathwe", "l_chatroom"], m=0)
|
c = [cookie.name for cookie in self._req.cookies if not cookie.has_nonstandard_attr('HttpOnly')]
|
||||||
data = dict(_token=self.token, captchaId=captcha, imageId=image, challengeId=challenge,
|
env = dict(l=['tets'], s=[], c=c, m=0)
|
||||||
clickMatrix=coords, isMobile=0, env=utils.b64json(env), src=src)
|
cookies = dict(sh=hashlib.sha256(','.join(env['l']+env['s']).encode('utf8')).hexdigest(),
|
||||||
return self.post(f'{self.url}/main/sessionUnlock', data=data)
|
ch=hashlib.sha256(','.join(env['c']).encode('utf8')).hexdigest())
|
||||||
|
cookie_kwargs = dict(expires=int(time.time())+120, path="/en/main/sessionUnlock", domain='.www.erepublik.com',
|
||||||
|
secure=True, rest={'HttpOnly': True})
|
||||||
|
self._req.cookies.set('sh', cookies['sh'], **cookie_kwargs)
|
||||||
|
self._req.cookies.set('ch', cookies['ch'], **cookie_kwargs)
|
||||||
|
b64_env = utils.b64json(env)
|
||||||
|
data = dict(_token=self.token, captchaId=captcha_id, imageId=image_id, challengeId=challenge_id,
|
||||||
|
clickMatrix=utils.json_dumps(coords).replace(' ', ''), isMobile=0, env=b64_env, src=src)
|
||||||
|
return self.post(f'{self.url}/main/sessionUnlock', data=data, json=data,
|
||||||
|
headers={'X-Requested-With': 'XMLHttpRequest', 'Referer': 'https://www.erepublik.com/en'})
|
||||||
|
|
||||||
|
def _post_energy_refill_get_inventory(self):
|
||||||
|
return self.post(f'{self.url}/economy/energyRefill-getInventory', data={'_token': self.token})
|
||||||
|
|
||||||
|
|
||||||
class ErepublikAnniversaryAPI(CitizenBaseAPI):
|
class ErepublikAnniversaryAPI(CitizenBaseAPI):
|
||||||
@ -437,8 +458,10 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
|
|||||||
data = dict(_token=self.token, sideCountryId=side_id, battleId=battle_id)
|
data = dict(_token=self.token, sideCountryId=side_id, battleId=battle_id)
|
||||||
return self.post(f"{self.url}/main/battlefieldTravel", data=data)
|
return self.post(f"{self.url}/main/battlefieldTravel", data=data)
|
||||||
|
|
||||||
def _post_main_battlefield_change_division(self, battle_id: int, division_id: int) -> Response:
|
def _post_main_battlefield_change_division(self, battle_id: int, division_id: int, side_id: int = None) -> Response:
|
||||||
data = dict(_token=self.token, battleZoneId=division_id, battleId=battle_id)
|
data = dict(_token=self.token, battleZoneId=division_id, battleId=battle_id)
|
||||||
|
if side_id is not None:
|
||||||
|
data.update(sideCountryId=side_id)
|
||||||
return self.post(f"{self.url}/main/battlefieldTravel", data=data)
|
return self.post(f"{self.url}/main/battlefieldTravel", data=data)
|
||||||
|
|
||||||
def _get_wars_show(self, war_id: int) -> Response:
|
def _get_wars_show(self, war_id: int) -> Response:
|
||||||
@ -489,6 +512,9 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
|
|||||||
weaponQuality=weapon, totalEnergy=energy, **kwargs)
|
weaponQuality=weapon, totalEnergy=energy, **kwargs)
|
||||||
return self.post(f"{self.url}/military/fightDeploy-startDeploy", data=data)
|
return self.post(f"{self.url}/military/fightDeploy-startDeploy", data=data)
|
||||||
|
|
||||||
|
def _post_military_fight_deploy_deploy_report_data(self, deployment_id: int) -> Response:
|
||||||
|
data = dict(_token=self.token, deploymentId=deployment_id)
|
||||||
|
return self.post(f"{self.url}/military/fightDeploy-deployReportData", data=data)
|
||||||
|
|
||||||
|
|
||||||
class ErepublikPoliticsAPI(CitizenBaseAPI):
|
class ErepublikPoliticsAPI(CitizenBaseAPI):
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,20 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import threading
|
import threading
|
||||||
|
import warnings
|
||||||
import weakref
|
import weakref
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Any, Dict, Generator, Iterable, List, NamedTuple, NoReturn, Union
|
from io import BytesIO
|
||||||
|
from typing import Any, Dict, Generator, Iterable, List, NamedTuple, NoReturn, Optional, Tuple, Union
|
||||||
|
|
||||||
from requests import Response, Session, post
|
from requests import HTTPError, Response, Session, post
|
||||||
|
|
||||||
from . import constants, types, utils
|
from erepublik import _types as types
|
||||||
|
from erepublik import constants, utils
|
||||||
|
|
||||||
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
|
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
|
||||||
'ErepublikJSONEncoder', 'ErepublikNetworkException', 'EnergyToFight', 'Holding', 'Inventory', 'MyCompanies',
|
'ErepublikNetworkException', 'EnergyToFight', 'Holding', 'Inventory', 'MyCompanies', 'OfferItem', 'Politics',
|
||||||
'OfferItem', 'Politics', 'Reporter', 'TelegramReporter', ]
|
'Reporter', 'TelegramReporter', ]
|
||||||
|
|
||||||
|
|
||||||
class ErepublikException(Exception):
|
class ErepublikException(Exception):
|
||||||
@ -25,6 +28,14 @@ class ErepublikNetworkException(ErepublikException):
|
|||||||
self.request = request
|
self.request = request
|
||||||
|
|
||||||
|
|
||||||
|
class CloudFlareSessionError(ErepublikNetworkException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CaptchaSessionError(ErepublikNetworkException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Holding:
|
class Holding:
|
||||||
id: int
|
id: int
|
||||||
region: int
|
region: int
|
||||||
@ -412,26 +423,35 @@ class Config:
|
|||||||
class Energy:
|
class Energy:
|
||||||
limit = 500 # energyToRecover
|
limit = 500 # energyToRecover
|
||||||
interval = 10 # energyPerInterval
|
interval = 10 # energyPerInterval
|
||||||
recoverable = 0 # energyFromFoodRemaining
|
energy = 0 # energy
|
||||||
recovered = 0 # energy
|
|
||||||
_recovery_time = None
|
_recovery_time = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._recovery_time = utils.now()
|
self._recovery_time = utils.now()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.recovered:4}/{self.limit:4} + {self.recoverable:4}, {self.interval:3}hp/6min"
|
return f"{self.energy:4}/{self.limit:4}, {self.interval:3}hp/6min"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recovered(self):
|
||||||
|
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.energy', DeprecationWarning)
|
||||||
|
return self.energy
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recoverable(self):
|
||||||
|
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.energy', DeprecationWarning)
|
||||||
|
return 0
|
||||||
|
|
||||||
def set_reference_time(self, recovery_time: datetime.datetime):
|
def set_reference_time(self, recovery_time: datetime.datetime):
|
||||||
self._recovery_time = recovery_time.replace(microsecond=0)
|
self._recovery_time = recovery_time.replace(microsecond=0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def food_fights(self):
|
def food_fights(self):
|
||||||
return self.available // 10
|
return self.energy // 10
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reference_time(self):
|
def reference_time(self):
|
||||||
if self.is_recovered_full or self._recovery_time < utils.now():
|
if self.is_energy_full or self._recovery_time < utils.now():
|
||||||
ret = utils.now()
|
ret = utils.now()
|
||||||
else:
|
else:
|
||||||
ret = self._recovery_time
|
ret = self._recovery_time
|
||||||
@ -439,26 +459,28 @@ class Energy:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_recoverable_full(self):
|
def is_recoverable_full(self):
|
||||||
return self.recoverable >= self.limit - 5 * self.interval
|
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.is_energy_full', DeprecationWarning)
|
||||||
|
return self.is_energy_full
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_recovered_full(self):
|
def is_recovered_full(self):
|
||||||
return self.recovered >= self.limit - self.interval
|
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.is_energy_full', DeprecationWarning)
|
||||||
|
return self.is_energy_full
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_energy_full(self):
|
def is_energy_full(self):
|
||||||
return self.is_recoverable_full and self.is_recovered_full
|
return self.energy >= self.limit - self.interval
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
return self.recovered + self.recoverable
|
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.energy', DeprecationWarning)
|
||||||
|
return self.energy
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def as_dict(self) -> Dict[str, Union[int, datetime.datetime, bool]]:
|
def as_dict(self) -> Dict[str, Union[int, datetime.datetime, bool]]:
|
||||||
return dict(limit=self.limit, interval=self.interval, recoverable=self.recoverable, recovered=self.recovered,
|
return dict(limit=self.limit, interval=self.interval, energy=self.energy,
|
||||||
reference_time=self.reference_time, food_fights=self.food_fights,
|
reference_time=self.reference_time, food_fights=self.food_fights,
|
||||||
is_recoverable_full=self.is_recoverable_full, is_recovered_full=self.is_recovered_full,
|
is_energy_full=self.is_energy_full)
|
||||||
is_energy_full=self.is_energy_full, available=self.available)
|
|
||||||
|
|
||||||
|
|
||||||
class Details:
|
class Details:
|
||||||
@ -482,6 +504,8 @@ class Details:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.next_pp = []
|
self.next_pp = []
|
||||||
self.mayhem_skills = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0}
|
self.mayhem_skills = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0}
|
||||||
|
_default_country = constants.Country(0, 'Unknown', 'Unknown', 'XX')
|
||||||
|
self.citizenship = self.current_country = self.residence_country = _default_country
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def xp_till_level_up(self):
|
def xp_till_level_up(self):
|
||||||
@ -603,21 +627,33 @@ class Reporter:
|
|||||||
if self.__to_update:
|
if self.__to_update:
|
||||||
for unreported_data in self.__to_update:
|
for unreported_data in self.__to_update:
|
||||||
unreported_data.update(player_id=self.citizen_id, key=self.key)
|
unreported_data.update(player_id=self.citizen_id, key=self.key)
|
||||||
unreported_data = utils.json.loads(utils.json.dumps(unreported_data, cls=ErepublikJSONEncoder))
|
unreported_data = utils.json_loads(utils.json_dumps(unreported_data))
|
||||||
self._req.post(f"{self.url}/bot/update", json=unreported_data)
|
r = self._req.post(f"{self.url}/bot/update", json=unreported_data)
|
||||||
|
r.raise_for_status()
|
||||||
self.__to_update.clear()
|
self.__to_update.clear()
|
||||||
data = utils.json.loads(utils.json.dumps(data, cls=ErepublikJSONEncoder))
|
data = utils.json.loads(utils.json_dumps(data))
|
||||||
r = self._req.post(f"{self.url}/bot/update", json=data)
|
r = self._req.post(f"{self.url}/bot/update", json=data)
|
||||||
|
r.raise_for_status()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
def _bot_update(self, data: Dict[str, Any]) -> Optional[Response]:
|
||||||
|
if not self.__registered:
|
||||||
|
self.do_init()
|
||||||
|
if self.allowed:
|
||||||
|
try:
|
||||||
|
return self.__bot_update(data)
|
||||||
|
except HTTPError:
|
||||||
|
self.__to_update.append(data)
|
||||||
|
else:
|
||||||
|
self.__to_update.append(data)
|
||||||
|
|
||||||
def register_account(self):
|
def register_account(self):
|
||||||
if not self.__registered:
|
if not self.__registered:
|
||||||
try:
|
r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id))
|
||||||
r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id))
|
if r:
|
||||||
if not r.json().get('status'):
|
if not r.json().get('status'):
|
||||||
self._req.post(f"{self.url}/bot/register", json=dict(name=self.name, email=self.email,
|
self._req.post(f"{self.url}/bot/register", json=dict(name=self.name, email=self.email,
|
||||||
player_id=self.citizen_id))
|
player_id=self.citizen_id))
|
||||||
finally:
|
|
||||||
self.__registered = True
|
self.__registered = True
|
||||||
self.allowed = True
|
self.allowed = True
|
||||||
self.report_action('STARTED', value=utils.now().strftime("%F %T"))
|
self.report_action('STARTED', value=utils.now().strftime("%F %T"))
|
||||||
@ -629,9 +665,7 @@ class Reporter:
|
|||||||
xp=xp, cc=cc, gold=gold, inv_total=inv_total, inv_free=inv_total - inv, inv=inv, food=food,
|
xp=xp, cc=cc, gold=gold, inv_total=inv_total, inv_free=inv_total - inv, inv=inv, food=food,
|
||||||
pp=pp, hp_limit=hp_limit, hp_interval=hp_interval, hp_available=hp_available,
|
pp=pp, hp_limit=hp_limit, hp_interval=hp_interval, hp_available=hp_available,
|
||||||
))
|
))
|
||||||
|
self._bot_update(data)
|
||||||
if self.allowed:
|
|
||||||
self.__bot_update(data)
|
|
||||||
|
|
||||||
def report_action(self, action: str, json_val: Dict[Any, Any] = None, value: str = None):
|
def report_action(self, action: str, json_val: Dict[Any, Any] = None, value: str = None):
|
||||||
json_data = dict(
|
json_data = dict(
|
||||||
@ -643,10 +677,7 @@ class Reporter:
|
|||||||
json_data['log'].update(dict(value=value))
|
json_data['log'].update(dict(value=value))
|
||||||
if not any([self.key, self.email, self.name, self.citizen_id]):
|
if not any([self.key, self.email, self.name, self.citizen_id]):
|
||||||
return
|
return
|
||||||
if self.allowed:
|
self._bot_update(json_data)
|
||||||
self.__bot_update(json_data)
|
|
||||||
else:
|
|
||||||
self.__to_update.append(json_data)
|
|
||||||
|
|
||||||
def report_fighting(self, battle: 'Battle', invader: bool, division: 'BattleDivision', damage: float, hits: int):
|
def report_fighting(self, battle: 'Battle', invader: bool, division: 'BattleDivision', damage: float, hits: int):
|
||||||
side = battle.invader if invader else battle.defender
|
side = battle.invader if invader else battle.defender
|
||||||
@ -687,33 +718,6 @@ class Reporter:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
class ErepublikJSONEncoder(utils.json.JSONEncoder):
|
|
||||||
def default(self, o):
|
|
||||||
from erepublik.citizen import Citizen
|
|
||||||
if isinstance(o, Decimal):
|
|
||||||
return float(f"{o:.02f}")
|
|
||||||
elif isinstance(o, datetime.datetime):
|
|
||||||
return dict(__type__='datetime', date=o.strftime("%Y-%m-%d"), time=o.strftime("%H:%M:%S"),
|
|
||||||
tzinfo=str(o.tzinfo) if o.tzinfo else None)
|
|
||||||
elif isinstance(o, datetime.date):
|
|
||||||
return dict(__type__='date', date=o.strftime("%Y-%m-%d"))
|
|
||||||
elif isinstance(o, datetime.timedelta):
|
|
||||||
return dict(__type__='timedelta', days=o.days, seconds=o.seconds,
|
|
||||||
microseconds=o.microseconds, total_seconds=o.total_seconds())
|
|
||||||
elif isinstance(o, Response):
|
|
||||||
return dict(headers=dict(o.__dict__['headers']), url=o.url, text=o.text, status_code=o.status_code)
|
|
||||||
elif hasattr(o, 'as_dict'):
|
|
||||||
return o.as_dict
|
|
||||||
elif isinstance(o, set):
|
|
||||||
return list(o)
|
|
||||||
elif isinstance(o, Citizen):
|
|
||||||
return o.to_json()
|
|
||||||
try:
|
|
||||||
return super().default(o)
|
|
||||||
except Exception as e: # noqa
|
|
||||||
return 'Object is not JSON serializable'
|
|
||||||
|
|
||||||
|
|
||||||
class BattleSide:
|
class BattleSide:
|
||||||
points: int
|
points: int
|
||||||
deployed: List[constants.Country]
|
deployed: List[constants.Country]
|
||||||
@ -985,7 +989,7 @@ class TelegramReporter:
|
|||||||
if token is None:
|
if token is None:
|
||||||
token = "864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o"
|
token = "864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o"
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.api_url = f"https://api.telegram.org/bot{token}/sendMessage"
|
self.api_url = f"https://api.telegram.org/bot{token}"
|
||||||
self.player_name = player_name or ""
|
self.player_name = player_name or ""
|
||||||
self.__initialized = True
|
self.__initialized = True
|
||||||
self._last_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-5))
|
self._last_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-5))
|
||||||
@ -1042,13 +1046,21 @@ class TelegramReporter:
|
|||||||
message = "\n\n".join(self.__queue)
|
message = "\n\n".join(self.__queue)
|
||||||
if self.player_name:
|
if self.player_name:
|
||||||
message = f"Player *{self.player_name}*\n\n" + message
|
message = f"Player *{self.player_name}*\n\n" + message
|
||||||
response = post(self.api_url, json=dict(chat_id=self.chat_id, text=message, parse_mode='Markdown'))
|
response = post(f"{self.api_url}/sendMessage", json=dict(chat_id=self.chat_id, text=message, parse_mode='Markdown'))
|
||||||
self._last_time = utils.now()
|
self._last_time = utils.now()
|
||||||
if response.json().get('ok'):
|
if response.json().get('ok'):
|
||||||
self.__queue.clear()
|
self.__queue.clear()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def send_photos(self, photos: List[Tuple[str, BytesIO]]):
|
||||||
|
for photo_title, photo in photos:
|
||||||
|
photo.seek(0)
|
||||||
|
post(f"https://{self.api_url}/sendPhoto",
|
||||||
|
data=dict(chat_id=self.chat_id, caption=photo_title),
|
||||||
|
files=[('photo', ("f{utils.slugify(photo_title)}.png", photo))])
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class OfferItem(NamedTuple):
|
class OfferItem(NamedTuple):
|
||||||
price: float = 999_999_999.
|
price: float = 999_999_999.
|
||||||
|
@ -81,24 +81,94 @@ class Industries:
|
|||||||
return dict(by_id=self.__by_id, by_name=self.__by_name)
|
return dict(by_id=self.__by_id, by_name=self.__by_name)
|
||||||
|
|
||||||
|
|
||||||
AIR_RANKS: Dict[int, str] = {
|
class Rank:
|
||||||
1: 'Airman', 2: 'Airman 1st Class', 3: 'Airman 1st Class*', 4: 'Airman 1st Class**', 5: 'Airman 1st Class***',
|
id: int
|
||||||
6: 'Airman 1st Class****', 7: 'Airman 1st Class*****', 8: 'Senior Airman', 9: 'Senior Airman*',
|
name: str
|
||||||
10: 'Senior Airman**', 11: 'Senior Airman***', 12: 'Senior Airman****', 13: 'Senior Airman*****',
|
rank_points: int
|
||||||
14: 'Staff Sergeant', 15: 'Staff Sergeant*', 16: 'Staff Sergeant**', 17: 'Staff Sergeant***',
|
is_air: bool
|
||||||
18: 'Staff Sergeant****', 19: 'Staff Sergeant*****', 20: 'Aviator', 21: 'Aviator*', 22: 'Aviator**',
|
|
||||||
23: 'Aviator***', 24: 'Aviator****', 25: 'Aviator*****', 26: 'Flight Lieutenant', 27: 'Flight Lieutenant*',
|
def __init__(self, id: int, name: str, rank_points: int, is_air: bool = False):
|
||||||
28: 'Flight Lieutenant**', 29: 'Flight Lieutenant***', 30: 'Flight Lieutenant****', 31: 'Flight Lieutenant*****',
|
self.id = id
|
||||||
32: 'Squadron Leader', 33: 'Squadron Leader*', 34: 'Squadron Leader**', 35: 'Squadron Leader***',
|
self.name = name
|
||||||
36: 'Squadron Leader****', 37: 'Squadron Leader*****', 38: 'Chief Master Sergeant', 39: 'Chief Master Sergeant*',
|
self.rank_points = rank_points
|
||||||
40: 'Chief Master Sergeant**', 41: 'Chief Master Sergeant***', 42: 'Chief Master Sergeant****',
|
self.is_air = bool(is_air)
|
||||||
43: 'Chief Master Sergeant*****', 44: 'Wing Commander', 45: 'Wing Commander*', 46: 'Wing Commander**',
|
|
||||||
47: 'Wing Commander***', 48: 'Wing Commander****', 49: 'Wing Commander*****', 50: 'Group Captain',
|
def __int__(self):
|
||||||
51: 'Group Captain*', 52: 'Group Captain**', 53: 'Group Captain***', 54: 'Group Captain****',
|
return self.id
|
||||||
55: 'Group Captain*****', 56: 'Air Commodore', 57: 'Air Commodore*', 58: 'Air Commodore**', 59: 'Air Commodore***',
|
|
||||||
60: 'Air Commodore****', 61: 'Air Commodore*****',
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, Rank):
|
||||||
|
return self.id == other.id if other.is_air == self.is_air else False
|
||||||
|
else:
|
||||||
|
return self.id == int(other)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if isinstance(other, Rank):
|
||||||
|
return not self.id == other.id if other.is_air == self.is_air else True
|
||||||
|
else:
|
||||||
|
return not self.id == int(other)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, Rank):
|
||||||
|
return self.id < other.id if other.is_air == self.is_air else False
|
||||||
|
else:
|
||||||
|
return self.id < int(other)
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
if isinstance(other, Rank):
|
||||||
|
return self.id <= other.id if other.is_air == self.is_air else False
|
||||||
|
else:
|
||||||
|
return self.id <= int(other)
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if isinstance(other, Rank):
|
||||||
|
return self.id > other.id if other.is_air == self.is_air else False
|
||||||
|
else:
|
||||||
|
return self.id > int(other)
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
if isinstance(other, Rank):
|
||||||
|
return self.id >= other.id if other.is_air == self.is_air else False
|
||||||
|
else:
|
||||||
|
return self.id >= int(other)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_dict(self):
|
||||||
|
return dict(id=self.id, name=self.name, rank_points=self.rank_points, is_air=self.is_air)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{'Air' if self.is_air else 'Ground'}Rank<#{self.id} {self.name}>"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
|
AIR_RANK_NAMES: Dict[int, str] = {
|
||||||
|
1: 'Airman', 2: 'Airman 1st Class', 3: 'Airman 1st Class*', 4: 'Airman 1st Class**', 5: 'Airman 1st Class***', 6: 'Airman 1st Class****', 7: 'Airman 1st Class*****',
|
||||||
|
8: 'Senior Airman', 9: 'Senior Airman*', 10: 'Senior Airman**', 11: 'Senior Airman***', 12: 'Senior Airman****', 13: 'Senior Airman*****',
|
||||||
|
14: 'Staff Sergeant', 15: 'Staff Sergeant*', 16: 'Staff Sergeant**', 17: 'Staff Sergeant***', 18: 'Staff Sergeant****', 19: 'Staff Sergeant*****',
|
||||||
|
20: 'Aviator', 21: 'Aviator*', 22: 'Aviator**', 23: 'Aviator***', 24: 'Aviator****', 25: 'Aviator*****',
|
||||||
|
26: 'Flight Lieutenant', 27: 'Flight Lieutenant*', 28: 'Flight Lieutenant**', 29: 'Flight Lieutenant***', 30: 'Flight Lieutenant****', 31: 'Flight Lieutenant*****',
|
||||||
|
32: 'Squadron Leader', 33: 'Squadron Leader*', 34: 'Squadron Leader**', 35: 'Squadron Leader***', 36: 'Squadron Leader****', 37: 'Squadron Leader*****',
|
||||||
|
38: 'Chief Master Sergeant', 39: 'Chief Master Sergeant*', 40: 'Chief Master Sergeant**', 41: 'Chief Master Sergeant***', 42: 'Chief Master Sergeant****', 43: 'Chief Master Sergeant*****',
|
||||||
|
44: 'Wing Commander', 45: 'Wing Commander*', 46: 'Wing Commander**', 47: 'Wing Commander***', 48: 'Wing Commander****', 49: 'Wing Commander*****',
|
||||||
|
50: 'Group Captain', 51: 'Group Captain*', 52: 'Group Captain**', 53: 'Group Captain***', 54: 'Group Captain****', 55: 'Group Captain*****',
|
||||||
|
56: 'Air Commodore', 57: 'Air Commodore*', 58: 'Air Commodore**', 59: 'Air Commodore***', 60: 'Air Commodore****', 61: 'Air Commodore*****',
|
||||||
|
62: 'Air Vice Marshal', 63: 'Air Vice Marshal*', 64: 'Air Vice Marshal**', 65: 'Air Vice Marshal***', 66: 'Air Vice Marshal****', 67: 'Air Vice Marshal*****',
|
||||||
|
68: 'Air Marshal', 69: 'Air Marshal*', 70: 'Air Marshal**', 71: 'Air Marshal***', 72: 'Air Marshal****', 73: 'Air Marshal*****',
|
||||||
|
74: 'Air Chief Marshal', 75: 'Air Chief Marshal*', 76: 'Air Chief Marshal**', 77: 'Air Chief Marshal***', 78: 'Air Chief Marshal****', 79: 'Air Chief Marshal*****',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AIR_RANK_POINTS: Dict[int, Optional[int]] = {
|
||||||
|
1: 0, 2: 10, 3: 25, 4: 45, 5: 70, 6: 100, 7: 140, 8: 190, 9: 270, 10: 380, 11: 530, 12: 850, 13: 1300, 14: 2340, 15: 3300, 16: 4200, 17: 5150, 18: 6100, 19: 7020, 20: 9100, 21: 12750, 22: 16400, 23: 20000, 24: 23650, 25: 27300,
|
||||||
|
26: 35500, 27: 48000, 28: 60000, 29: 72400, 30: 84500, 31: 97000, 32: 110000, 33: 140000, 34: 170000, 35: 210000, 36: 290000, 37: 350000, 38: 429000, 39: 601000, 40: 772000, 41: 944000, 42: 1115000, 43: 1287000,
|
||||||
|
44: 1673000, 45: 2238000, 46: 2804000, 47: 3369000, 48: 3935000, 49: 4500000, 50: 5020000, 51: 7028000, 52: 9036000, 53: 11044000, 54: 13052000, 55: 15060000,
|
||||||
|
56: 19580000, 57: 27412000, 58: 35244000, 59: 43076000, 60: 50908000, 61: 58740000, 62: 76360000, 63: 113166443, 64: 137448000, 65: None, 66: None, 67: None,
|
||||||
|
68: None, 69: None, 70: None, 71: None, 72: None, 73: None, 74: None, 75: None, 76: None, 77: None, 78: None, 79: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
AIR_RANKS: Dict[int, Rank] = {i: Rank(i, AIR_RANK_NAMES[i], AIR_RANK_POINTS[i], True) for i in range(1, 80)}
|
||||||
|
|
||||||
COUNTRIES: Dict[int, Country] = {
|
COUNTRIES: Dict[int, Country] = {
|
||||||
1: Country(1, 'Romania', 'Romania', 'ROU'), 9: Country(9, 'Brazil', 'Brazil', 'BRA'),
|
1: Country(1, 'Romania', 'Romania', 'ROU'), 9: Country(9, 'Brazil', 'Brazil', 'BRA'),
|
||||||
10: Country(10, 'Italy', 'Italy', 'ITA'), 11: Country(11, 'France', 'France', 'FRA'),
|
10: Country(10, 'Italy', 'Italy', 'ITA'), 11: Country(11, 'France', 'France', 'FRA'),
|
||||||
@ -144,28 +214,18 @@ COUNTRIES: Dict[int, Country] = {
|
|||||||
|
|
||||||
FOOD_ENERGY: Dict[str, int] = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
|
FOOD_ENERGY: Dict[str, int] = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
|
||||||
|
|
||||||
GROUND_RANKS: Dict[int, str] = {
|
GROUND_RANK_NAMES: Dict[int, str] = {
|
||||||
1: 'Recruit', 2: 'Private', 3: 'Private*', 4: 'Private**', 5: 'Private***',
|
1: 'Recruit', 2: 'Private', 3: 'Private*', 4: 'Private**', 5: 'Private***', 6: 'Corporal', 7: 'Corporal*', 8: 'Corporal**', 9: 'Corporal***',
|
||||||
6: 'Corporal', 7: 'Corporal*', 8: 'Corporal**', 9: 'Corporal***',
|
10: 'Sergeant', 11: 'Sergeant*', 12: 'Sergeant**', 13: 'Sergeant***', 14: 'Lieutenant', 15: 'Lieutenant*', 16: 'Lieutenant**', 17: 'Lieutenant***',
|
||||||
10: 'Sergeant', 11: 'Sergeant*', 12: 'Sergeant**', 13: 'Sergeant***',
|
18: 'Captain', 19: 'Captain*', 20: 'Captain**', 21: 'Captain***', 22: 'Major', 23: 'Major*', 24: 'Major**', 25: 'Major***',
|
||||||
14: 'Lieutenant', 15: 'Lieutenant*', 16: 'Lieutenant**', 17: 'Lieutenant***',
|
26: 'Commander', 27: 'Commander*', 28: 'Commander**', 29: 'Commander***', 30: 'Lt Colonel', 31: 'Lt Colonel*', 32: 'Lt Colonel**', 33: 'Lt Colonel***',
|
||||||
18: 'Captain', 19: 'Captain*', 20: 'Captain**', 21: 'Captain***',
|
34: 'Colonel', 35: 'Colonel*', 36: 'Colonel**', 37: 'Colonel***', 38: 'General', 39: 'General*', 40: 'General**', 41: 'General***',
|
||||||
22: 'Major', 23: 'Major*', 24: 'Major**', 25: 'Major***',
|
42: 'Field Marshal', 43: 'Field Marshal*', 44: 'Field Marshal**', 45: 'Field Marshal***', 46: 'Supreme Marshal', 47: 'Supreme Marshal*', 48: 'Supreme Marshal**', 49: 'Supreme Marshal***',
|
||||||
26: 'Commander', 27: 'Commander*', 28: 'Commander**', 29: 'Commander***',
|
50: 'National Force', 51: 'National Force*', 52: 'National Force**', 53: 'National Force***', 54: 'World Class Force', 55: 'World Class Force*', 56: 'World Class Force**', 57: 'World Class Force***',
|
||||||
30: 'Lt Colonel', 31: 'Lt Colonel*', 32: 'Lt Colonel**', 33: 'Lt Colonel***',
|
58: 'Legendary Force', 59: 'Legendary Force*', 60: 'Legendary Force**', 61: 'Legendary Force***', 62: 'God of War', 63: 'God of War*', 64: 'God of War**', 65: 'God of War***',
|
||||||
34: 'Colonel', 35: 'Colonel*', 36: 'Colonel**', 37: 'Colonel***',
|
|
||||||
38: 'General', 39: 'General*', 40: 'General**', 41: 'General***',
|
|
||||||
42: 'Field Marshal', 43: 'Field Marshal*', 44: 'Field Marshal**', 45: 'Field Marshal***',
|
|
||||||
46: 'Supreme Marshal', 47: 'Supreme Marshal*', 48: 'Supreme Marshal**', 49: 'Supreme Marshal***',
|
|
||||||
50: 'National Force', 51: 'National Force*', 52: 'National Force**', 53: 'National Force***',
|
|
||||||
54: 'World Class Force', 55: 'World Class Force*', 56: 'World Class Force**', 57: 'World Class Force***',
|
|
||||||
58: 'Legendary Force', 59: 'Legendary Force*', 60: 'Legendary Force**', 61: 'Legendary Force***',
|
|
||||||
62: 'God of War', 63: 'God of War*', 64: 'God of War**', 65: 'God of War***',
|
|
||||||
66: 'Titan', 67: 'Titan*', 68: 'Titan**', 69: 'Titan***',
|
66: 'Titan', 67: 'Titan*', 68: 'Titan**', 69: 'Titan***',
|
||||||
70: 'Legends I', 71: 'Legends II', 72: 'Legends III', 73: 'Legends IV', 74: 'Legends V', 75: 'Legends VI',
|
70: 'Legends I', 71: 'Legends II', 72: 'Legends III', 73: 'Legends IV', 74: 'Legends V', 75: 'Legends VI', 76: 'Legends VII', 77: 'Legends VIII', 78: 'Legends IX', 79: 'Legends X',
|
||||||
76: 'Legends VII', 77: 'Legends VIII', 78: 'Legends IX', 79: 'Legends X', 80: 'Legends XI', 81: 'Legends XII',
|
80: 'Legends XI', 81: 'Legends XII', 82: 'Legends XIII', 83: 'Legends XIV', 84: 'Legends XV', 85: 'Legends XVI', 86: 'Legends XVII', 87: 'Legends XVIII', 88: 'Legends XIX', 89: 'Legends XX'
|
||||||
82: 'Legends XIII', 83: 'Legends XIV', 84: 'Legends XV', 85: 'Legends XVI', 86: 'Legends XVII', 87: 'Legends XVIII',
|
|
||||||
88: 'Legends XIX', 89: 'Legends XX',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GROUND_RANK_POINTS: Dict[int, int] = {
|
GROUND_RANK_POINTS: Dict[int, int] = {
|
||||||
@ -184,6 +244,8 @@ GROUND_RANK_POINTS: Dict[int, int] = {
|
|||||||
86: 180000000000, 87: 190000000000, 88: 200000000000, 89: 210000000000
|
86: 180000000000, 87: 190000000000, 88: 200000000000, 89: 210000000000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GROUND_RANKS: Dict[int, Rank] = {i: Rank(i, GROUND_RANK_NAMES[i], GROUND_RANK_POINTS[i], False) for i in range(1, 90)}
|
||||||
|
|
||||||
INDUSTRIES = Industries()
|
INDUSTRIES = Industries()
|
||||||
|
|
||||||
TERRAINS: Dict[int, str] = {0: 'Standard', 1: 'Industrial', 2: 'Urban', 3: 'Suburbs', 4: 'Airport', 5: 'Plains',
|
TERRAINS: Dict[int, str] = {0: 'Standard', 1: 'Industrial', 2: 'Urban', 3: 'Suburbs', 4: 'Airport', 5: 'Plains',
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import inspect
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import warnings
|
import warnings
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from logging import Logger
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Union
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
import requests
|
import requests
|
||||||
|
from requests import Response
|
||||||
|
|
||||||
from . import __version__, constants
|
from erepublik import __version__, constants
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import json
|
import json
|
||||||
|
|
||||||
__all__ = ['VERSION', 'calculate_hit', 'caught_error', 'date_from_eday', 'eday_from_date', 'deprecation',
|
__all__ = [
|
||||||
'get_air_hit_dmg_value', 'get_file', 'get_ground_hit_dmg_value', 'get_sleep_seconds', 'good_timedelta',
|
'VERSION', 'calculate_hit', 'date_from_eday', 'eday_from_date', 'deprecation', 'get_final_hit_dmg', 'write_file',
|
||||||
'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now',
|
'get_air_hit_dmg_value', 'get_file', 'get_ground_hit_dmg_value', 'get_sleep_seconds', 'good_timedelta', 'slugify',
|
||||||
'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file', 'write_request',
|
'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now', 'silent_sleep',
|
||||||
'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock',
|
'json_decode_object_hook', 'json_load', 'json_loads', 'json_dump', 'json_dumps', 'b64json', 'ErepublikJSONEncoder',
|
||||||
'json_decode_object_hook', 'json_load', 'json_loads']
|
]
|
||||||
|
|
||||||
VERSION: str = __version__
|
VERSION: str = __version__
|
||||||
|
|
||||||
@ -103,27 +103,6 @@ def interactive_sleep(sleep_seconds: int):
|
|||||||
silent_sleep = time.sleep
|
silent_sleep = time.sleep
|
||||||
|
|
||||||
|
|
||||||
def _write_log(msg, timestamp: bool = True, should_print: bool = False):
|
|
||||||
erep_time_now = now()
|
|
||||||
txt = f"[{erep_time_now.strftime('%F %T')}] {msg}" if timestamp else msg
|
|
||||||
if not os.path.isdir('log'):
|
|
||||||
os.mkdir('log')
|
|
||||||
with open(f'log/{erep_time_now.strftime("%F")}.log', 'a', encoding='utf-8') as f:
|
|
||||||
f.write(f'{txt}\n')
|
|
||||||
if should_print:
|
|
||||||
print(txt)
|
|
||||||
|
|
||||||
|
|
||||||
def write_interactive_log(*args, **kwargs):
|
|
||||||
kwargs.pop('should_print', None)
|
|
||||||
_write_log(should_print=True, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def write_silent_log(*args, **kwargs):
|
|
||||||
kwargs.pop('should_print', None)
|
|
||||||
_write_log(should_print=False, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def get_file(filepath: str) -> str:
|
def get_file(filepath: str) -> str:
|
||||||
file = Path(filepath)
|
file = Path(filepath)
|
||||||
if file.exists():
|
if file.exists():
|
||||||
@ -155,89 +134,6 @@ def write_file(filename: str, content: str) -> int:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def write_request(response: requests.Response, is_error: bool = False):
|
|
||||||
from erepublik import Citizen
|
|
||||||
|
|
||||||
# Remove GET args from url name
|
|
||||||
url = response.url
|
|
||||||
last_index = url.index("?") if "?" in url else len(response.url)
|
|
||||||
|
|
||||||
name = slugify(response.url[len(Citizen.url):last_index])
|
|
||||||
html = response.text
|
|
||||||
|
|
||||||
try:
|
|
||||||
json.loads(html)
|
|
||||||
ext = 'json'
|
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
ext = 'html'
|
|
||||||
|
|
||||||
if not is_error:
|
|
||||||
filename = f"debug/requests/{now().strftime('%F_%H-%M-%S')}_{name}.{ext}"
|
|
||||||
write_file(filename, html)
|
|
||||||
else:
|
|
||||||
return dict(name=f"{now().strftime('%F_%H-%M-%S')}_{name}.{ext}", content=html.encode('utf-8'),
|
|
||||||
mimetype="application/json" if ext == 'json' else "text/html")
|
|
||||||
|
|
||||||
|
|
||||||
def send_email(name: str, content: List[Any], player=None, local_vars: Dict[str, Any] = None,
|
|
||||||
promo: bool = False, captcha: bool = False):
|
|
||||||
if local_vars is None:
|
|
||||||
local_vars = {}
|
|
||||||
from erepublik import Citizen
|
|
||||||
|
|
||||||
file_content_template = '<html><head><title>{title}</title></head><body>{body}</body></html>'
|
|
||||||
if isinstance(player, Citizen) and player.r:
|
|
||||||
resp = write_request(player.r, is_error=True)
|
|
||||||
else:
|
|
||||||
resp = dict(name='None.html', mimetype='text/html',
|
|
||||||
content=file_content_template.format(body='<br/>'.join(content), title='Error'))
|
|
||||||
|
|
||||||
if promo:
|
|
||||||
resp = dict(name=f"{name}.html", mimetype='text/html',
|
|
||||||
content=file_content_template.format(title='Promo', body='<br/>'.join(content)))
|
|
||||||
subject = f"[eBot][{now().strftime('%F %T')}] Promos: {name}"
|
|
||||||
|
|
||||||
elif captcha:
|
|
||||||
resp = dict(name=f'{name}.html', mimetype='text/html',
|
|
||||||
content=file_content_template.format(title='ReCaptcha', body='<br/>'.join(content)))
|
|
||||||
subject = f"[eBot][{now().strftime('%F %T')}] RECAPTCHA: {name}"
|
|
||||||
else:
|
|
||||||
subject = f"[eBot][{now().strftime('%F %T')}] Bug trace: {name}"
|
|
||||||
|
|
||||||
body = "".join(traceback.format_stack()) + \
|
|
||||||
"\n\n" + \
|
|
||||||
"\n".join(content)
|
|
||||||
data = dict(send_mail=True, subject=subject, bugtrace=body)
|
|
||||||
if promo:
|
|
||||||
data.update(promo=True)
|
|
||||||
elif captcha:
|
|
||||||
data.update(captcha=True)
|
|
||||||
else:
|
|
||||||
data.update(bug=True)
|
|
||||||
|
|
||||||
files = [('file', (resp.get('name'), resp.get('content'), resp.get('mimetype'))), ]
|
|
||||||
filename = f'log/{now().strftime("%F")}.log'
|
|
||||||
if os.path.isfile(filename):
|
|
||||||
files.append(('file', (filename[4:], open(filename, 'rb'), 'text/plain')))
|
|
||||||
if local_vars:
|
|
||||||
if 'state_thread' in local_vars:
|
|
||||||
local_vars.pop('state_thread', None)
|
|
||||||
|
|
||||||
if isinstance(local_vars.get('self'), Citizen):
|
|
||||||
local_vars['self'] = repr(local_vars['self'])
|
|
||||||
if isinstance(local_vars.get('player'), Citizen):
|
|
||||||
local_vars['player'] = repr(local_vars['player'])
|
|
||||||
if isinstance(local_vars.get('citizen'), Citizen):
|
|
||||||
local_vars['citizen'] = repr(local_vars['citizen'])
|
|
||||||
|
|
||||||
from erepublik.classes import ErepublikJSONEncoder
|
|
||||||
files.append(('file', ('local_vars.json', json.dumps(local_vars, cls=ErepublikJSONEncoder),
|
|
||||||
"application/json")))
|
|
||||||
if isinstance(player, Citizen):
|
|
||||||
files.append(('file', ('instance.json', player.to_json(indent=True), "application/json")))
|
|
||||||
requests.post('https://pasts.72.lv', data=data, files=files)
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_html_json(js: str) -> str:
|
def normalize_html_json(js: str) -> str:
|
||||||
js = re.sub(r' \'(.*?)\'', lambda a: f'"{a.group(1)}"', js)
|
js = re.sub(r' \'(.*?)\'', lambda a: f'"{a.group(1)}"', js)
|
||||||
js = re.sub(r'(\d\d):(\d\d):(\d\d)', r'\1\2\3', js)
|
js = re.sub(r'(\d\d):(\d\d):(\d\d)', r'\1\2\3', js)
|
||||||
@ -246,72 +142,6 @@ def normalize_html_json(js: str) -> str:
|
|||||||
return js
|
return js
|
||||||
|
|
||||||
|
|
||||||
def caught_error(e: Exception):
|
|
||||||
process_error(str(e), 'Unclassified', sys.exc_info(), interactive=False)
|
|
||||||
|
|
||||||
|
|
||||||
def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None,
|
|
||||||
interactive: bool = None):
|
|
||||||
"""
|
|
||||||
Process error logging and email sending to developer
|
|
||||||
:param interactive: Should print interactively
|
|
||||||
:type interactive: bool
|
|
||||||
:param log_info: String to be written in output
|
|
||||||
:type log_info: str
|
|
||||||
:param name: String Instance name
|
|
||||||
:type name: str
|
|
||||||
:param exc_info: tuple output from sys.exc_info()
|
|
||||||
:type exc_info: tuple
|
|
||||||
:param citizen: Citizen instance
|
|
||||||
:type citizen: Citizen
|
|
||||||
:param commit_id: Caller's code version's commit id
|
|
||||||
:type commit_id: str
|
|
||||||
"""
|
|
||||||
type_, value_, traceback_ = exc_info
|
|
||||||
content = [log_info]
|
|
||||||
content += [f"eRepublik version {VERSION}"]
|
|
||||||
if commit_id:
|
|
||||||
content += [f"Commit id {commit_id}"]
|
|
||||||
content += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))]
|
|
||||||
|
|
||||||
if interactive:
|
|
||||||
write_interactive_log(log_info)
|
|
||||||
elif interactive is not None:
|
|
||||||
write_silent_log(log_info)
|
|
||||||
trace = inspect.trace()
|
|
||||||
if trace:
|
|
||||||
local_vars = trace[-1][0].f_locals
|
|
||||||
if local_vars.get('__name__') == '__main__':
|
|
||||||
local_vars.update(commit_id=local_vars.get('COMMIT_ID'), interactive=local_vars.get('INTERACTIVE'),
|
|
||||||
version=local_vars.get('__version__'), config=local_vars.get('CONFIG'))
|
|
||||||
else:
|
|
||||||
local_vars = dict()
|
|
||||||
send_email(name, content, citizen, local_vars=local_vars)
|
|
||||||
|
|
||||||
|
|
||||||
def process_warning(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None):
|
|
||||||
"""
|
|
||||||
Process error logging and email sending to developer
|
|
||||||
:param log_info: String to be written in output
|
|
||||||
:param name: String Instance name
|
|
||||||
:param exc_info: tuple output from sys.exc_info()
|
|
||||||
:param citizen: Citizen instance
|
|
||||||
:param commit_id: Code's version by commit id
|
|
||||||
"""
|
|
||||||
type_, value_, traceback_ = exc_info
|
|
||||||
content = [log_info]
|
|
||||||
if commit_id:
|
|
||||||
content += [f'Commit id: {commit_id}']
|
|
||||||
content += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))]
|
|
||||||
|
|
||||||
trace = inspect.trace()
|
|
||||||
if trace:
|
|
||||||
local_vars = trace[-1][0].f_locals
|
|
||||||
else:
|
|
||||||
local_vars = dict()
|
|
||||||
send_email(name, content, citizen, local_vars=local_vars)
|
|
||||||
|
|
||||||
|
|
||||||
def slugify(value, allow_unicode=False) -> str:
|
def slugify(value, allow_unicode=False) -> str:
|
||||||
"""
|
"""
|
||||||
Function copied from Django2.2.1 django.utils.text.slugify
|
Function copied from Django2.2.1 django.utils.text.slugify
|
||||||
@ -378,27 +208,6 @@ def deprecation(message):
|
|||||||
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
||||||
|
|
||||||
|
|
||||||
def wait_for_lock(function):
|
|
||||||
def wrapper(instance, *args, **kwargs):
|
|
||||||
if not instance.concurrency_available.wait(600):
|
|
||||||
e = 'Concurrency not freed in 10min!'
|
|
||||||
instance.write_log(e)
|
|
||||||
if instance.debug:
|
|
||||||
instance.report_error(e)
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
instance.concurrency_available.clear()
|
|
||||||
try:
|
|
||||||
ret = function(instance, *args, **kwargs)
|
|
||||||
except Exception as e:
|
|
||||||
instance.concurrency_available.set()
|
|
||||||
raise e
|
|
||||||
instance.concurrency_available.set()
|
|
||||||
return ret
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def json_decode_object_hook(
|
def json_decode_object_hook(
|
||||||
o: Union[Dict[str, Any], List[Any], int, float, str]
|
o: Union[Dict[str, Any], List[Any], int, float, str]
|
||||||
) -> Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta]:
|
) -> Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta]:
|
||||||
@ -432,9 +241,21 @@ def json_loads(s: str, **kwargs):
|
|||||||
return json.loads(s, **kwargs)
|
return json.loads(s, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def json_dump(obj, fp, *args, **kwargs):
|
||||||
|
if not kwargs.get('cls'):
|
||||||
|
kwargs.update(cls=ErepublikJSONEncoder)
|
||||||
|
return json.dump(obj, fp, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def json_dumps(obj, *args, **kwargs):
|
||||||
|
if not kwargs.get('cls'):
|
||||||
|
kwargs.update(cls=ErepublikJSONEncoder)
|
||||||
|
return json.dumps(obj, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def b64json(obj: Union[Dict[str, Union[int, List[str]]], List[str]]):
|
def b64json(obj: Union[Dict[str, Union[int, List[str]]], List[str]]):
|
||||||
if isinstance(obj, list):
|
if isinstance(obj, list):
|
||||||
return b64encode(json.dumps(obj).encode('utf-8')).decode('utf-8')
|
return b64encode(json.dumps(obj, separators=(',', ':')).encode('utf-8')).decode('utf-8')
|
||||||
elif isinstance(obj, (int, str)):
|
elif isinstance(obj, (int, str)):
|
||||||
return obj
|
return obj
|
||||||
elif isinstance(obj, dict):
|
elif isinstance(obj, dict):
|
||||||
@ -443,4 +264,36 @@ def b64json(obj: Union[Dict[str, Union[int, List[str]]], List[str]]):
|
|||||||
else:
|
else:
|
||||||
from .classes import ErepublikException
|
from .classes import ErepublikException
|
||||||
raise ErepublikException(f'Unhandled object type! obj is {type(obj)}')
|
raise ErepublikException(f'Unhandled object type! obj is {type(obj)}')
|
||||||
return b64encode(json.dumps(obj).encode('utf-8')).decode('utf-8')
|
return b64encode(json.dumps(obj, separators=(',', ':')).encode('utf-8')).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
class ErepublikJSONEncoder(json.JSONEncoder):
|
||||||
|
def default(self, o):
|
||||||
|
try:
|
||||||
|
from erepublik.citizen import Citizen
|
||||||
|
if isinstance(o, Decimal):
|
||||||
|
return float(f"{o:.02f}")
|
||||||
|
elif isinstance(o, datetime.datetime):
|
||||||
|
return dict(__type__='datetime', date=o.strftime("%Y-%m-%d"), time=o.strftime("%H:%M:%S"),
|
||||||
|
tzinfo=str(o.tzinfo) if o.tzinfo else None)
|
||||||
|
elif isinstance(o, datetime.date):
|
||||||
|
return dict(__type__='date', date=o.strftime("%Y-%m-%d"))
|
||||||
|
elif isinstance(o, datetime.timedelta):
|
||||||
|
return dict(__type__='timedelta', days=o.days, seconds=o.seconds,
|
||||||
|
microseconds=o.microseconds, total_seconds=o.total_seconds())
|
||||||
|
elif isinstance(o, Response):
|
||||||
|
return dict(headers=dict(o.__dict__['headers']), url=o.url, text=o.text, status_code=o.status_code)
|
||||||
|
elif hasattr(o, 'as_dict'):
|
||||||
|
return o.as_dict
|
||||||
|
elif isinstance(o, set):
|
||||||
|
return list(o)
|
||||||
|
elif isinstance(o, Citizen):
|
||||||
|
return o.to_json()
|
||||||
|
elif isinstance(o, Logger):
|
||||||
|
return str(o)
|
||||||
|
elif hasattr(o, '__dict__'):
|
||||||
|
return o.__dict__
|
||||||
|
else:
|
||||||
|
return super().default(o)
|
||||||
|
except Exception as e: # noqa
|
||||||
|
return str(e)
|
||||||
|
@ -26,9 +26,7 @@ def main():
|
|||||||
player.login()
|
player.login()
|
||||||
now = player.now.replace(second=0, microsecond=0)
|
now = player.now.replace(second=0, microsecond=0)
|
||||||
dt_max = constants.max_datetime
|
dt_max = constants.max_datetime
|
||||||
tasks = {
|
tasks = {}
|
||||||
'eat': now,
|
|
||||||
}
|
|
||||||
if player.config.work:
|
if player.config.work:
|
||||||
tasks.update({'work': now})
|
tasks.update({'work': now})
|
||||||
if player.config.train:
|
if player.config.train:
|
||||||
@ -61,7 +59,6 @@ def main():
|
|||||||
if tasks.get('wam', dt_max) <= now:
|
if tasks.get('wam', dt_max) <= now:
|
||||||
player.write_log("Doing task: Work as manager")
|
player.write_log("Doing task: Work as manager")
|
||||||
success = player.work_as_manager()
|
success = player.work_as_manager()
|
||||||
player.eat()
|
|
||||||
if success:
|
if success:
|
||||||
next_time = utils.good_timedelta(now.replace(hour=14, minute=0, second=0, microsecond=0),
|
next_time = utils.good_timedelta(now.replace(hour=14, minute=0, second=0, microsecond=0),
|
||||||
timedelta(days=1))
|
timedelta(days=1))
|
||||||
@ -70,19 +67,8 @@ def main():
|
|||||||
|
|
||||||
tasks.update({'wam': next_time})
|
tasks.update({'wam': next_time})
|
||||||
|
|
||||||
if tasks.get('eat', dt_max) <= now:
|
|
||||||
player.write_log("Doing task: eat")
|
|
||||||
player.eat()
|
|
||||||
|
|
||||||
if player.energy.food_fights > player.energy.limit // 10:
|
|
||||||
next_minutes = 12
|
|
||||||
else:
|
|
||||||
next_minutes = (player.energy.limit - 5 * player.energy.interval) // player.energy.interval * 6
|
|
||||||
|
|
||||||
next_time = player.energy.reference_time + timedelta(minutes=next_minutes)
|
|
||||||
tasks.update({'eat': next_time})
|
|
||||||
|
|
||||||
if tasks.get('ot', dt_max) <= now:
|
if tasks.get('ot', dt_max) <= now:
|
||||||
|
player.update_job_info()
|
||||||
player.write_log("Doing task: work overtime")
|
player.write_log("Doing task: work overtime")
|
||||||
if now > player.my_companies.next_ot_time:
|
if now > player.my_companies.next_ot_time:
|
||||||
player.work_ot()
|
player.work_ot()
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
bump2version==1.0.1
|
bump2version==1.0.1
|
||||||
coverage==5.4
|
coverage==5.5
|
||||||
edx-sphinx-theme==1.6.1
|
edx-sphinx-theme==3.0.0
|
||||||
flake8==3.8.4
|
flake8==3.9.2
|
||||||
ipython>=7.19.0
|
ipython>=7.25.0
|
||||||
jedi!=0.18.0
|
jedi!=0.18.0
|
||||||
isort==5.7.0
|
isort==5.9.2
|
||||||
pip==21.0
|
pip==21.1.3
|
||||||
pre-commit==2.9.3
|
pre-commit==2.13.0
|
||||||
pur==5.3.0
|
pur==5.4.2
|
||||||
PyInstaller==4.2
|
PyInstaller==4.3
|
||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
pytest==6.2.2
|
pytest==6.2.4
|
||||||
pytz>=2020.5
|
pytz==2021.1
|
||||||
requests>=2.25.1
|
requests==2.25.1
|
||||||
responses==0.12.1
|
requests-toolbelt==0.9.1
|
||||||
setuptools==52.0.0
|
responses==0.13.3
|
||||||
Sphinx==3.4.3
|
setuptools==57.1.0
|
||||||
twine==3.3.0
|
Sphinx==4.0.3
|
||||||
|
twine==3.4.1
|
||||||
wheel==0.36.2
|
wheel==0.36.2
|
||||||
|
10
setup.cfg
10
setup.cfg
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.24.0.3
|
current_version = 0.25.0.3
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
|
||||||
@ -19,15 +19,15 @@ universal = 1
|
|||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = docs,.git,log,debug,venv
|
exclude = docs,.git,log,debug,venv
|
||||||
max-line-length = 120
|
max-line-length = 240
|
||||||
ignore = D100,D101,D102,D103
|
ignore = D100,D101,D102,D103
|
||||||
|
|
||||||
[pycodestyle]
|
[pycodestyle]
|
||||||
max-line-length = 120
|
max-line-length = 240
|
||||||
exclude = .git,log,debug,venv, build
|
exclude = .git,log,debug,venv, build
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
python_version = 3.7
|
python_version = 3.8
|
||||||
check_untyped_defs = True
|
check_untyped_defs = True
|
||||||
ignore_missing_imports = False
|
ignore_missing_imports = False
|
||||||
warn_unused_ignores = True
|
warn_unused_ignores = True
|
||||||
@ -36,4 +36,4 @@ warn_unused_configs = True
|
|||||||
|
|
||||||
[isort]
|
[isort]
|
||||||
multi_line_output = 2
|
multi_line_output = 2
|
||||||
line_length = 120
|
line_length = 240
|
||||||
|
17
setup.py
17
setup.py
@ -12,16 +12,17 @@ with open('HISTORY.rst') as history_file:
|
|||||||
history = history_file.read()
|
history = history_file.read()
|
||||||
|
|
||||||
requirements = [
|
requirements = [
|
||||||
'pytz>=2020.0',
|
'PySocks>=1.7.1',
|
||||||
'requests>=2.24.0,<2.26.0',
|
'pytz>=2021.1',
|
||||||
'PySocks==1.7.1'
|
'requests>=2.25.0',
|
||||||
|
'requests-toolbelt>=0.9.0',
|
||||||
]
|
]
|
||||||
|
|
||||||
setup_requirements = []
|
setup_requirements = []
|
||||||
|
|
||||||
test_requirements = [
|
test_requirements = [
|
||||||
"pytest==6.1.2",
|
"pytest==6.2.4",
|
||||||
"responses==0.12.1"
|
"responses==0.13.3"
|
||||||
]
|
]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
@ -33,8 +34,8 @@ setup(
|
|||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 3.7',
|
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
|
'Programming Language :: Python :: 3.9',
|
||||||
],
|
],
|
||||||
description="Python package for automated eRepublik playing",
|
description="Python package for automated eRepublik playing",
|
||||||
entry_points={},
|
entry_points={},
|
||||||
@ -45,11 +46,11 @@ setup(
|
|||||||
keywords='erepublik',
|
keywords='erepublik',
|
||||||
name='eRepublik',
|
name='eRepublik',
|
||||||
packages=find_packages(include=['erepublik']),
|
packages=find_packages(include=['erepublik']),
|
||||||
python_requires='>=3.7, <4',
|
python_requires='>=3.8, <4',
|
||||||
setup_requires=setup_requirements,
|
setup_requires=setup_requirements,
|
||||||
test_suite='tests',
|
test_suite='tests',
|
||||||
tests_require=test_requirements,
|
tests_require=test_requirements,
|
||||||
url='https://github.com/eeriks/erepublik/',
|
url='https://github.com/eeriks/erepublik/',
|
||||||
version='0.24.0.3',
|
version='0.25.0.3',
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user