Compare commits

...

75 Commits

Author SHA1 Message Date
dd6e22af51 Bump version: 0.25.1.3 → 0.25.1.4 2021-07-23 11:14:50 +03:00
df4ed4fceb bugfix 2021-07-23 11:14:27 +03:00
b5973ef815 Bump version: 0.25.1.2 → 0.25.1.3 2021-07-23 10:29:46 +03:00
61df989cb4 Catch connection errors.
Not logged in bugfix.
Minor tweaks.

Black for setup
2021-07-23 10:29:33 +03:00
ade0f9b11a Black formatting
Style config update

Makefile update

Test fix
2021-07-21 14:20:35 +03:00
0b07ee8b8f Bump version: 0.25.1.1 → 0.25.1.2 2021-07-20 14:47:17 +03:00
b8884b4877 Requirement update
Bugfix
2021-07-20 14:05:54 +03:00
5a1f7801a2 Bump version: 0.25.1 → 0.25.1.1 2021-07-20 13:16:55 +03:00
17845f750c Bugfix 2021-07-20 13:14:13 +03:00
883af51197 Bump version: 0.25.0.4 → 0.25.1 2021-07-10 01:32:57 +03:00
814cb5ab87 Programmatic user agent spoofing 2021-07-10 01:29:31 +03:00
3c316bada3 Bump version: 0.25.0.3 → 0.25.0.4 2021-07-10 01:14:30 +03:00
ea03979943 Energy variable bugfix 2021-07-10 01:13:21 +03:00
9aae685b21 Bump version: 0.25.0.2 → 0.25.0.3 2021-07-10 00:47:51 +03:00
cae94d7aa8 requirement update 2021-07-10 00:47:40 +03:00
fae7b0fd37 Bump version: 0.25.0.1 → 0.25.0.2 2021-07-10 00:37:46 +03:00
b7771b4da2 Update 2021-07-10 00:36:22 +03:00
e3a10af101 Bump version: 0.25.0 → 0.25.0.1 2021-05-25 09:54:44 +03:00
33a5bcacf1 Backward compatability bugfix 2021-05-25 09:54:35 +03:00
342ca2e1cc Bump version: 0.24.2.4 → 0.25.0 2021-05-25 09:49:49 +03:00
580240a015 isort 2021-05-25 09:49:27 +03:00
1517103ba3 Remove deprecated eat task from examples 2021-05-25 09:48:08 +03:00
3dac8c5e74 New eating 2021-05-25 09:43:47 +03:00
8cf86fb9d3 Bump version: 0.24.2.3 → 0.24.2.4 2021-04-27 12:21:54 +03:00
cf927df6e6 bugfix 2021-04-27 12:21:50 +03:00
a6f5dbd05f Bump version: 0.24.2.2 → 0.24.2.3 2021-04-27 12:19:22 +03:00
967afa472f Rank.__str__ 2021-04-27 12:18:56 +03:00
a65568cd0c Bump version: 0.24.2.1 → 0.24.2.2 2021-04-27 11:05:57 +03:00
6e45334d99 Rank comparision 2021-04-27 11:05:51 +03:00
936a1010a6 Bump version: 0.24.2 → 0.24.2.1 2021-04-26 13:12:32 +03:00
acc528cb1d bugfix 2021-04-26 13:12:28 +03:00
614d273104 Bump version: 0.24.1 → 0.24.2 2021-04-23 13:46:51 +03:00
95966764e8 Updated rank constants 2021-04-23 13:45:58 +03:00
f52b078e6a Captcha challange solving simplification 2021-03-05 16:11:52 +02:00
3af27f6512 Cookie dump migration 2021-03-03 13:15:26 +02:00
6276242260 bugfixes 2021-03-03 13:13:19 +02:00
45623de97b Reporter update to queue messages if network error occures 2021-02-06 15:33:08 +02:00
25f932121c Code cleanup and JSONEncoder update to support dumping Logger instances 2021-02-06 15:32:30 +02:00
61be2b1edf Bump version: 0.24.0.5 → 0.24.1 2021-02-06 12:22:37 +02:00
ce9034ad24 Legacy fight support until 2021-02-08 00:00:00 2021-02-06 12:22:23 +02:00
69b2073b74 more fixes 2021-02-06 11:07:23 +02:00
eb048bf9f8 session dump 2021-02-06 11:07:03 +02:00
fe1206dc84 Cookie magick 2021-02-05 13:47:30 +02:00
a2a1ed3dad Cookie magick 2021-02-05 13:37:32 +02:00
39c8f6913e Cookie magick 2021-02-04 20:33:59 +02:00
d7b15b3708 changes 2021-02-04 13:50:24 +02:00
4fe3efa045 fix 2021-02-03 21:01:37 +02:00
14bcb46735 More precisly mimic javascript's JSON.stringify() 2021-02-03 20:13:51 +02:00
b04cc896d8 bugfix 2021-02-03 20:13:15 +02:00
f07062788b LocalVars 2021-02-03 18:45:23 +02:00
4504bdaa97 don't lose image id 2021-02-03 18:15:06 +02:00
ac135614cc LocalVars 2021-02-03 17:27:55 +02:00
41752e1f2e clickMatrix bugfix 2021-02-03 17:08:58 +02:00
a1739e627e refactoring 2021-02-03 16:43:49 +02:00
12ff11deea refactoring 2021-02-03 16:30:52 +02:00
4e3a16b8d4 Don't print stack and exc traces 2021-02-03 16:16:23 +02:00
5f56f59ab8 bugfi 2021-02-03 14:15:32 +02:00
50c66efbda report error update 2021-02-03 14:03:43 +02:00
47b3154c6a Typehints 2021-02-03 13:36:26 +02:00
632e4e8ad2 HttpHandler improvements 2021-02-03 12:02:06 +02:00
7c0d66f126 deploy 2021-02-03 02:03:02 +02:00
842fb64dae deploy 2021-02-03 01:58:18 +02:00
b22349cb1a Disabled oldSchool Shoooooooooot 2021-02-03 01:48:53 +02:00
a9ced91741 deploy 2021-02-03 01:47:50 +02:00
e374562189 Bump version: 0.24.0.4 → 0.24.0.5 2021-02-03 01:09:51 +02:00
e0b64e09b1 bugfix 2021-02-03 01:09:43 +02:00
c51337d249 Bump version: 0.24.0.3 → 0.24.0.4 2021-02-03 01:07:21 +02:00
f4896e0b79 Merge branch 'bugfix-v0.23.4'
# Conflicts:
#	erepublik/__init__.py
#	setup.cfg
#	setup.py
2021-02-03 00:50:03 +02:00
13f5c673ad Migrate Citizen.write_log to self.logger.warning where applicable 2021-02-03 00:48:50 +02:00
e95ffbd505 cleanup 2021-02-03 00:48:13 +02:00
5e638806b5 Switch from manual log creation to Python logging 2021-02-03 00:47:52 +02:00
7860fa3669 Bump version: 0.23.4.15 → 0.23.4.16 2021-02-02 23:57:58 +02:00
e38f603e8b Division switch bugfix for option to switch side 2021-02-02 23:57:54 +02:00
0e1c42a8fb Bump version: 0.23.4.14 → 0.23.4.15 2021-02-02 23:52:38 +02:00
ddc412b348 accesspoint update to change side in RW 2021-02-02 23:52:32 +02:00
18 changed files with 2820 additions and 1860 deletions

View File

@ -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

View File

@ -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

View File

@ -52,12 +52,13 @@ clean-test: ## remove test and coverage artifacts
rm -fr .pytest_cache rm -fr .pytest_cache
lint: ## check style with flake8 lint: ## check style with flake8
black erepublik tests
flake8 erepublik tests flake8 erepublik tests
test: ## run tests quickly with the default Python test: ## run tests quickly with the default Python
python setup.py test python -m unittest
coverage: ## check code coverage quickly with the default Python coverage: lint ## check code coverage quickly with the default Python
coverage run --source erepublik setup.py test coverage run --source erepublik setup.py test
coverage report -m coverage report -m
coverage html coverage html

View File

@ -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
--------------- ---------------

View File

@ -3,10 +3,9 @@
"""Top-level package for eRepublik script.""" """Top-level package for eRepublik script."""
__author__ = """Eriks Karls""" __author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv' __email__ = "eriks@72.lv"
__version__ = '0.24.0.3' __version__ = "0.25.1.4"
from erepublik import classes, constants, utils
from erepublik.citizen import Citizen from erepublik.citizen import Citizen
__all__ = ["classes", "utils", "Citizen", 'constants'] __all__ = ["Citizen", "__version__"]

212
erepublik/_logging.py Normal file
View File

@ -0,0 +1,212 @@
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: # noqa
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)

View File

@ -1,39 +1,21 @@
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.exceptions import ConnectionError
from requests_toolbelt.utils import dump
from . import constants, utils from erepublik import constants, utils
__all__ = ['SlowRequests', 'CitizenAPI'] __all__ = ["SlowRequests", "CitizenAPI"]
class SlowRequests(Session): class SlowRequests(Session):
last_time: datetime.datetime last_time: datetime.datetime
timeout: datetime.timedelta = datetime.timedelta(milliseconds=500) timeout: datetime.timedelta = datetime.timedelta(milliseconds=500)
_uas: List[str] = [
# Chrome
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
# FireFox
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0',
]
debug: bool = False debug: bool = False
def __init__(self, proxies: Dict[str, str] = None, user_agent: str = None): def __init__(self, proxies: Dict[str, str] = None, user_agent: str = None):
@ -41,21 +23,33 @@ class SlowRequests(Session):
if proxies: if proxies:
self.proxies = proxies self.proxies = proxies
if user_agent is None: if user_agent is None:
user_agent = random.choice(self._uas) user_agent = random.choice(self.get_random_user_agent())
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):
return dict(last_time=self.last_time, timeout=self.timeout, cookies=self.cookies.get_dict(), debug=self.debug, return dict(
user_agent=self.headers['User-Agent'], request_log_name=self.request_log_name, proxies=self.proxies) last_time=self.last_time,
timeout=self.timeout,
cookies=self.cookies.get_dict(),
debug=self.debug,
user_agent=self.headers["User-Agent"],
request_log_name=self.request_log_name,
proxies=self.proxies,
)
def request(self, method, url, *args, **kwargs): def request(self, method, url, *args, **kwargs):
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) try:
self._log_response(url, resp) resp = super().request(method, url, *args, **kwargs)
except ConnectionError:
time.sleep(1)
return self.request(method, url, *args, **kwargs)
# self._log_response(resp)
return resp return resp
def _slow_down_requests(self): def _slow_down_requests(self):
@ -68,46 +62,81 @@ class SlowRequests(Session):
def _log_request(self, url, method, data=None, json=None, params=None, **kwargs): def _log_request(self, url, method, data=None, json=None, params=None, **kwargs):
if self.debug: if self.debug:
args = {} args = {}
kwargs.pop('allow_redirects', None) kwargs.pop("allow_redirects", None)
if kwargs: if kwargs:
args.update({'kwargs': kwargs}) args.update({"kwargs": kwargs})
if data: if data:
args.update({'data': data}) args.update({"data": data})
if json: if json:
args.update({'json': json}) args.update({"json": json})
if params: if params:
args.update({'params': params}) args.update({"params": params})
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"))
@staticmethod
def get_random_user_agent() -> str:
windows_x64 = "Windows NT 10.0; Win64; x64"
linux_x64 = "X11; Linux x86_64"
android_11 = "Android 11; Mobile"
android_10 = "Android 10; Mobile"
android_9 = "Android 9; Mobile"
firefox_tmplt = "Mozilla/5.0 ({osystem}; rv:{version}.0) Gecko/20100101 Firefox/{version}.0"
ff_version = range(85, 92)
chrome_tmplt = "Mozilla/5.0 ({osystem}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version} Safari/537.36"
chrome_version = [
"85.0.4183.121",
"86.0.4240.183",
"87.0.4280.141",
"88.0.4324.182",
"89.0.4389.128",
"90.0.4430.18",
"91.0.4472.73",
"92.0.4515.14",
]
uas = []
for osystem in [windows_x64, linux_x64, android_9, android_10, android_11]:
for version in ff_version:
uas.append(firefox_tmplt.format(osystem=osystem, version=version))
for version in chrome_version:
uas.append(chrome_tmplt.format(osystem=osystem, version=version))
return random.choice(uas)
class CitizenBaseAPI: class CitizenBaseAPI:
@ -116,7 +145,7 @@ class CitizenBaseAPI:
token: str token: str
def __init__(self): def __init__(self):
""" Class for unifying eRepublik known endpoints and their required/optional parameters """ """Class for unifying eRepublik known endpoints and their required/optional parameters"""
self._req = SlowRequests() self._req = SlowRequests()
self.token = "" self.token = ""
@ -134,60 +163,90 @@ class CitizenBaseAPI:
return self.get(self.url) return self.get(self.url)
def set_socks_proxy(self, host: str, port: int, username: str = None, password: str = None): def set_socks_proxy(self, host: str, port: int, username: str = None, password: str = None):
url = f'socks5://{username}:{password}@{host}:{port}' if username and password else f'socks5://{host}:{port}' url = f"socks5://{username}:{password}@{host}:{port}" if username and password else f"socks5://{host}:{port}"
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:
return self.get(f'{self.url}/main/sessionCaptcha') return self.get(f"{self.url}/main/sessionCaptcha")
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))
return self.post(f'{self.url}/main/sessionGetChallenge', data=data) if image_id:
data.update(imageId=image_id, isRefresh=True)
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(
return self.post(f'{self.url}/main/sessionUnlock', data=data) sh=hashlib.sha256(",".join(env["l"] + env["s"]).encode("utf8")).hexdigest(),
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):
def _post_main_collect_anniversary_reward(self) -> Response: def _post_main_collect_anniversary_reward(self) -> Response:
return self.post(f"{self.url}/main/collect-anniversary-reward", data={'_token': self.token}) return self.post(f"{self.url}/main/collect-anniversary-reward", data={"_token": self.token})
# 12th anniversary endpoints # 12th anniversary endpoints
def _get_anniversary_quest_data(self) -> Response: def _get_anniversary_quest_data(self) -> Response:
return self.get(f"{self.url}/main/anniversaryQuestData") return self.get(f"{self.url}/main/anniversaryQuestData")
def _post_map_rewards_unlock(self, node_id: int) -> Response: def _post_map_rewards_unlock(self, node_id: int) -> Response:
data = {'nodeId': node_id, '_token': self.token} data = {"nodeId": node_id, "_token": self.token}
return self.post(f"{self.url}/main/map-rewards-unlock", data=data) return self.post(f"{self.url}/main/map-rewards-unlock", data=data)
def _post_map_rewards_speedup(self, node_id: int, currency_amount: int) -> Response: def _post_map_rewards_speedup(self, node_id: int, currency_amount: int) -> Response:
data = {'nodeId': node_id, '_token': self.token, 'currencyCost': currency_amount} data = {"nodeId": node_id, "_token": self.token, "currencyCost": currency_amount}
return self.post(f"{self.url}/main/map-rewards-speedup", data=data) return self.post(f"{self.url}/main/map-rewards-speedup", data=data)
def _post_map_rewards_claim(self, node_id: int, extra: bool = False) -> Response: def _post_map_rewards_claim(self, node_id: int, extra: bool = False) -> Response:
data = {'nodeId': node_id, '_token': self.token} data = {"nodeId": node_id, "_token": self.token}
if extra: if extra:
data['claimExtra'] = 1 data["claimExtra"] = 1
return self.post(f"{self.url}/main/map-rewards-claim", data=data) return self.post(f"{self.url}/main/map-rewards-claim", data=data)
def _post_main_wheel_of_fortune_spin(self, cost) -> Response: def _post_main_wheel_of_fortune_spin(self, cost) -> Response:
return self.post(f"{self.url}/main/wheeloffortune-spin", data={'_token': self.token, '_currentCost': cost}) return self.post(f"{self.url}/main/wheeloffortune-spin", data={"_token": self.token, "_currentCost": cost})
def _post_main_wheel_of_fortune_build(self) -> Response: def _post_main_wheel_of_fortune_build(self) -> Response:
return self.post(f"{self.url}/main/wheeloffortune-build", data={'_token': self.token}) return self.post(f"{self.url}/main/wheeloffortune-build", data={"_token": self.token})
class ErepublikArticleAPI(CitizenBaseAPI): class ErepublikArticleAPI(CitizenBaseAPI):
@ -200,13 +259,13 @@ class ErepublikArticleAPI(CitizenBaseAPI):
def _post_main_article_comments(self, article_id: int, page: int = 1) -> Response: def _post_main_article_comments(self, article_id: int, page: int = 1) -> Response:
data = dict(_token=self.token, articleId=article_id, page=page) data = dict(_token=self.token, articleId=article_id, page=page)
if page: if page:
data.update({'page': page}) data.update({"page": page})
return self.post(f"{self.url}/main/articleComments", data=data) return self.post(f"{self.url}/main/articleComments", data=data)
def _post_main_article_comments_create(self, message: str, article_id: int, parent: int = 0) -> Response: def _post_main_article_comments_create(self, message: str, article_id: int, parent: int = 0) -> Response:
data = dict(_token=self.token, message=message, articleId=article_id) data = dict(_token=self.token, message=message, articleId=article_id)
if parent: if parent:
data.update({'parentId': parent}) data.update({"parentId": parent})
return self.post(f"{self.url}/main/articleComments/create", data=data) return self.post(f"{self.url}/main/articleComments/create", data=data)
def _post_main_donate_article(self, article_id: int, amount: int) -> Response: def _post_main_donate_article(self, article_id: int, amount: int) -> Response:
@ -214,8 +273,7 @@ class ErepublikArticleAPI(CitizenBaseAPI):
return self.post(f"{self.url}/main/donate-article", data=data) return self.post(f"{self.url}/main/donate-article", data=data)
def _post_main_write_article(self, title: str, content: str, country_id: int, kind_id: int) -> Response: def _post_main_write_article(self, title: str, content: str, country_id: int, kind_id: int) -> Response:
data = dict(_token=self.token, article_name=title, article_body=content, article_location=country_id, data = dict(_token=self.token, article_name=title, article_body=content, article_location=country_id, article_category=kind_id)
article_category=kind_id)
return self.post(f"{self.url}/main/write-article", data=data) return self.post(f"{self.url}/main/write-article", data=data)
def _post_main_vote_article(self, article_id: int) -> Response: def _post_main_vote_article(self, article_id: int) -> Response:
@ -225,13 +283,12 @@ class ErepublikArticleAPI(CitizenBaseAPI):
class ErepublikCompanyAPI(CitizenBaseAPI): class ErepublikCompanyAPI(CitizenBaseAPI):
def _post_economy_assign_to_holding(self, factory_id: int, holding_id: int) -> Response: def _post_economy_assign_to_holding(self, factory_id: int, holding_id: int) -> Response:
data = dict(_token=self.token, factoryId=factory_id, action='assign', holdingCompanyId=holding_id) data = dict(_token=self.token, factoryId=factory_id, action="assign", holdingCompanyId=holding_id)
return self.post(f"{self.url}/economy/assign-to-holding", data=data) return self.post(f"{self.url}/economy/assign-to-holding", data=data)
def _post_economy_create_company(self, industry_id: int, building_type: int = 1) -> Response: def _post_economy_create_company(self, industry_id: int, building_type: int = 1) -> Response:
data = {'_token': self.token, "company[industry_id]": industry_id, "company[building_type]": building_type} data = {"_token": self.token, "company[industry_id]": industry_id, "company[building_type]": building_type}
return self.post(f"{self.url}/economy/create-company", data=data, return self.post(f"{self.url}/economy/create-company", data=data, headers={"Referer": f"{self.url}/economy/create-company"})
headers={'Referer': f"{self.url}/economy/create-company"})
def _get_economy_inventory_items(self) -> Response: def _get_economy_inventory_items(self) -> Response:
return self.get(f"{self.url}/economy/inventory-items/") return self.get(f"{self.url}/economy/inventory-items/")
@ -248,39 +305,43 @@ class ErepublikCompanyAPI(CitizenBaseAPI):
data["grounds[%i][id]" % idx] = tg_id data["grounds[%i][id]" % idx] = tg_id
data["grounds[%i][train]" % idx] = 1 data["grounds[%i][train]" % idx] = 1
if data: if data:
data['_token'] = self.token data["_token"] = self.token
return self.post(f"{self.url}/economy/train", data=data) return self.post(f"{self.url}/economy/train", data=data)
def _post_economy_upgrade_company(self, factory: int, level: int, pin: str = None) -> Response: def _post_economy_upgrade_company(self, factory: int, level: int, pin: str = None) -> Response:
data = dict(_token=self.token, type='upgrade', companyId=factory, level=level, pin="" if pin is None else pin) data = dict(_token=self.token, type="upgrade", companyId=factory, level=level, pin="" if pin is None else pin)
return self.post(f"{self.url}/economy/upgrade-company", data=data) return self.post(f"{self.url}/economy/upgrade-company", data=data)
def _post_economy_work(self, action_type: str, wam: List[int] = None, employ: Dict[int, int] = None) -> Response: def _post_economy_work(self, action_type: str, wam: List[int] = None, employ: Dict[int, int] = None) -> Response:
data: Dict[str, Union[int, str]] = dict(action_type=action_type, _token=self.token) data: Dict[str, Union[int, str]] = dict(action_type=action_type, _token=self.token)
if action_type == 'production': if action_type == "production":
if employ is None: if employ is None:
employ = {} employ = {}
if wam is None: if wam is None:
wam = [] wam = []
max_idx = 0 max_idx = 0
for company_id in sorted(wam or []): for company_id in sorted(wam or []):
data.update({ data.update(
f"companies[{max_idx}][id]": company_id, {
f"companies[{max_idx}][employee_works]": employ.pop(company_id, 0), f"companies[{max_idx}][id]": company_id,
f"companies[{max_idx}][own_work]": 1 f"companies[{max_idx}][employee_works]": employ.pop(company_id, 0),
}) f"companies[{max_idx}][own_work]": 1,
}
)
max_idx += 1 max_idx += 1
for company_id in sorted(employ or []): for company_id in sorted(employ or []):
data.update({ data.update(
f"companies[{max_idx}][id]": company_id, {
f"companies[{max_idx}][employee_works]": employ.pop(company_id, 0), f"companies[{max_idx}][id]": company_id,
f"companies[{max_idx}][own_work]": 0 f"companies[{max_idx}][employee_works]": employ.pop(company_id, 0),
}) f"companies[{max_idx}][own_work]": 0,
}
)
max_idx += 1 max_idx += 1
return self.post(f"{self.url}/economy/work", data=data) return self.post(f"{self.url}/economy/work", data=data)
def _post_economy_work_overtime(self) -> Response: def _post_economy_work_overtime(self) -> Response:
data = dict(action_type='workOvertime', _token=self.token) data = dict(action_type="workOvertime", _token=self.token)
return self.post(f"{self.url}/economy/workOvertime", data=data) return self.post(f"{self.url}/economy/workOvertime", data=data)
def _post_economy_job_market_apply(self, citizen_id: int, salary: float) -> Response: def _post_economy_job_market_apply(self, citizen_id: int, salary: float) -> Response:
@ -288,29 +349,28 @@ class ErepublikCompanyAPI(CitizenBaseAPI):
return self.post(f"{self.url}/economy/job-market-apply", data=data) return self.post(f"{self.url}/economy/job-market-apply", data=data)
def _post_economy_resign(self) -> Response: def _post_economy_resign(self) -> Response:
return self.post(f"{self.url}/economy/resign", return self.post(
headers={"Content-Type": "application/x-www-form-urlencoded"}, f"{self.url}/economy/resign",
data={'_token': self.token, 'action_type': 'resign'}) headers={"Content-Type": "application/x-www-form-urlencoded"},
data={"_token": self.token, "action_type": "resign"},
)
def _post_economy_sell_company(self, factory_id: int, pin: int = None, sell: bool = True) -> Response: def _post_economy_sell_company(self, factory_id: int, pin: int = None, sell: bool = True) -> Response:
data = dict(_token=self.token, pin="" if pin is None else pin) data = dict(_token=self.token, pin="" if pin is None else pin)
if sell: if sell:
data.update({'sell': 'sell'}) data.update({"sell": "sell"})
else: else:
data.update({'dissolve': factory_id}) data.update({"dissolve": factory_id})
return self.post(f"{self.url}/economy/sell-company/{factory_id}", return self.post(f"{self.url}/economy/sell-company/{factory_id}", data=data, headers={"Referer": self.url})
data=data, headers={'Referer': self.url})
class ErepublikCountryAPI(CitizenBaseAPI): class ErepublikCountryAPI(CitizenBaseAPI):
def _get_country_military(self, country_name: str) -> Response: def _get_country_military(self, country_name: str) -> Response:
return self.get(f"{self.url}/country/military/{country_name}") return self.get(f"{self.url}/country/military/{country_name}")
def _post_main_country_donate(self, country_id: int, action: str, value: Union[int, float], def _post_main_country_donate(self, country_id: int, action: str, value: Union[int, float], quality: int = None) -> Response:
quality: int = None) -> Response:
data = dict(countryId=country_id, action=action, _token=self.token, value=value, quality=quality) data = dict(countryId=country_id, action=action, _token=self.token, value=value, quality=quality)
return self.post(f"{self.url}/main/country-donate", data=data, return self.post(f"{self.url}/main/country-donate", data=data, headers={"Referer": f"{self.url}/country/economy/Latvia"})
headers={'Referer': f"{self.url}/country/economy/Latvia"})
class ErepublikEconomyAPI(CitizenBaseAPI): class ErepublikEconomyAPI(CitizenBaseAPI):
@ -332,20 +392,20 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
return self.post(f"{self.url}/economy/activateBooster", data=data) return self.post(f"{self.url}/economy/activateBooster", data=data)
def _post_economy_activate_house(self, quality: int) -> Response: def _post_economy_activate_house(self, quality: int) -> Response:
data = dict(action='activate', quality=quality, type='house', _token=self.token) data = dict(action="activate", quality=quality, type="house", _token=self.token)
return self.post(f"{self.url}/economy/activateHouse", data=data) return self.post(f"{self.url}/economy/activateHouse", data=data)
def _post_economy_donate_items_action(self, citizen_id: int, amount: int, industry: int, def _post_economy_donate_items_action(self, citizen_id: int, amount: int, industry: int, quality: int) -> Response:
quality: int) -> Response:
data = dict(citizen_id=citizen_id, amount=amount, industry_id=industry, quality=quality, _token=self.token) data = dict(citizen_id=citizen_id, amount=amount, industry_id=industry, quality=quality, _token=self.token)
return self.post(f"{self.url}/economy/donate-items-action", data=data, return self.post(
headers={'Referer': f"{self.url}/economy/donate-items/{citizen_id}"}) f"{self.url}/economy/donate-items-action", data=data, headers={"Referer": f"{self.url}/economy/donate-items/{citizen_id}"}
)
def _post_economy_donate_money_action(self, citizen_id: int, amount: float = 0.0, def _post_economy_donate_money_action(self, citizen_id: int, amount: float = 0.0, currency: int = 62) -> Response:
currency: int = 62) -> Response:
data = dict(citizen_id=citizen_id, _token=self.token, currency_id=currency, amount=amount) data = dict(citizen_id=citizen_id, _token=self.token, currency_id=currency, amount=amount)
return self.post(f"{self.url}/economy/donate-money-action", data=data, return self.post(
headers={'Referer': f"{self.url}/economy/donate-money/{citizen_id}"}) f"{self.url}/economy/donate-money-action", data=data, headers={"Referer": f"{self.url}/economy/donate-money/{citizen_id}"}
)
def _post_economy_exchange_purchase(self, amount: float, currency: int, offer: int) -> Response: def _post_economy_exchange_purchase(self, amount: float, currency: int, offer: int) -> Response:
data = dict(_token=self.token, amount=amount, currencyId=currency, offerId=offer) data = dict(_token=self.token, amount=amount, currencyId=currency, offerId=offer)
@ -356,46 +416,56 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
return self.post(f"{self.url}/economy/exchange/retrieve/", data=data) return self.post(f"{self.url}/economy/exchange/retrieve/", data=data)
def _post_economy_game_tokens_market(self, action: str) -> Response: def _post_economy_game_tokens_market(self, action: str) -> Response:
assert action in ['retrieve', ] assert action in [
"retrieve",
]
data = dict(_token=self.token, action=action) data = dict(_token=self.token, action=action)
return self.post(f"{self.url}/economy/gameTokensMarketAjax", data=data) return self.post(f"{self.url}/economy/gameTokensMarketAjax", data=data)
def _post_economy_marketplace(self, country: int, industry: int, quality: int, def _post_economy_marketplace(self, country: int, industry: int, quality: int, order_asc: bool = True) -> Response:
order_asc: bool = True) -> Response: data = dict(
data = dict(countryId=country, industryId=industry, quality=quality, ajaxMarket=1, countryId=country,
orderBy='price_asc' if order_asc else 'price_desc', _token=self.token) industryId=industry,
quality=quality,
ajaxMarket=1,
orderBy="price_asc" if order_asc else "price_desc",
_token=self.token,
)
return self.post(f"{self.url}/economy/marketplaceAjax", data=data) return self.post(f"{self.url}/economy/marketplaceAjax", data=data)
def _post_economy_marketplace_actions(self, action: str, **kwargs) -> Response: def _post_economy_marketplace_actions(self, action: str, **kwargs) -> Response:
if action == 'buy': if action == "buy":
data = dict(_token=self.token, offerId=kwargs['offer'], amount=kwargs['amount'], data = dict(
orderBy='price_asc', currentPage=1, buyAction=1) _token=self.token, offerId=kwargs["offer"], amount=kwargs["amount"], orderBy="price_asc", currentPage=1, buyAction=1
elif action == 'sell': )
data = dict(_token=self.token, countryId=kwargs['country_id'], price=kwargs['price'], elif action == "sell":
industryId=kwargs['industry'], quality=kwargs['quality'], amount=kwargs['amount'], data = dict(
sellAction='postOffer') _token=self.token,
elif action == 'delete': countryId=kwargs["country_id"],
data = dict(_token=self.token, offerId=kwargs['offer_id'], sellAction='deleteOffer') price=kwargs["price"],
industryId=kwargs["industry"],
quality=kwargs["quality"],
amount=kwargs["amount"],
sellAction="postOffer",
)
elif action == "delete":
data = dict(_token=self.token, offerId=kwargs["offer_id"], sellAction="deleteOffer")
else: else:
raise ValueError(f"Action '{action}' is not supported! Only 'buy/sell/delete' actions are available") raise ValueError(f"Action '{action}' is not supported! Only 'buy/sell/delete' actions are available")
return self.post(f"{self.url}/economy/marketplaceActions", data=data) return self.post(f"{self.url}/economy/marketplaceActions", data=data)
class ErepublikLeaderBoardAPI(CitizenBaseAPI): class ErepublikLeaderBoardAPI(CitizenBaseAPI):
def _get_main_leaderboards_damage_aircraft_rankings(self, country_id: int, weeks: int = 0, def _get_main_leaderboards_damage_aircraft_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0) -> Response: # noqa
mu_id: int = 0) -> Response: # noqa
return self.get(f"{self.url}/main/leaderboards-damage-aircraft-rankings/{country_id}/{weeks}/{mu_id}/0") return self.get(f"{self.url}/main/leaderboards-damage-aircraft-rankings/{country_id}/{weeks}/{mu_id}/0")
def _get_main_leaderboards_damage_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0, def _get_main_leaderboards_damage_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0, div: int = 0) -> Response: # noqa
div: int = 0) -> Response: # noqa
return self.get(f"{self.url}/main/leaderboards-damage-rankings/{country_id}/{weeks}/{mu_id}/{div}") return self.get(f"{self.url}/main/leaderboards-damage-rankings/{country_id}/{weeks}/{mu_id}/{div}")
def _get_main_leaderboards_kills_aircraft_rankings(self, country_id: int, weeks: int = 0, def _get_main_leaderboards_kills_aircraft_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0) -> Response: # noqa
mu_id: int = 0) -> Response: # noqa
return self.get(f"{self.url}/main/leaderboards-kills-aircraft-rankings/{country_id}/{weeks}/{mu_id}/0") return self.get(f"{self.url}/main/leaderboards-kills-aircraft-rankings/{country_id}/{weeks}/{mu_id}/0")
def _get_main_leaderboards_kills_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0, def _get_main_leaderboards_kills_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0, div: int = 0) -> Response: # noqa
div: int = 0) -> Response: # noqa
return self.get(f"{self.url}/main/leaderboards-kills-rankings/{country_id}/{weeks}/{mu_id}/{div}") return self.get(f"{self.url}/main/leaderboards-kills-rankings/{country_id}/{weeks}/{mu_id}/{div}")
@ -403,7 +473,7 @@ class ErepublikLocationAPI(CitizenBaseAPI):
def _get_main_city_data_residents(self, city_id: int, page: int = 1, params: Mapping[str, Any] = None) -> Response: def _get_main_city_data_residents(self, city_id: int, page: int = 1, params: Mapping[str, Any] = None) -> Response:
if params is None: if params is None:
params = {} params = {}
return self.get(f"{self.url}/main/city-data/{city_id}/residents", params={'currentPage': page, **params}) return self.get(f"{self.url}/main/city-data/{city_id}/residents", params={"currentPage": page, **params})
class ErepublikMilitaryAPI(CitizenBaseAPI): class ErepublikMilitaryAPI(CitizenBaseAPI):
@ -414,7 +484,7 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
return self.get(f"{self.url}/military/battlefield-choose-side/{battle_id}/{side_id}") return self.get(f"{self.url}/military/battlefield-choose-side/{battle_id}/{side_id}")
def _get_military_show_weapons(self, battle_id: int) -> Response: def _get_military_show_weapons(self, battle_id: int) -> Response:
return self.get(f"{self.url}/military/show-weapons", params={'_token': self.token, 'battleId': battle_id}) return self.get(f"{self.url}/military/show-weapons", params={"_token": self.token, "battleId": battle_id})
def _get_military_campaigns(self) -> Response: def _get_military_campaigns(self) -> Response:
return self.get(f"{self.url}/military/campaigns-new/") return self.get(f"{self.url}/military/campaigns-new/")
@ -426,7 +496,7 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
return self.get(f"{self.url}/military/campaignsJson/citizen") return self.get(f"{self.url}/military/campaignsJson/citizen")
def _get_military_unit_data(self, unit_id: int, **kwargs) -> Response: def _get_military_unit_data(self, unit_id: int, **kwargs) -> Response:
params = {'groupId': unit_id, 'panel': 'members', **kwargs} params = {"groupId": unit_id, "panel": "members", **kwargs}
return self.get(f"{self.url}/military/military-unit-data/", params=params) return self.get(f"{self.url}/military/military-unit-data/", params=params)
def _post_main_activate_battle_effect(self, battle_id: int, kind: str, citizen_id: int) -> Response: def _post_main_activate_battle_effect(self, battle_id: int, kind: str, citizen_id: int) -> Response:
@ -437,8 +507,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:
@ -448,22 +520,32 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
data = dict(type=kind, quality=quality, duration=duration, battleId=battle_id, _token=self.token) data = dict(type=kind, quality=quality, duration=duration, battleId=battle_id, _token=self.token)
return self.post(f"{self.url}/military/fight-activateBooster", data=data) return self.post(f"{self.url}/military/fight-activateBooster", data=data)
def _post_military_change_weapon(self, battle_id: int, battle_zone: int, weapon_level: int, ) -> Response: def _post_military_change_weapon(
self,
battle_id: int,
battle_zone: int,
weapon_level: int,
) -> Response:
data = dict(battleId=battle_id, _token=self.token, battleZoneId=battle_zone, customizationLevel=weapon_level) data = dict(battleId=battle_id, _token=self.token, battleZoneId=battle_zone, customizationLevel=weapon_level)
return self.post(f"{self.url}/military/change-weapon", data=data) return self.post(f"{self.url}/military/change-weapon", data=data)
def _post_military_battle_console(self, battle_id: int, action: str, page: int = 1, **kwargs) -> Response: def _post_military_battle_console(self, battle_id: int, action: str, page: int = 1, **kwargs) -> Response:
data = dict(battleId=battle_id, action=action, _token=self.token) data = dict(battleId=battle_id, action=action, _token=self.token)
if action == 'battleStatistics': if action == "battleStatistics":
data.update(round=kwargs['round_id'], zoneId=kwargs['round_id'], leftPage=page, rightPage=page, data.update(
division=kwargs['division'], type=kwargs.get('type', 'damage'), ) round=kwargs["round_id"],
elif action == 'warList': zoneId=kwargs["round_id"],
leftPage=page,
rightPage=page,
division=kwargs["division"],
type=kwargs.get("type", "damage"),
)
elif action == "warList":
data.update(page=page) data.update(page=page)
return self.post(f"{self.url}/military/battle-console", data=data) return self.post(f"{self.url}/military/battle-console", data=data)
def _post_military_deploy_bomb(self, battle_id: int, division_id: int, side_id: int, bomb_id: int) -> Response: def _post_military_deploy_bomb(self, battle_id: int, division_id: int, side_id: int, bomb_id: int) -> Response:
data = dict(battleId=battle_id, battleZoneId=division_id, sideId=side_id, sideCountryId=side_id, data = dict(battleId=battle_id, battleZoneId=division_id, sideId=side_id, sideCountryId=side_id, bombId=bomb_id, _token=self.token)
bombId=bomb_id, _token=self.token)
return self.post(f"{self.url}/military/deploy-bomb", data=data) return self.post(f"{self.url}/military/deploy-bomb", data=data)
def _post_military_fight_air(self, battle_id: int, side_id: int, zone_id: int) -> Response: def _post_military_fight_air(self, battle_id: int, side_id: int, zone_id: int) -> Response:
@ -485,10 +567,20 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
def _post_fight_deploy_start_deploy( def _post_fight_deploy_start_deploy(
self, battle_id: int, side_id: int, battle_zone_id: int, energy: int, weapon: int, **kwargs self, battle_id: int, side_id: int, battle_zone_id: int, energy: int, weapon: int, **kwargs
) -> Response: ) -> Response:
data = dict(_token=self.token, battleId=battle_id, battleZoneId=battle_zone_id, sideCountryId=side_id, data = dict(
weaponQuality=weapon, totalEnergy=energy, **kwargs) _token=self.token,
battleId=battle_id,
battleZoneId=battle_zone_id,
sideCountryId=side_id,
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):
@ -509,8 +601,7 @@ class ErepublikPoliticsAPI(CitizenBaseAPI):
return self.get(f"{self.url}/main/presidential-elections/{country_id}/{timestamp}") return self.get(f"{self.url}/main/presidential-elections/{country_id}/{timestamp}")
def _post_propose_president_candidate(self, party_slug: str, citizen_id: int) -> Response: def _post_propose_president_candidate(self, party_slug: str, citizen_id: int) -> Response:
return self.post(f"{self.url}/propose-president-candidate/{party_slug}", return self.post(f"{self.url}/propose-president-candidate/{party_slug}", data=dict(_token=self.token, citizen=citizen_id))
data=dict(_token=self.token, citizen=citizen_id))
def _get_auto_propose_president_candidate(self, party_slug: str) -> Response: def _get_auto_propose_president_candidate(self, party_slug: str) -> Response:
return self.get(f"{self.url}/auto-propose-president-candidate/{party_slug}") return self.get(f"{self.url}/auto-propose-president-candidate/{party_slug}")
@ -518,17 +609,15 @@ class ErepublikPoliticsAPI(CitizenBaseAPI):
class ErepublikPresidentAPI(CitizenBaseAPI): class ErepublikPresidentAPI(CitizenBaseAPI):
def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response: def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response:
data = {'_token': self.token, 'warId': war_id, 'regionName': region_name, 'regionNameConfirm': region_name} data = {"_token": self.token, "warId": war_id, "regionName": region_name, "regionNameConfirm": region_name}
return self.post(f'{self.url}/wars/attack-region/{war_id}/{region_id}', data=data) return self.post(f"{self.url}/wars/attack-region/{war_id}/{region_id}", data=data)
def _post_new_war(self, self_country_id: int, attack_country_id: int, debate: str = "") -> Response: def _post_new_war(self, self_country_id: int, attack_country_id: int, debate: str = "") -> Response:
data = dict(requirments=1, _token=self.token, debate=debate, data = dict(requirments=1, _token=self.token, debate=debate, countryNameConfirm=constants.COUNTRIES[attack_country_id].link)
countryNameConfirm=constants.COUNTRIES[attack_country_id].link)
return self.post(f"{self.url}/{constants.COUNTRIES[self_country_id].link}/new-war", data=data) return self.post(f"{self.url}/{constants.COUNTRIES[self_country_id].link}/new-war", data=data)
def _post_new_donation(self, country_id: int, amount: int, org_name: str, debate: str = "") -> Response: def _post_new_donation(self, country_id: int, amount: int, org_name: str, debate: str = "") -> Response:
data = dict(requirments=1, _token=self.token, debate=debate, currency=1, value=amount, commit='Propose', data = dict(requirments=1, _token=self.token, debate=debate, currency=1, value=amount, commit="Propose", type_name=org_name)
type_name=org_name)
return self.post(f"{self.url}/{constants.COUNTRIES[country_id].link}/new-donation", data=data) return self.post(f"{self.url}/{constants.COUNTRIES[country_id].link}/new-donation", data=data)
@ -549,10 +638,10 @@ class ErepublikProfileAPI(CitizenBaseAPI):
return self.get(f"{self.url}/main/messages-paginated/{page}") return self.get(f"{self.url}/main/messages-paginated/{page}")
def _get_main_money_donation_accept(self, donation_id: int) -> Response: def _get_main_money_donation_accept(self, donation_id: int) -> Response:
return self.get(f"{self.url}/main/money-donation/accept/{donation_id}", params={'_token': self.token}) return self.get(f"{self.url}/main/money-donation/accept/{donation_id}", params={"_token": self.token})
def _get_main_money_donation_reject(self, donation_id: int) -> Response: def _get_main_money_donation_reject(self, donation_id: int) -> Response:
return self.get(f"{self.url}/main/money-donation/reject/{donation_id}", params={'_token': self.token}) return self.get(f"{self.url}/main/money-donation/reject/{donation_id}", params={"_token": self.token})
def _get_main_notifications_ajax_community(self, page: int = 1) -> Response: def _get_main_notifications_ajax_community(self, page: int = 1) -> Response:
return self.get(f"{self.url}/main/notificationsAjax/community/{page}") return self.get(f"{self.url}/main/notificationsAjax/community/{page}")
@ -572,16 +661,16 @@ class ErepublikProfileAPI(CitizenBaseAPI):
def _post_main_citizen_add_remove_friend(self, citizen: int, add: bool) -> Response: def _post_main_citizen_add_remove_friend(self, citizen: int, add: bool) -> Response:
data = dict(_token=self.token, citizenId=citizen, url="//www.erepublik.com/en/main/citizen-addRemoveFriend") data = dict(_token=self.token, citizenId=citizen, url="//www.erepublik.com/en/main/citizen-addRemoveFriend")
if add: if add:
data.update({'action': 'addFriend'}) data.update({"action": "addFriend"})
else: else:
data.update({'action': 'removeFriend'}) data.update({"action": "removeFriend"})
return self.post(f"{self.url}/main/citizen-addRemoveFriend", data=data) return self.post(f"{self.url}/main/citizen-addRemoveFriend", data=data)
def _post_main_daily_task_reward(self) -> Response: def _post_main_daily_task_reward(self) -> Response:
return self.post(f"{self.url}/main/daily-tasks-reward", data=dict(_token=self.token)) return self.post(f"{self.url}/main/daily-tasks-reward", data=dict(_token=self.token))
def _post_delete_message(self, msg_id: list) -> Response: def _post_delete_message(self, msg_id: list) -> Response:
data = {'_token': self.token, "delete_message[]": msg_id} data = {"_token": self.token, "delete_message[]": msg_id}
return self.post(f"{self.url}/main/messages-delete", data) return self.post(f"{self.url}/main/messages-delete", data)
def _post_eat(self, color: str) -> Response: def _post_eat(self, color: str) -> Response:
@ -593,37 +682,36 @@ class ErepublikProfileAPI(CitizenBaseAPI):
return self.post(f"{self.url}/main/global-alerts/close", data=data) return self.post(f"{self.url}/main/global-alerts/close", data=data)
def _post_forgot_password(self, email: str) -> Response: def _post_forgot_password(self, email: str) -> Response:
data = dict(_token=self.token, email=email, commit='Reset password') data = dict(_token=self.token, email=email, commit="Reset password")
return self.post(f"{self.url}/forgot-password", data=data) return self.post(f"{self.url}/forgot-password", data=data)
def _post_login(self, email: str, password: str) -> Response: def _post_login(self, email: str, password: str) -> Response:
data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember='on') data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember="on")
return self.post(f"{self.url}/login", data=data) return self.post(f"{self.url}/login", data=data)
def _post_main_messages_alert(self, notification_ids: List[int]) -> Response: def _post_main_messages_alert(self, notification_ids: List[int]) -> Response:
data = {'_token': self.token, "delete_alerts[]": notification_ids, 'deleteAllAlerts': '1', 'delete': 'Delete'} data = {"_token": self.token, "delete_alerts[]": notification_ids, "deleteAllAlerts": "1", "delete": "Delete"}
return self.post(f"{self.url}/main/messages-alerts/1", data=data) return self.post(f"{self.url}/main/messages-alerts/1", data=data)
def _post_main_notifications_ajax_community(self, notification_ids: List[int], page: int = 1) -> Response: def _post_main_notifications_ajax_community(self, notification_ids: List[int], page: int = 1) -> Response:
data = {'_token': self.token, "delete_alerts[]": notification_ids} data = {"_token": self.token, "delete_alerts[]": notification_ids}
return self.post(f"{self.url}/main/notificationsAjax/community/{page}", data=data) return self.post(f"{self.url}/main/notificationsAjax/community/{page}", data=data)
def _post_main_notifications_ajax_system(self, notification_ids: List[int], page: int = 1) -> Response: def _post_main_notifications_ajax_system(self, notification_ids: List[int], page: int = 1) -> Response:
data = {'_token': self.token, "delete_alerts[]": notification_ids} data = {"_token": self.token, "delete_alerts[]": notification_ids}
return self.post(f"{self.url}/main/notificationsAjax/system/{page}", data=data) return self.post(f"{self.url}/main/notificationsAjax/system/{page}", data=data)
def _post_main_notifications_ajax_report(self, notification_ids: List[int], page: int = 1) -> Response: def _post_main_notifications_ajax_report(self, notification_ids: List[int], page: int = 1) -> Response:
data = {'_token': self.token, "delete_alerts[]": notification_ids} data = {"_token": self.token, "delete_alerts[]": notification_ids}
return self.post(f"{self.url}/main/notificationsAjax/report/{page}", data=data) return self.post(f"{self.url}/main/notificationsAjax/report/{page}", data=data)
def _post_main_messages_compose(self, subject: str, body: str, citizens: List[int]) -> Response: def _post_main_messages_compose(self, subject: str, body: str, citizens: List[int]) -> Response:
url_pk = 0 if len(citizens) > 1 else str(citizens[0]) url_pk = 0 if len(citizens) > 1 else str(citizens[0])
data = dict(citizen_name=",".join([str(x) for x in citizens]), data = dict(citizen_name=",".join([str(x) for x in citizens]), citizen_subject=subject, _token=self.token, citizen_message=body)
citizen_subject=subject, _token=self.token, citizen_message=body)
return self.post(f"{self.url}/main/messages-compose/{url_pk}", data=data) return self.post(f"{self.url}/main/messages-compose/{url_pk}", data=data)
def _post_military_group_missions(self) -> Response: def _post_military_group_missions(self) -> Response:
data = dict(action='check', _token=self.token) data = dict(action="check", _token=self.token)
return self.post(f"{self.url}/military/group-missions", data=data) return self.post(f"{self.url}/military/group-missions", data=data)
def _post_main_weekly_challenge_reward(self, reward_id: int) -> Response: def _post_main_weekly_challenge_reward(self, reward_id: int) -> Response:
@ -635,7 +723,7 @@ class ErepublikProfileAPI(CitizenBaseAPI):
return self.post(f"{self.url}/main/weekly-challenge-collect-all", data=data) return self.post(f"{self.url}/main/weekly-challenge-collect-all", data=data)
def _post_main_profile_update(self, action: str, params: str): def _post_main_profile_update(self, action: str, params: str):
data = {'action': action, 'params': params, '_token': self.token} data = {"action": action, "params": params, "_token": self.token}
return self.post(f"{self.url}/main/profile-update", data=data) return self.post(f"{self.url}/main/profile-update", data=data)
@ -652,85 +740,95 @@ class ErepublikWallPostAPI(CitizenBaseAPI):
# ## Country # ## Country
def _post_main_country_comment_retrieve(self, post_id: int) -> Response: def _post_main_country_comment_retrieve(self, post_id: int) -> Response:
data = {'_token': self.token, 'postId': post_id} data = {"_token": self.token, "postId": post_id}
return self.post(f"{self.url}/main/country-comment/retrieve/json", data=data) return self.post(f"{self.url}/main/country-comment/retrieve/json", data=data)
def _post_main_country_comment_create(self, post_id: int, comment_message: str) -> Response: def _post_main_country_comment_create(self, post_id: int, comment_message: str) -> Response:
data = {'_token': self.token, 'postId': post_id, 'comment_message': comment_message} data = {"_token": self.token, "postId": post_id, "comment_message": comment_message}
return self.post(f"{self.url}/main/country-comment/create/json", data=data) return self.post(f"{self.url}/main/country-comment/create/json", data=data)
def _post_main_country_post_create(self, body: str, post_as: int) -> Response: def _post_main_country_post_create(self, body: str, post_as: int) -> Response:
data = {'_token': self.token, 'post_message': body, 'post_as': post_as} data = {"_token": self.token, "post_message": body, "post_as": post_as}
return self.post(f"{self.url}/main/country-post/create/json", data=data) return self.post(f"{self.url}/main/country-post/create/json", data=data)
def _post_main_country_post_retrieve(self) -> Response: def _post_main_country_post_retrieve(self) -> Response:
data = {'_token': self.token, 'page': 1, 'switchedFrom': False} data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post(f"{self.url}/main/country-post/retrieve/json", data=data) return self.post(f"{self.url}/main/country-post/retrieve/json", data=data)
# ## Military Unit # ## Military Unit
def _post_main_military_unit_comment_retrieve(self, post_id: int) -> Response: def _post_main_military_unit_comment_retrieve(self, post_id: int) -> Response:
data = {'_token': self.token, 'postId': post_id} data = {"_token": self.token, "postId": post_id}
return self.post(f"{self.url}/main/military-unit-comment/retrieve/json", data=data) return self.post(f"{self.url}/main/military-unit-comment/retrieve/json", data=data)
def _post_main_military_unit_comment_create(self, post_id: int, comment_message: str) -> Response: def _post_main_military_unit_comment_create(self, post_id: int, comment_message: str) -> Response:
data = {'_token': self.token, 'postId': post_id, 'comment_message': comment_message} data = {"_token": self.token, "postId": post_id, "comment_message": comment_message}
return self.post(f"{self.url}/main/military-unit-comment/create/json", data=data) return self.post(f"{self.url}/main/military-unit-comment/create/json", data=data)
def _post_main_military_unit_post_create(self, body: str, post_as: int) -> Response: def _post_main_military_unit_post_create(self, body: str, post_as: int) -> Response:
data = {'_token': self.token, 'post_message': body, 'post_as': post_as} data = {"_token": self.token, "post_message": body, "post_as": post_as}
return self.post(f"{self.url}/main/military-unit-post/create/json", data=data) return self.post(f"{self.url}/main/military-unit-post/create/json", data=data)
def _post_main_military_unit_post_retrieve(self) -> Response: def _post_main_military_unit_post_retrieve(self) -> Response:
data = {'_token': self.token, 'page': 1, 'switchedFrom': False} data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post(f"{self.url}/main/military-unit-post/retrieve/json", data=data) return self.post(f"{self.url}/main/military-unit-post/retrieve/json", data=data)
# ## Party # ## Party
def _post_main_party_comment_retrieve(self, post_id: int) -> Response: def _post_main_party_comment_retrieve(self, post_id: int) -> Response:
data = {'_token': self.token, 'postId': post_id} data = {"_token": self.token, "postId": post_id}
return self.post(f"{self.url}/main/party-comment/retrieve/json", data=data) return self.post(f"{self.url}/main/party-comment/retrieve/json", data=data)
def _post_main_party_comment_create(self, post_id: int, comment_message: str) -> Response: def _post_main_party_comment_create(self, post_id: int, comment_message: str) -> Response:
data = {'_token': self.token, 'postId': post_id, 'comment_message': comment_message} data = {"_token": self.token, "postId": post_id, "comment_message": comment_message}
return self.post(f"{self.url}/main/party-comment/create/json", data=data) return self.post(f"{self.url}/main/party-comment/create/json", data=data)
def _post_main_party_post_create(self, body: str) -> Response: def _post_main_party_post_create(self, body: str) -> Response:
data = {'_token': self.token, 'post_message': body} data = {"_token": self.token, "post_message": body}
return self.post(f"{self.url}/main/party-post/create/json", data=data) return self.post(f"{self.url}/main/party-post/create/json", data=data)
def _post_main_party_post_retrieve(self) -> Response: def _post_main_party_post_retrieve(self) -> Response:
data = {'_token': self.token, 'page': 1, 'switchedFrom': False} data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post(f"{self.url}/main/party-post/retrieve/json", data=data) return self.post(f"{self.url}/main/party-post/retrieve/json", data=data)
# ## Friend's Wall # ## Friend's Wall
def _post_main_wall_comment_retrieve(self, post_id: int) -> Response: def _post_main_wall_comment_retrieve(self, post_id: int) -> Response:
data = {'_token': self.token, 'postId': post_id} data = {"_token": self.token, "postId": post_id}
return self.post(f"{self.url}/main/wall-comment/retrieve/json", data=data) return self.post(f"{self.url}/main/wall-comment/retrieve/json", data=data)
def _post_main_wall_comment_create(self, post_id: int, comment_message: str) -> Response: def _post_main_wall_comment_create(self, post_id: int, comment_message: str) -> Response:
data = {'_token': self.token, 'postId': post_id, 'comment_message': comment_message} data = {"_token": self.token, "postId": post_id, "comment_message": comment_message}
return self.post(f"{self.url}/main/wall-comment/create/json", data=data) return self.post(f"{self.url}/main/wall-comment/create/json", data=data)
def _post_main_wall_post_create(self, body: str) -> Response: def _post_main_wall_post_create(self, body: str) -> Response:
data = {'_token': self.token, 'post_message': body} data = {"_token": self.token, "post_message": body}
return self.post(f"{self.url}/main/wall-post/create/json", data=data) return self.post(f"{self.url}/main/wall-post/create/json", data=data)
def _post_main_wall_post_retrieve(self) -> Response: def _post_main_wall_post_retrieve(self) -> Response:
data = {'_token': self.token, 'page': 1, 'switchedFrom': False} data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post(f"{self.url}/main/wall-post/retrieve/json", data=data) return self.post(f"{self.url}/main/wall-post/retrieve/json", data=data)
# ## Medal posting # ## Medal posting
def _post_main_wall_post_automatic(self, message: str, achievement_id: int) -> Response: def _post_main_wall_post_automatic(self, message: str, achievement_id: int) -> Response:
return self.post(f"{self.url}/main/wall-post/automatic", data=dict(_token=self.token, message=message, return self.post(
achievementId=achievement_id)) f"{self.url}/main/wall-post/automatic", data=dict(_token=self.token, message=message, achievementId=achievement_id)
)
class CitizenAPI( class CitizenAPI(
ErepublikArticleAPI, ErepublikCountryAPI, ErepublikCompanyAPI, ErepublikEconomyAPI, ErepublikArticleAPI,
ErepublikLeaderBoardAPI, ErepublikLocationAPI, ErepublikMilitaryAPI, ErepublikProfileAPI, ErepublikCountryAPI,
ErepublikPresidentAPI, ErepublikPoliticsAPI, ErepublikAnniversaryAPI, ErepublikWallPostAPI, ErepublikCompanyAPI,
ErepublikTravelAPI ErepublikEconomyAPI,
ErepublikLeaderBoardAPI,
ErepublikLocationAPI,
ErepublikMilitaryAPI,
ErepublikProfileAPI,
ErepublikPresidentAPI,
ErepublikPoliticsAPI,
ErepublikAnniversaryAPI,
ErepublikWallPostAPI,
ErepublikTravelAPI,
): ):
pass pass

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,10 +3,21 @@ from typing import Dict, Optional, Union
import pytz import pytz
__all__ = ['erep_tz', 'min_datetime', 'max_datetime', 'Country', 'AIR_RANKS', 'COUNTRIES', 'FOOD_ENERGY', __all__ = [
'GROUND_RANKS', 'GROUND_RANK_POINTS', 'INDUSTRIES', 'TERRAINS'] "erep_tz",
"min_datetime",
"max_datetime",
"Country",
"AIR_RANKS",
"COUNTRIES",
"FOOD_ENERGY",
"GROUND_RANKS",
"GROUND_RANK_POINTS",
"INDUSTRIES",
"TERRAINS",
]
erep_tz = pytz.timezone('US/Pacific') erep_tz = pytz.timezone("US/Pacific")
min_datetime = erep_tz.localize(datetime.datetime(2007, 11, 20)) min_datetime = erep_tz.localize(datetime.datetime(2007, 11, 20))
max_datetime = erep_tz.localize(datetime.datetime(2281, 9, 4)) max_datetime = erep_tz.localize(datetime.datetime(2281, 9, 4))
@ -53,18 +64,70 @@ class Country:
class Industries: class Industries:
__by_name = {'food': 1, 'weapon': 2, 'ticket': 3, 'house': 4, 'aircraft': 23, __by_name = {
'foodraw': 7, 'weaponraw': 12, 'houseraw': 18, 'aircraftraw': 24, 'airplaneraw': 24, "food": 1,
'frm': 7, 'wrm': 12, 'hrm': 18, 'arm': 24, "weapon": 2,
'frm q1': 7, 'frm q2': 8, 'frm q3': 9, 'frm q4': 10, 'frm q5': 11, "ticket": 3,
'wrm q1': 12, 'wrm q2': 13, 'wrm q3': 14, 'wrm q4': 15, 'wrm q5': 16, "house": 4,
'hrm q1': 18, 'hrm q2': 19, 'hrm q3': 20, 'hrm q4': 21, 'hrm q5': 22, "aircraft": 23,
'arm q1': 24, 'arm q2': 25, 'arm q3': 26, 'arm q4': 27, 'arm q5': 28} "foodraw": 7,
__by_id = {1: 'Food', 2: 'Weapon', 3: 'Ticket', 4: 'House', 23: 'Aircraft', "weaponraw": 12,
7: 'foodRaw', 8: 'FRM q2', 9: 'FRM q3', 10: 'FRM q4', 11: 'FRM q5', "houseraw": 18,
12: 'weaponRaw', 13: 'WRM q2', 14: 'WRM q3', 15: 'WRM q4', 16: 'WRM q5', "aircraftraw": 24,
17: 'houseRaw', 18: 'houseRaw', 19: 'HRM q2', 20: 'HRM q3', 21: 'HRM q4', 22: 'HRM q5', "airplaneraw": 24,
24: 'aircraftRaw', 25: 'ARM q2', 26: 'ARM q3', 27: 'ARM q4', 28: 'ARM q5'} "frm": 7,
"wrm": 12,
"hrm": 18,
"arm": 24,
"frm q1": 7,
"frm q2": 8,
"frm q3": 9,
"frm q4": 10,
"frm q5": 11,
"wrm q1": 12,
"wrm q2": 13,
"wrm q3": 14,
"wrm q4": 15,
"wrm q5": 16,
"hrm q1": 18,
"hrm q2": 19,
"hrm q3": 20,
"hrm q4": 21,
"hrm q5": 22,
"arm q1": 24,
"arm q2": 25,
"arm q3": 26,
"arm q4": 27,
"arm q5": 28,
}
__by_id = {
1: "Food",
2: "Weapon",
3: "Ticket",
4: "House",
23: "Aircraft",
7: "foodRaw",
8: "FRM q2",
9: "FRM q3",
10: "FRM q4",
11: "FRM q5",
12: "weaponRaw",
13: "WRM q2",
14: "WRM q3",
15: "WRM q4",
16: "WRM q5",
17: "houseRaw",
18: "houseRaw",
19: "HRM q2",
20: "HRM q3",
21: "HRM q4",
22: "HRM q5",
24: "aircraftRaw",
25: "ARM q2",
26: "ARM q3",
27: "ARM q4",
28: "ARM q5",
}
def __getitem__(self, item) -> Optional[Union[int, str]]: def __getitem__(self, item) -> Optional[Union[int, str]]:
if isinstance(item, int): if isinstance(item, int):
@ -81,111 +144,515 @@ 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"),
10: Country(10, 'Italy', 'Italy', 'ITA'), 11: Country(11, 'France', 'France', 'FRA'), 9: Country(9, "Brazil", "Brazil", "BRA"),
12: Country(12, 'Germany', 'Germany', 'DEU'), 13: Country(13, 'Hungary', 'Hungary', 'HUN'), 10: Country(10, "Italy", "Italy", "ITA"),
14: Country(14, 'China', 'China', 'CHN'), 15: Country(15, 'Spain', 'Spain', 'ESP'), 11: Country(11, "France", "France", "FRA"),
23: Country(23, 'Canada', 'Canada', 'CAN'), 24: Country(24, 'USA', 'USA', 'USA'), 12: Country(12, "Germany", "Germany", "DEU"),
26: Country(26, 'Mexico', 'Mexico', 'MEX'), 27: Country(27, 'Argentina', 'Argentina', 'ARG'), 13: Country(13, "Hungary", "Hungary", "HUN"),
28: Country(28, 'Venezuela', 'Venezuela', 'VEN'), 29: Country(29, 'United Kingdom', 'United-Kingdom', 'GBR'), 14: Country(14, "China", "China", "CHN"),
30: Country(30, 'Switzerland', 'Switzerland', 'CHE'), 31: Country(31, 'Netherlands', 'Netherlands', 'NLD'), 15: Country(15, "Spain", "Spain", "ESP"),
32: Country(32, 'Belgium', 'Belgium', 'BEL'), 33: Country(33, 'Austria', 'Austria', 'AUT'), 23: Country(23, "Canada", "Canada", "CAN"),
34: Country(34, 'Czech Republic', 'Czech-Republic', 'CZE'), 35: Country(35, 'Poland', 'Poland', 'POL'), 24: Country(24, "USA", "USA", "USA"),
36: Country(36, 'Slovakia', 'Slovakia', 'SVK'), 37: Country(37, 'Norway', 'Norway', 'NOR'), 26: Country(26, "Mexico", "Mexico", "MEX"),
38: Country(38, 'Sweden', 'Sweden', 'SWE'), 39: Country(39, 'Finland', 'Finland', 'FIN'), 27: Country(27, "Argentina", "Argentina", "ARG"),
40: Country(40, 'Ukraine', 'Ukraine', 'UKR'), 41: Country(41, 'Russia', 'Russia', 'RUS'), 28: Country(28, "Venezuela", "Venezuela", "VEN"),
42: Country(42, 'Bulgaria', 'Bulgaria', 'BGR'), 43: Country(43, 'Turkey', 'Turkey', 'TUR'), 29: Country(29, "United Kingdom", "United-Kingdom", "GBR"),
44: Country(44, 'Greece', 'Greece', 'GRC'), 45: Country(45, 'Japan', 'Japan', 'JPN'), 30: Country(30, "Switzerland", "Switzerland", "CHE"),
47: Country(47, 'South Korea', 'South-Korea', 'KOR'), 48: Country(48, 'India', 'India', 'IND'), 31: Country(31, "Netherlands", "Netherlands", "NLD"),
49: Country(49, 'Indonesia', 'Indonesia', 'IDN'), 50: Country(50, 'Australia', 'Australia', 'AUS'), 32: Country(32, "Belgium", "Belgium", "BEL"),
51: Country(51, 'South Africa', 'South-Africa', 'ZAF'), 33: Country(33, "Austria", "Austria", "AUT"),
52: Country(52, 'Republic of Moldova', 'Republic-of-Moldova', 'MDA'), 34: Country(34, "Czech Republic", "Czech-Republic", "CZE"),
53: Country(53, 'Portugal', 'Portugal', 'PRT'), 54: Country(54, 'Ireland', 'Ireland', 'IRL'), 35: Country(35, "Poland", "Poland", "POL"),
55: Country(55, 'Denmark', 'Denmark', 'DNK'), 56: Country(56, 'Iran', 'Iran', 'IRN'), 36: Country(36, "Slovakia", "Slovakia", "SVK"),
57: Country(57, 'Pakistan', 'Pakistan', 'PAK'), 58: Country(58, 'Israel', 'Israel', 'ISR'), 37: Country(37, "Norway", "Norway", "NOR"),
59: Country(59, 'Thailand', 'Thailand', 'THA'), 61: Country(61, 'Slovenia', 'Slovenia', 'SVN'), 38: Country(38, "Sweden", "Sweden", "SWE"),
63: Country(63, 'Croatia', 'Croatia', 'HRV'), 64: Country(64, 'Chile', 'Chile', 'CHL'), 39: Country(39, "Finland", "Finland", "FIN"),
65: Country(65, 'Serbia', 'Serbia', 'SRB'), 66: Country(66, 'Malaysia', 'Malaysia', 'MYS'), 40: Country(40, "Ukraine", "Ukraine", "UKR"),
67: Country(67, 'Philippines', 'Philippines', 'PHL'), 68: Country(68, 'Singapore', 'Singapore', 'SGP'), 41: Country(41, "Russia", "Russia", "RUS"),
69: Country(69, 'Bosnia and Herzegovina', 'Bosnia-Herzegovina', 'BiH'), 42: Country(42, "Bulgaria", "Bulgaria", "BGR"),
70: Country(70, 'Estonia', 'Estonia', 'EST'), 80: Country(80, 'Montenegro', 'Montenegro', 'MNE'), 43: Country(43, "Turkey", "Turkey", "TUR"),
71: Country(71, 'Latvia', 'Latvia', 'LVA'), 72: Country(72, 'Lithuania', 'Lithuania', 'LTU'), 44: Country(44, "Greece", "Greece", "GRC"),
73: Country(73, 'North Korea', 'North-Korea', 'PRK'), 74: Country(74, 'Uruguay', 'Uruguay', 'URY'), 45: Country(45, "Japan", "Japan", "JPN"),
75: Country(75, 'Paraguay', 'Paraguay', 'PRY'), 76: Country(76, 'Bolivia', 'Bolivia', 'BOL'), 47: Country(47, "South Korea", "South-Korea", "KOR"),
77: Country(77, 'Peru', 'Peru', 'PER'), 78: Country(78, 'Colombia', 'Colombia', 'COL'), 48: Country(48, "India", "India", "IND"),
79: Country(79, 'Republic of Macedonia (FYROM)', 'Republic-of-Macedonia-FYROM', 'MKD'), 49: Country(49, "Indonesia", "Indonesia", "IDN"),
81: Country(81, 'Republic of China (Taiwan)', 'Republic-of-China-Taiwan', 'TWN'), 50: Country(50, "Australia", "Australia", "AUS"),
82: Country(82, 'Cyprus', 'Cyprus', 'CYP'), 167: Country(167, 'Albania', 'Albania', 'ALB'), 51: Country(51, "South Africa", "South-Africa", "ZAF"),
83: Country(83, 'Belarus', 'Belarus', 'BLR'), 84: Country(84, 'New Zealand', 'New-Zealand', 'NZL'), 52: Country(52, "Republic of Moldova", "Republic-of-Moldova", "MDA"),
164: Country(164, 'Saudi Arabia', 'Saudi-Arabia', 'SAU'), 165: Country(165, 'Egypt', 'Egypt', 'EGY'), 53: Country(53, "Portugal", "Portugal", "PRT"),
166: Country(166, 'United Arab Emirates', 'United-Arab-Emirates', 'UAE'), 54: Country(54, "Ireland", "Ireland", "IRL"),
168: Country(168, 'Georgia', 'Georgia', 'GEO'), 169: Country(169, 'Armenia', 'Armenia', 'ARM'), 55: Country(55, "Denmark", "Denmark", "DNK"),
170: Country(170, 'Nigeria', 'Nigeria', 'NGA'), 171: Country(171, 'Cuba', 'Cuba', 'CUB') 56: Country(56, "Iran", "Iran", "IRN"),
57: Country(57, "Pakistan", "Pakistan", "PAK"),
58: Country(58, "Israel", "Israel", "ISR"),
59: Country(59, "Thailand", "Thailand", "THA"),
61: Country(61, "Slovenia", "Slovenia", "SVN"),
63: Country(63, "Croatia", "Croatia", "HRV"),
64: Country(64, "Chile", "Chile", "CHL"),
65: Country(65, "Serbia", "Serbia", "SRB"),
66: Country(66, "Malaysia", "Malaysia", "MYS"),
67: Country(67, "Philippines", "Philippines", "PHL"),
68: Country(68, "Singapore", "Singapore", "SGP"),
69: Country(69, "Bosnia and Herzegovina", "Bosnia-Herzegovina", "BiH"),
70: Country(70, "Estonia", "Estonia", "EST"),
80: Country(80, "Montenegro", "Montenegro", "MNE"),
71: Country(71, "Latvia", "Latvia", "LVA"),
72: Country(72, "Lithuania", "Lithuania", "LTU"),
73: Country(73, "North Korea", "North-Korea", "PRK"),
74: Country(74, "Uruguay", "Uruguay", "URY"),
75: Country(75, "Paraguay", "Paraguay", "PRY"),
76: Country(76, "Bolivia", "Bolivia", "BOL"),
77: Country(77, "Peru", "Peru", "PER"),
78: Country(78, "Colombia", "Colombia", "COL"),
79: Country(79, "Republic of Macedonia (FYROM)", "Republic-of-Macedonia-FYROM", "MKD"),
81: Country(81, "Republic of China (Taiwan)", "Republic-of-China-Taiwan", "TWN"),
82: Country(82, "Cyprus", "Cyprus", "CYP"),
167: Country(167, "Albania", "Albania", "ALB"),
83: Country(83, "Belarus", "Belarus", "BLR"),
84: Country(84, "New Zealand", "New-Zealand", "NZL"),
164: Country(164, "Saudi Arabia", "Saudi-Arabia", "SAU"),
165: Country(165, "Egypt", "Egypt", "EGY"),
166: Country(166, "United Arab Emirates", "United-Arab-Emirates", "UAE"),
168: Country(168, "Georgia", "Georgia", "GEO"),
169: Country(169, "Armenia", "Armenia", "ARM"),
170: Country(170, "Nigeria", "Nigeria", "NGA"),
171: Country(171, "Cuba", "Cuba", "CUB"),
} }
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",
6: 'Corporal', 7: 'Corporal*', 8: 'Corporal**', 9: 'Corporal***', 2: "Private",
10: 'Sergeant', 11: 'Sergeant*', 12: 'Sergeant**', 13: 'Sergeant***', 3: "Private*",
14: 'Lieutenant', 15: 'Lieutenant*', 16: 'Lieutenant**', 17: 'Lieutenant***', 4: "Private**",
18: 'Captain', 19: 'Captain*', 20: 'Captain**', 21: 'Captain***', 5: "Private***",
22: 'Major', 23: 'Major*', 24: 'Major**', 25: 'Major***', 6: "Corporal",
26: 'Commander', 27: 'Commander*', 28: 'Commander**', 29: 'Commander***', 7: "Corporal*",
30: 'Lt Colonel', 31: 'Lt Colonel*', 32: 'Lt Colonel**', 33: 'Lt Colonel***', 8: "Corporal**",
34: 'Colonel', 35: 'Colonel*', 36: 'Colonel**', 37: 'Colonel***', 9: "Corporal***",
38: 'General', 39: 'General*', 40: 'General**', 41: 'General***', 10: "Sergeant",
42: 'Field Marshal', 43: 'Field Marshal*', 44: 'Field Marshal**', 45: 'Field Marshal***', 11: "Sergeant*",
46: 'Supreme Marshal', 47: 'Supreme Marshal*', 48: 'Supreme Marshal**', 49: 'Supreme Marshal***', 12: "Sergeant**",
50: 'National Force', 51: 'National Force*', 52: 'National Force**', 53: 'National Force***', 13: "Sergeant***",
54: 'World Class Force', 55: 'World Class Force*', 56: 'World Class Force**', 57: 'World Class Force***', 14: "Lieutenant",
58: 'Legendary Force', 59: 'Legendary Force*', 60: 'Legendary Force**', 61: 'Legendary Force***', 15: "Lieutenant*",
62: 'God of War', 63: 'God of War*', 64: 'God of War**', 65: 'God of War***', 16: "Lieutenant**",
66: 'Titan', 67: 'Titan*', 68: 'Titan**', 69: 'Titan***', 17: "Lieutenant***",
70: 'Legends I', 71: 'Legends II', 72: 'Legends III', 73: 'Legends IV', 74: 'Legends V', 75: 'Legends VI', 18: "Captain",
76: 'Legends VII', 77: 'Legends VIII', 78: 'Legends IX', 79: 'Legends X', 80: 'Legends XI', 81: 'Legends XII', 19: "Captain*",
82: 'Legends XIII', 83: 'Legends XIV', 84: 'Legends XV', 85: 'Legends XVI', 86: 'Legends XVII', 87: 'Legends XVIII', 20: "Captain**",
88: 'Legends XIX', 89: 'Legends XX', 21: "Captain***",
22: "Major",
23: "Major*",
24: "Major**",
25: "Major***",
26: "Commander",
27: "Commander*",
28: "Commander**",
29: "Commander***",
30: "Lt Colonel",
31: "Lt Colonel*",
32: "Lt Colonel**",
33: "Lt Colonel***",
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***",
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",
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",
} }
GROUND_RANK_POINTS: Dict[int, int] = { GROUND_RANK_POINTS: Dict[int, int] = {
1: 0, 2: 15, 3: 45, 4: 80, 5: 120, 6: 170, 7: 250, 8: 350, 9: 450, 10: 600, 11: 800, 12: 1000, 1: 0,
13: 1400, 14: 1850, 15: 2350, 16: 3000, 17: 3750, 18: 5000, 19: 6500, 20: 9000, 21: 12000, 2: 15,
22: 15500, 23: 20000, 24: 25000, 25: 31000, 26: 40000, 27: 52000, 28: 67000, 29: 85000, 3: 45,
30: 110000, 31: 140000, 32: 180000, 33: 225000, 34: 285000, 35: 355000, 36: 435000, 37: 540000, 4: 80,
38: 660000, 39: 800000, 40: 950000, 41: 1140000, 42: 1350000, 43: 1600000, 44: 1875000, 5: 120,
45: 2185000, 46: 2550000, 47: 3000000, 48: 3500000, 49: 4150000, 50: 4900000, 51: 5800000, 6: 170,
52: 7000000, 53: 9000000, 54: 11500000, 55: 14500000, 56: 18000000, 57: 22000000, 58: 26500000, 7: 250,
59: 31500000, 60: 37000000, 61: 43000000, 62: 50000000, 63: 100000000, 64: 200000000, 8: 350,
65: 500000000, 66: 1000000000, 67: 2000000000, 68: 4000000000, 69: 10000000000, 70: 20000000000, 9: 450,
71: 30000000000, 72: 40000000000, 73: 50000000000, 74: 60000000000, 75: 70000000000, 10: 600,
76: 80000000000, 77: 90000000000, 78: 100000000000, 79: 110000000000, 80: 120000000000, 11: 800,
81: 130000000000, 82: 140000000000, 83: 150000000000, 84: 160000000000, 85: 170000000000, 12: 1000,
86: 180000000000, 87: 190000000000, 88: 200000000000, 89: 210000000000 13: 1400,
14: 1850,
15: 2350,
16: 3000,
17: 3750,
18: 5000,
19: 6500,
20: 9000,
21: 12000,
22: 15500,
23: 20000,
24: 25000,
25: 31000,
26: 40000,
27: 52000,
28: 67000,
29: 85000,
30: 110000,
31: 140000,
32: 180000,
33: 225000,
34: 285000,
35: 355000,
36: 435000,
37: 540000,
38: 660000,
39: 800000,
40: 950000,
41: 1140000,
42: 1350000,
43: 1600000,
44: 1875000,
45: 2185000,
46: 2550000,
47: 3000000,
48: 3500000,
49: 4150000,
50: 4900000,
51: 5800000,
52: 7000000,
53: 9000000,
54: 11500000,
55: 14500000,
56: 18000000,
57: 22000000,
58: 26500000,
59: 31500000,
60: 37000000,
61: 43000000,
62: 50000000,
63: 100000000,
64: 200000000,
65: 500000000,
66: 1000000000,
67: 2000000000,
68: 4000000000,
69: 10000000000,
70: 20000000000,
71: 30000000000,
72: 40000000000,
73: 50000000000,
74: 60000000000,
75: 70000000000,
76: 80000000000,
77: 90000000000,
78: 100000000000,
79: 110000000000,
80: 120000000000,
81: 130000000000,
82: 140000000000,
83: 150000000000,
84: 160000000000,
85: 170000000000,
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] = {
6: 'Wasteland', 7: 'Mountains', 8: 'Beach', 9: 'Swamp', 10: 'Mud', 11: 'Hills', 0: "Standard",
12: 'Jungle', 13: 'Forest', 14: 'Desert'} 1: "Industrial",
2: "Urban",
3: "Suburbs",
4: "Airport",
5: "Plains",
6: "Wasteland",
7: "Mountains",
8: "Beach",
9: "Swamp",
10: "Mud",
11: "Hills",
12: "Jungle",
13: "Forest",
14: "Desert",
}

View File

@ -1,33 +1,56 @@
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",
'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now', "calculate_hit",
'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file', 'write_request', "date_from_eday",
'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock', "eday_from_date",
'json_decode_object_hook', 'json_load', 'json_loads'] "deprecation",
"get_final_hit_dmg",
"write_file",
"get_air_hit_dmg_value",
"get_file",
"get_ground_hit_dmg_value",
"get_sleep_seconds",
"good_timedelta",
"slugify",
"interactive_sleep",
"json",
"localize_dt",
"localize_timestamp",
"normalize_html_json",
"now",
"silent_sleep",
"json_decode_object_hook",
"json_load",
"json_loads",
"json_dump",
"json_dumps",
"b64json",
"ErepublikJSONEncoder",
]
VERSION: str = __version__ VERSION: str = __version__
@ -75,7 +98,7 @@ def date_from_eday(eday: int) -> datetime.date:
def get_sleep_seconds(time_until: datetime.datetime) -> int: def get_sleep_seconds(time_until: datetime.datetime) -> int:
""" time_until aware datetime object Wrapper for sleeping until """ """time_until aware datetime object Wrapper for sleeping until"""
sleep_seconds = int((time_until - now()).total_seconds()) sleep_seconds = int((time_until - now()).total_seconds())
return sleep_seconds if sleep_seconds > 0 else 0 return sleep_seconds if sleep_seconds > 0 else 0
@ -103,32 +126,11 @@ 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():
if file.is_dir(): if file.is_dir():
return str(file / 'new_file.txt') return str(file / "new_file.txt")
else: else:
version = 1 version = 1
try: try:
@ -150,168 +152,19 @@ def get_file(filepath: str) -> str:
def write_file(filename: str, content: str) -> int: def write_file(filename: str, content: str) -> int:
filename = get_file(filename) filename = get_file(filename)
with open(filename, 'ab') as f: with open(filename, "ab") as f:
ret = f.write(content.encode("utf-8")) ret = f.write(content.encode("utf-8"))
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)
js = re.sub(r'([{\s,])(\w+)(:)(?!"})', r'\1"\2"\3', js) js = re.sub(r'([{\s,])(\w+)(:)(?!"})', r'\1"\2"\3', js)
js = re.sub(r',\s*}', '}', js) js = re.sub(r",\s*}", "}", js)
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
@ -321,47 +174,51 @@ def slugify(value, allow_unicode=False) -> str:
""" """
value = str(value) value = str(value)
if allow_unicode: if allow_unicode:
value = unicodedata.normalize('NFKC', value) value = unicodedata.normalize("NFKC", value)
else: else:
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii') value = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
value = re.sub(r'[^\w\s-]', '_', value).strip().lower() value = re.sub(r"[^\w\s-]", "_", value).strip().lower()
return re.sub(r'[-\s]+', '-', value) return re.sub(r"[-\s]+", "-", value)
def calculate_hit(strength: float, rang: int, tp: bool, elite: bool, ne: bool, booster: int = 0, def calculate_hit(
weapon: int = 200, is_deploy: bool = False) -> Decimal: strength: float, rang: int, tp: bool, elite: bool, ne: bool, booster: int = 0, weapon: int = 200, is_deploy: bool = False
) -> Decimal:
dec = 3 if is_deploy else 0 dec = 3 if is_deploy else 0
base_str = (1 + Decimal(str(round(strength, 3))) / 400) base_str = 1 + Decimal(str(round(strength, 3))) / 400
base_rnk = (1 + Decimal(str(rang / 5))) base_rnk = 1 + Decimal(str(rang / 5))
base_wpn = (1 + Decimal(str(weapon / 100))) base_wpn = 1 + Decimal(str(weapon / 100))
dmg = 10 * base_str * base_rnk * base_wpn dmg = 10 * base_str * base_rnk * base_wpn
dmg = get_final_hit_dmg(dmg, rang, tp=tp, elite=elite, ne=ne, booster=booster) dmg = get_final_hit_dmg(dmg, rang, tp=tp, elite=elite, ne=ne, booster=booster)
return Decimal(round(dmg, dec)) return Decimal(round(dmg, dec))
def get_ground_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False, def get_ground_hit_dmg_value(
booster: int = 0, weapon_power: int = 200) -> Decimal: citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False, booster: int = 0, weapon_power: int = 200
r = requests.get(f'https://www.erepublik.com/en/main/citizen-profile-json/{citizen_id}').json() ) -> Decimal:
rang = r['military']['militaryData']['ground']['rankNumber'] r = requests.get(f"https://www.erepublik.com/en/main/citizen-profile-json/{citizen_id}").json()
strength = r['military']['militaryData']['ground']['strength'] rang = r["military"]["militaryData"]["ground"]["rankNumber"]
elite = r['citizenAttributes']['level'] > 100 strength = r["military"]["militaryData"]["ground"]["strength"]
elite = r["citizenAttributes"]["level"] > 100
if natural_enemy: if natural_enemy:
true_patriot = True true_patriot = True
return calculate_hit(strength, rang, true_patriot, elite, natural_enemy, booster, weapon_power) return calculate_hit(strength, rang, true_patriot, elite, natural_enemy, booster, weapon_power)
def get_air_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False, booster: int = 0, def get_air_hit_dmg_value(
weapon_power: int = 0) -> Decimal: citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False, booster: int = 0, weapon_power: int = 0
r = requests.get(f'https://www.erepublik.com/en/main/citizen-profile-json/{citizen_id}').json() ) -> Decimal:
rang = r['military']['militaryData']['aircraft']['rankNumber'] r = requests.get(f"https://www.erepublik.com/en/main/citizen-profile-json/{citizen_id}").json()
elite = r['citizenAttributes']['level'] > 100 rang = r["military"]["militaryData"]["aircraft"]["rankNumber"]
elite = r["citizenAttributes"]["level"] > 100
return calculate_hit(0, rang, true_patriot, elite, natural_enemy, booster, weapon_power) return calculate_hit(0, rang, true_patriot, elite, natural_enemy, booster, weapon_power)
def get_final_hit_dmg(base_dmg: Union[Decimal, float, str], rang: int, def get_final_hit_dmg(
tp: bool = False, elite: bool = False, ne: bool = False, booster: int = 0) -> Decimal: base_dmg: Union[Decimal, float, str], rang: int, tp: bool = False, elite: bool = False, ne: bool = False, booster: int = 0
) -> Decimal:
dmg = Decimal(str(base_dmg)) dmg = Decimal(str(base_dmg))
if elite: if elite:
@ -378,63 +235,54 @@ 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]:
""" Convert classes.ErepublikJSONEncoder datetime, date and timedelta to their python objects """Convert classes.ErepublikJSONEncoder datetime, date and timedelta to their python objects
:param o: :param o:
:return: Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta] :return: Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta]
""" """
if o.get('__type__'): if o.get("__type__"):
_type = o.get('__type__') _type = o.get("__type__")
if _type == 'datetime': if _type == "datetime":
dt = datetime.datetime.strptime(f"{o['date']} {o['time']}", '%Y-%m-%d %H:%M:%S') dt = datetime.datetime.strptime(f"{o['date']} {o['time']}", "%Y-%m-%d %H:%M:%S")
if o.get('tzinfo'): if o.get("tzinfo"):
dt = pytz.timezone(o['tzinfo']).localize(dt) dt = pytz.timezone(o["tzinfo"]).localize(dt)
return dt return dt
elif _type == 'date': elif _type == "date":
dt = datetime.datetime.strptime(f"{o['date']}", '%Y-%m-%d') dt = datetime.datetime.strptime(f"{o['date']}", "%Y-%m-%d")
return dt.date() return dt.date()
elif _type == 'timedelta': elif _type == "timedelta":
return datetime.timedelta(seconds=o['total_seconds']) return datetime.timedelta(seconds=o["total_seconds"])
return o return o
def json_load(f, **kwargs): def json_load(f, **kwargs):
kwargs.update(object_hook=json_decode_object_hook) # kwargs.update(object_hook=json_decode_object_hook)
return json.load(f, **kwargs) return json.load(f, **kwargs)
def json_loads(s: str, **kwargs): def json_loads(s: str, **kwargs):
kwargs.update(object_hook=json_decode_object_hook) # kwargs.update(object_hook=json_decode_object_hook)
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):
@ -442,5 +290,44 @@ def b64json(obj: Union[Dict[str, Union[int, List[str]]], List[str]]):
obj[k] = b64json(v) obj[k] = b64json(v)
else: else:
from .classes import ErepublikException from .classes import ErepublikException
raise ErepublikException(f'Unhandled object type! obj is {type(obj)}')
return b64encode(json.dumps(obj).encode('utf-8')).decode('utf-8') raise ErepublikException(f"Unhandled object type! obj is {type(obj)}")
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)

View File

@ -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()

4
pyproject.toml Normal file
View File

@ -0,0 +1,4 @@
[tool.black]
line-length = 140
target-version = ['py38', 'py39']

View File

@ -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.4
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.26.0
responses==0.12.1 requests-toolbelt==0.9.1
setuptools==52.0.0 responses==0.13.3
Sphinx==3.4.3 setuptools==57.4.0
twine==3.3.0 Sphinx==4.1.1
twine==3.4.1
wheel==0.36.2 wheel==0.36.2

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.24.0.3 current_version = 0.25.1.4
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+)?
@ -7,27 +7,29 @@ serialize = {major}.{minor}.{patch}.{dev}
{major}.{minor}.{patch} {major}.{minor}.{patch}
[bumpversion:file:setup.py] [bumpversion:file:setup.py]
search = version='{current_version}' search = version="{current_version}"
replace = version='{new_version}' replace = version="{new_version}"
[bumpversion:file:erepublik/__init__.py] [bumpversion:file:erepublik/__init__.py]
search = __version__ = '{current_version}' search = __version__ = "{current_version}"
replace = __version__ = '{new_version}' replace = __version__ = "{new_version}"
[bdist_wheel] [bdist_wheel]
universal = 1 universal = 1
[flake8] [flake8]
exclude = docs,.git,log,debug,venv exclude = docs,.git,log,debug,venv
max-line-length = 120 line_length = 140
ignore = D100,D101,D102,D103 max-line-length = 140
ignore = D100,D101,D102,D103,E203
[pycodestyle] [pycodestyle]
max-line-length = 120 line_length = 140
max-line-length = 140
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 +38,4 @@ warn_unused_configs = True
[isort] [isort]
multi_line_output = 2 multi_line_output = 2
line_length = 120 line_length = 140

View File

@ -5,51 +5,51 @@
from setuptools import find_packages, setup from setuptools import find_packages, setup
with open('README.rst') as readme_file: with open("README.rst") as readme_file:
readme = readme_file.read() readme = readme_file.read()
with open('HISTORY.rst') as history_file: 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.26.0",
"requests-toolbelt==0.9.1",
] ]
setup_requirements = [] setup_requirements = []
test_requirements = [ test_requirements = [
"pytest==6.1.2", "pytest==6.2.4",
"responses==0.12.1"
] ]
setup( setup(
author="Eriks Karls", author="Eriks Karls",
author_email='eriks@72.lv', author_email="eriks@72.lv",
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', "Development Status :: 4 - Beta",
'Intended Audience :: Developers', "Intended Audience :: Developers",
'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={},
install_requires=requirements, install_requires=requirements,
license="MIT license", license="MIT license",
long_description=readme + '\n\n' + history, long_description=readme + "\n\n" + history,
include_package_data=True, include_package_data=True,
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.1.4",
zip_safe=False, zip_safe=False,
) )

View File

@ -17,14 +17,13 @@ class TestErepublik(unittest.TestCase):
self.citizen.config.interactive = False self.citizen.config.interactive = False
def test_should_do_levelup(self): def test_should_do_levelup(self):
self.citizen.energy.recovered = 1900 self.citizen.energy.energy = 5950
self.citizen.energy.recoverable = 2940
self.citizen.energy.interval = 30 self.citizen.energy.interval = 30
self.citizen.energy.limit = 3000 self.citizen.energy.limit = 6000
self.citizen.details.xp = 14850 self.citizen.details.xp = 14850
self.assertTrue(self.citizen.should_do_levelup) self.assertTrue(self.citizen.should_do_levelup)
self.citizen.energy.recoverable = 1000 self.citizen.energy.energy = 1000
self.assertFalse(self.citizen.should_do_levelup) self.assertFalse(self.citizen.should_do_levelup)
def test_should_travel_to_fight(self): def test_should_travel_to_fight(self):
@ -33,40 +32,38 @@ class TestErepublik(unittest.TestCase):
self.citizen.config.always_travel = False self.citizen.config.always_travel = False
self.assertFalse(self.citizen.should_travel_to_fight()) self.assertFalse(self.citizen.should_travel_to_fight())
self.citizen.energy.recovered = 1900 self.citizen.energy.energy = 5960
self.citizen.energy.recoverable = 2940
self.citizen.energy.interval = 30 self.citizen.energy.interval = 30
self.citizen.energy.limit = 3000 self.citizen.energy.limit = 6000
self.citizen.details.xp = 14850 self.citizen.details.xp = 14850
self.assertTrue(self.citizen.should_travel_to_fight()) self.assertTrue(self.citizen.should_travel_to_fight())
self.citizen.details.xp = 15000 self.citizen.details.xp = 15000
self.citizen.energy.energy = 5000
self.assertFalse(self.citizen.should_travel_to_fight()) self.assertFalse(self.citizen.should_travel_to_fight())
self.citizen.energy.recovered = 3000 self.citizen.energy.energy = 5910
self.citizen.energy.recoverable = 2910
self.assertTrue(self.citizen.should_travel_to_fight()) self.assertTrue(self.citizen.should_travel_to_fight())
self.citizen.energy.recoverable = 2900 self.citizen.energy.energy = 5900
self.assertFalse(self.citizen.should_travel_to_fight()) self.assertFalse(self.citizen.should_travel_to_fight())
# self.citizen.next_reachable_energy and self.citizen.config.next_energy # self.citizen.next_reachable_energy and self.citizen.config.next_energy
self.citizen.config.next_energy = True self.citizen.config.next_energy = True
self.citizen.energy.limit = 5000 self.citizen.energy.limit = 10000
self.citizen.details.next_pp = [5000, 5250, 5750, 6250, 6750] self.citizen.details.next_pp = [5000, 5250, 5750, 6250, 6750]
self.citizen.details.pp = 4900 self.citizen.details.pp = 4900
self.citizen.energy.recovered = 4000 self.citizen.energy.energy = 8510
self.citizen.energy.recoverable = 4510
self.assertEqual(self.citizen.next_reachable_energy, 850) self.assertEqual(self.citizen.next_reachable_energy, 850)
self.citizen.energy.recoverable = 4490 self.citizen.energy.energy = 8490
self.assertTrue(self.citizen.should_travel_to_fight()) self.assertTrue(self.citizen.should_travel_to_fight())
self.assertEqual(self.citizen.next_reachable_energy, 350) self.assertEqual(self.citizen.next_reachable_energy, 350)
self.citizen.energy.recovered = 100 self.citizen.energy.energy = 250
self.citizen.energy.recoverable = 150
self.assertFalse(self.citizen.should_travel_to_fight()) self.assertFalse(self.citizen.should_travel_to_fight())
self.assertEqual(self.citizen.next_reachable_energy, 0) self.assertEqual(self.citizen.next_reachable_energy, 0)
def test_should_fight(self): def test_should_fight(self):
def is_wc_close(): def is_wc_close():
return self.citizen.max_time_till_full_ff > self.citizen.time_till_week_change return self.citizen.max_time_till_full_ff > self.citizen.time_till_week_change
self.citizen.config.fight = False self.citizen.config.fight = False
self.assertEqual(self.citizen.should_fight(), (0, "Fighting not allowed!", False)) self.assertEqual(self.citizen.should_fight(), (0, "Fighting not allowed!", False))
@ -76,62 +73,64 @@ class TestErepublik(unittest.TestCase):
self.citizen.energy.limit = 3000 self.citizen.energy.limit = 3000
self.citizen.details.xp = 24705 self.citizen.details.xp = 24705
if not is_wc_close: if not is_wc_close:
self.assertEqual(self.citizen.should_fight(), (0, 'Level up', False)) self.assertEqual(self.citizen.should_fight(), (0, "Level up", False))
self.citizen.energy.recovered = 3000 self.citizen.energy.energy = 5950
self.citizen.energy.recoverable = 2950
self.citizen.energy.interval = 30 self.citizen.energy.interval = 30
self.assertEqual(self.citizen.should_fight(), (900, 'Level up', True)) self.assertEqual(self.citizen.should_fight(), (900, "Level up", True))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), (900, 'Level up', True)) self.assertEqual(self.citizen.should_fight(), (900, "Level up", True))
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
# Level up reachable # Level up reachable
self.citizen.details.xp = 24400 self.citizen.details.xp = 24400
self.assertEqual(self.citizen.should_fight(), (305, 'Fighting for close Levelup. Doing 305 hits', True)) self.assertEqual(self.citizen.should_fight(), (305, "Fighting for close Levelup. Doing 305 hits", True))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), (305, 'Fighting for close Levelup. Doing 305 hits', True)) self.assertEqual(self.citizen.should_fight(), (305, "Fighting for close Levelup. Doing 305 hits", True))
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.details.xp = 21000 self.citizen.details.xp = 21000
self.assertEqual(self.citizen.should_fight(), (75, 'Obligatory fighting for at least 75pp', True)) self.assertEqual(self.citizen.should_fight(), (75, "Obligatory fighting for at least 75pp", True))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), (75, 'Obligatory fighting for at least 75pp', True)) self.assertEqual(self.citizen.should_fight(), (75, "Obligatory fighting for at least 75pp", True))
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.details.pp = 80 self.citizen.details.pp = 80
# All-in (type = all-in and full ff) # All-in (type = all-in and full ff)
self.citizen.config.all_in = True self.citizen.config.all_in = True
self.assertEqual(self.citizen.should_fight(), (595, 'Fighting all-in. Doing 595 hits', False)) self.assertEqual(self.citizen.should_fight(), (595, "Fighting all-in. Doing 595 hits", False))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), ( self.assertEqual(
435, 'Fight count modified (old count: 595 | FF: 595 | WAM ff_lockdown: 160 | New count: 435)', False self.citizen.should_fight(),
)) (435, "Fight count modified (old count: 595 | FF: 595 | WAM ff_lockdown: 160 | New count: 435)", False),
)
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.config.air = True self.citizen.config.air = True
self.citizen.energy.recoverable = 1000 self.citizen.energy.energy = 4000
self.assertEqual(self.citizen.should_fight(), (400, 'Fighting all-in in AIR. Doing 400 hits', False)) self.assertEqual(self.citizen.should_fight(), (400, "Fighting all-in in AIR. Doing 400 hits", False))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), ( self.assertEqual(
240, 'Fight count modified (old count: 400 | FF: 400 | WAM ff_lockdown: 160 | New count: 240)', False self.citizen.should_fight(),
)) (240, "Fight count modified (old count: 400 | FF: 400 | WAM ff_lockdown: 160 | New count: 240)", False),
)
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.config.all_in = False self.citizen.config.all_in = False
self.citizen.config.next_energy = True self.citizen.config.next_energy = True
self.citizen.energy.limit = 5000 self.citizen.energy.limit = 10000
self.citizen.details.next_pp = [100, 150, 250, 400, 500] self.citizen.details.next_pp = [100, 150, 250, 400, 500]
self.assertEqual(self.citizen.should_fight(), (320, 'Fighting for +1 energy. Doing 320 hits', False)) self.assertEqual(self.citizen.should_fight(), (320, "Fighting for +1 energy. Doing 320 hits", False))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), ( self.assertEqual(
160, 'Fight count modified (old count: 320 | FF: 400 | WAM ff_lockdown: 160 | New count: 160)', False self.citizen.should_fight(),
)) (160, "Fight count modified (old count: 320 | FF: 400 | WAM ff_lockdown: 160 | New count: 160)", False),
)
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.energy.limit = 3000 self.citizen.energy.limit = 3000
self.citizen.details.next_pp = [19250, 20000] self.citizen.details.next_pp = [19250, 20000]
self.citizen.config.next_energy = False self.citizen.config.next_energy = False
# 1h worth of energy # 1h worth of energy
self.citizen.energy.recoverable = 2910 self.citizen.energy.energy = 5910
self.assertEqual(self.citizen.should_fight(), (30, 'Fighting for 1h energy. Doing 30 hits', True)) self.assertEqual(self.citizen.should_fight(), (30, "Fighting for 1h energy. Doing 30 hits", True))