Compare commits

..

28 Commits

Author SHA1 Message Date
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
9 changed files with 269 additions and 153 deletions

View File

@ -4,7 +4,7 @@
__author__ = """Eriks Karls""" __author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv' __email__ = 'eriks@72.lv'
__version__ = '0.24.0.4' __version__ = '0.24.1'
from erepublik import classes, constants, utils from erepublik import classes, constants, utils
from erepublik.citizen import Citizen from erepublik.citizen import Citizen

View File

@ -5,15 +5,15 @@ import logging
import os import os
import sys import sys
import weakref import weakref
from logging import LogRecord, handlers
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Union
import requests import requests
from logging import handlers, LogRecord
from typing import Union, Dict, Any
from erepublik.classes import Reporter from erepublik.classes import Reporter
from erepublik.constants import erep_tz from erepublik.constants import erep_tz
from erepublik.utils import slugify, json_loads, json, now, json_dumps from erepublik.utils import json, json_dumps, json_loads, slugify
class ErepublikFileHandler(handlers.TimedRotatingFileHandler): class ErepublikFileHandler(handlers.TimedRotatingFileHandler):
@ -25,7 +25,7 @@ class ErepublikFileHandler(handlers.TimedRotatingFileHandler):
log_path.parent.mkdir(parents=True, exist_ok=True) 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) at_time = erep_tz.localize(datetime.datetime.now()).replace(hour=0, minute=0, second=0, microsecond=0)
kwargs.update(atTime=at_time) kwargs.update(atTime=at_time)
super().__init__(filename, *args, **kwargs) super().__init__(filename, when='d', *args, **kwargs)
def doRollover(self) -> None: def doRollover(self) -> None:
self._file_path.parent.mkdir(parents=True, exist_ok=True) self._file_path.parent.mkdir(parents=True, exist_ok=True)
@ -52,6 +52,18 @@ class ErepublikFormatter(logging.Formatter):
return datetime.datetime.utcfromtimestamp(timestamp).astimezone(erep_tz) return datetime.datetime.utcfromtimestamp(timestamp).astimezone(erep_tz)
def format(self, record: logging.LogRecord) -> str: 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: if record.levelno == logging.DEBUG:
self._fmt = self.dbg_fmt self._fmt = self.dbg_fmt
elif record.levelno == logging.INFO: elif record.levelno == logging.INFO:
@ -59,7 +71,17 @@ class ErepublikFormatter(logging.Formatter):
else: else:
self._fmt = self.default_fmt self._fmt = self.default_fmt
self._style = logging.PercentStyle(self._fmt) self._style = logging.PercentStyle(self._fmt)
return super().format(record)
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): def formatTime(self, record, datefmt=None):
dt = self.converter(record.created) dt = self.converter(record.created)
@ -69,15 +91,18 @@ class ErepublikFormatter(logging.Formatter):
s = dt.strftime('%Y-%m-%d %H:%M:%S') s = dt.strftime('%Y-%m-%d %H:%M:%S')
return s return s
def usesTime(self):
return self._style.usesTime()
class ErepublikErrorHTTTPHandler(handlers.HTTPHandler): class ErepublikErrorHTTTPHandler(handlers.HTTPHandler):
def __init__(self, reporter: Reporter): def __init__(self, reporter: Reporter):
logging.Handler.__init__(self, level=logging.ERROR) logging.Handler.__init__(self, level=logging.ERROR)
self._reporter = weakref.ref(reporter) self._reporter = weakref.ref(reporter)
self.host = 'localhost:5000' self.host = 'erep.lv'
self.url = '/ebot/error/' self.url = '/ebot/error/'
self.method = 'POST' self.method = 'POST'
self.secure = False self.secure = True
self.credentials = (str(reporter.citizen_id), reporter.key) self.credentials = (str(reporter.citizen_id), reporter.key)
self.context = None self.context = None
@ -85,10 +110,7 @@ class ErepublikErrorHTTTPHandler(handlers.HTTPHandler):
def reporter(self): def reporter(self):
return self._reporter() return self._reporter()
def mapLogRecord(self, record: logging.LogRecord) -> Dict[str, Any]: def _get_last_response(self) -> Dict[str, str]:
data = super().mapLogRecord(record)
# Log last response
response = self.reporter.citizen.r response = self.reporter.citizen.r
url = response.url url = response.url
last_index = url.index("?") if "?" in url else len(response.url) last_index = url.index("?") if "?" in url else len(response.url)
@ -107,14 +129,10 @@ class ErepublikErrorHTTTPHandler(handlers.HTTPHandler):
).replace(tzinfo=datetime.timezone.utc).astimezone(erep_tz).strftime('%F_%H-%M-%S') ).replace(tzinfo=datetime.timezone.utc).astimezone(erep_tz).strftime('%F_%H-%M-%S')
except: except:
resp_time = slugify(response.headers.get('date')) resp_time = slugify(response.headers.get('date'))
return dict(name=f"{resp_time}_{name}.{ext}", content=html.encode('utf-8'),
resp = dict(name=f"{resp_time}_{name}.{ext}", content=html.encode('utf-8'),
mimetype="application/json" if ext == 'json' else "text/html") mimetype="application/json" if ext == 'json' else "text/html")
files = [('file', (resp.get('name'), resp.get('content'), resp.get('mimetype'))), ] def _get_local_vars(self) -> str:
filename = f'log/{now().strftime("%F")}.log'
if os.path.isfile(filename):
files.append(('file', (filename[4:], open(filename, 'rb'), 'text/plain')))
trace = inspect.trace() trace = inspect.trace()
local_vars = {} local_vars = {}
if trace: if trace:
@ -122,20 +140,46 @@ class ErepublikErrorHTTTPHandler(handlers.HTTPHandler):
if local_vars.get('__name__') == '__main__': if local_vars.get('__name__') == '__main__':
local_vars.update(commit_id=local_vars.get('COMMIT_ID'), interactive=local_vars.get('INTERACTIVE'), 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')) 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 local_vars: if 'state_thread' in local_vars:
if 'state_thread' in local_vars: local_vars.pop('state_thread', None)
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)
if isinstance(local_vars.get('self'), self.reporter.citizen.__class__): def _get_instance_json(self) -> str:
local_vars['self'] = repr(local_vars['self']) if self.reporter:
if isinstance(local_vars.get('player'), self.reporter.citizen.__class__): return self.reporter.citizen.to_json(False)
local_vars['player'] = repr(local_vars['player']) return ""
if isinstance(local_vars.get('citizen'), self.reporter.citizen.__class__):
local_vars['citizen'] = repr(local_vars['citizen'])
files.append(('file', ('local_vars.json', json_dumps(local_vars), "application/json"))) def mapLogRecord(self, record: logging.LogRecord) -> Dict[str, Any]:
files.append(('file', ('instance.json', self.reporter.citizen.to_json(indent=True), "application/json"))) 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) data.update(files=files)
return data return data

View File

@ -1,7 +1,9 @@
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_toolbelt.utils import dump
from requests import Response, Session from requests import Response, Session
@ -45,6 +47,7 @@ class SlowRequests(Session):
self.request_log_name = utils.get_file(utils.now().strftime("debug/requests_%Y-%m-%d.log")) self.request_log_name = utils.get_file(utils.now().strftime("debug/requests_%Y-%m-%d.log"))
self.last_time = utils.now() self.last_time = utils.now()
self.headers.update({'User-Agent': user_agent}) self.headers.update({'User-Agent': user_agent})
self.hooks["response"] = [self._log_response]
@property @property
def as_dict(self): def as_dict(self):
@ -55,7 +58,7 @@ class SlowRequests(Session):
self._slow_down_requests() self._slow_down_requests()
self._log_request(url, method, **kwargs) self._log_request(url, method, **kwargs)
resp = super().request(method, url, *args, **kwargs) resp = super().request(method, url, *args, **kwargs)
self._log_response(url, resp) # self._log_response(resp)
return resp return resp
def _slow_down_requests(self): def _slow_down_requests(self):
@ -85,12 +88,14 @@ class SlowRequests(Session):
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"))
def _log_response(self, url, resp, redirect: bool = False): def _log_response(self, response: Response, *args, **kwargs):
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')
@ -98,13 +103,17 @@ class SlowRequests(Session):
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)
if not redirect:
data = dump.dump_all(response)
utils.write_file(f'debug/dumps/{fd_time}_{fd_name}{fd_extra}.{fd_ext}.dump', data.decode('utf8'))
class CitizenBaseAPI: class CitizenBaseAPI:
@ -152,12 +161,27 @@ class CitizenBaseAPI:
return self.post(f'{self.url}/main/sessionGetChallenge', data=data) return self.post(f'{self.url}/main/sessionGetChallenge', data=data)
def _post_main_session_unlock( def _post_main_session_unlock(
self, captcha: int, image: str, challenge: str, coords: List[Dict[str, int]], src: str self, captcha_id: int, image_id: str, challenge_id: str, coords: List[Dict[str, int]], src: str
) -> Response: ) -> Response:
env = dict(l=['tets', ], s=[], c=["l_chathwe", "l_chatroom"], m=0) if not self._req.cookies.get('l_chathwe'):
data = dict(_token=self.token, captchaId=captcha, imageId=image, challengeId=challenge, self._req.cookies.set('l_chathwe', 1, expires=int(time.time())+300, path="/en/military", domain='.www.erepublik.com', secure=True)
clickMatrix=coords, isMobile=0, env=utils.b64json(env), src=src) if self._req.cookies.get('sh'):
return self.post(f'{self.url}/main/sessionUnlock', data=data) self._req.cookies.pop('sh')
if self._req.cookies.get('ch'):
self._req.cookies.pop('ch')
c = [cookie.name for cookie in self._req.cookies if cookie.domain == '.www.erepublik.com' and not cookie.name.startswith('erpk')]
env = dict(l=['tets'], s=[], c=c + ['l_chathwe'], m=0)
cookies = dict(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)
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'})
class ErepublikAnniversaryAPI(CitizenBaseAPI): class ErepublikAnniversaryAPI(CitizenBaseAPI):
@ -490,6 +514,10 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
weaponQuality=weapon, totalEnergy=energy, **kwargs) weaponQuality=weapon, totalEnergy=energy, **kwargs)
return self.post(f"{self.url}/military/fightDeploy-startDeploy", data=data) return self.post(f"{self.url}/military/fightDeploy-startDeploy", data=data)
def _post_military_fight_deploy_deploy_report_data(self, deployment_id: int) -> Response:
data = dict(_token=self.token, deploymentId=deployment_id)
return self.post(f"{self.url}/military/fightDeploy-deployReportData", data=data)
class ErepublikPoliticsAPI(CitizenBaseAPI): class ErepublikPoliticsAPI(CitizenBaseAPI):
def _get_candidate_party(self, party_slug: str) -> Response: def _get_candidate_party(self, party_slug: str) -> Response:

View File

@ -9,11 +9,11 @@ from threading import Event
from time import sleep from time import sleep
from typing import Any, Dict, List, NoReturn, Optional, Set, Tuple, Union from typing import Any, Dict, List, NoReturn, Optional, Set, Tuple, Union
from requests import HTTPError, RequestException, Response from requests import RequestException, Response, HTTPError
from . import access_points, classes, constants, types, utils from . import access_points, classes, constants, _types as types, utils
from .classes import OfferItem from .classes import OfferItem
from .logging import ErepublikLogConsoleHandler, ErepublikFormatter, ErepublikFileHandler, ErepublikErrorHTTTPHandler from ._logging import ErepublikErrorHTTTPHandler, ErepublikFileHandler, ErepublikFormatter, ErepublikLogConsoleHandler
class BaseCitizen(access_points.CitizenAPI): class BaseCitizen(access_points.CitizenAPI):
@ -93,7 +93,7 @@ class BaseCitizen(access_points.CitizenAPI):
self.token = re_login_token.group(1) self.token = re_login_token.group(1)
self._login() self._login()
else: else:
self.logger.error("Something went wrong! Can't find token in page!") self.report_error("Something went wrong! Can't find token in page!")
raise classes.ErepublikException("Something went wrong! Can't find token in page! Exiting!") raise classes.ErepublikException("Something went wrong! Can't find token in page! Exiting!")
try: try:
self.update_citizen_info(resp.text) self.update_citizen_info(resp.text)
@ -111,8 +111,8 @@ class BaseCitizen(access_points.CitizenAPI):
else: else:
try: try:
response = super().get(url, **kwargs) response = super().get(url, **kwargs)
except RequestException as e: except RequestException:
self.logger.error('Network error while issuing GET request', exc_info=e) self.report_error('Network error while issuing GET request')
self.sleep(60) self.sleep(60)
return self.get(url, **kwargs) return self.get(url, **kwargs)
@ -144,15 +144,15 @@ class BaseCitizen(access_points.CitizenAPI):
try: try:
response = super().post(url, data=data, json=json, **kwargs) response = super().post(url, data=data, json=json, **kwargs)
except RequestException as e: except RequestException:
self.logger.error('Network error while issuing POST request', exc_info=e) self.report_error('Network error while issuing POST request')
self.sleep(60) self.sleep(60)
return self.post(url, data=data, json=json, **kwargs) return self.post(url, data=data, json=json, **kwargs)
try: try:
r_json = response.json() r_json = response.json()
if (r_json.get('error') or not r_json.get('status')) and r_json.get('message', '') == 'captcha': if (r_json.get('error') or not r_json.get('status')) and r_json.get('message', '') == 'captcha':
self.logger.warning('Regular captcha must be filled!', extra=r_json) self.write_warning('Regular captcha must be filled!', extra=r_json)
except (AttributeError, utils.json.JSONDecodeError, ValueError, KeyError): except (AttributeError, utils.json.JSONDecodeError, ValueError, KeyError):
pass pass
@ -255,16 +255,24 @@ class BaseCitizen(access_points.CitizenAPI):
data = utils.json_loads(utils.normalize_html_json(data.group(1))) data = utils.json_loads(utils.normalize_html_json(data.group(1)))
captcha_id = data.get('sessionValidation', {}).get("captchaId") captcha_id = data.get('sessionValidation', {}).get("captchaId")
captcha_data = self._post_main_session_get_challenge(captcha_id).json() captcha_data = self._post_main_session_get_challenge(captcha_id).json()
coordinates = self.solve_captcha(captcha_data.get('src')) coordinates = self.solve_captcha(captcha_data['src'])
r = self._post_main_session_unlock( while True:
captcha_id, captcha_data['imageId'], captcha_data['challengeId'], coordinates, captcha_data['src'] r = self._post_main_session_unlock(
).json() captcha_id, captcha_data['imageId'], captcha_data['challengeId'], coordinates, captcha_data['src']
if not r.get('error') and r.get('verified'): )
return True rj = r.json()
else: if not rj.get('error') and rj.get('verified'):
self.report_error('Captcha failed!') return True
if retry < 6: else:
return self.do_captcha_challenge(retry + 1) try:
raise classes.ErepublikException('Captcha failed!')
except classes.ErepublikException:
self.report_error('Captcha failed!')
captcha_data = self._post_main_session_get_challenge(captcha_id, captcha_data['imageId']).json()
coordinates = self.solve_captcha(captcha_data['src'])
if retry < 1:
return False
retry -= 1
return False return False
def refresh_captcha_image(self, captcha_id: int, image_id: str): def refresh_captcha_image(self, captcha_id: int, image_id: str):
@ -477,11 +485,17 @@ class BaseCitizen(access_points.CitizenAPI):
def write_log(self, msg: str): def write_log(self, msg: str):
self.logger.info(msg) self.logger.info(msg)
def report_error(self, msg: str = "", is_warning: bool = False): def write_warning(self, msg: str = "", extra: Dict[str, Any] = None):
if is_warning: if extra is None:
self.logger.warning(msg) extra = {}
else: extra.update(erep_version=utils.VERSION)
self.logger.error(msg) self.logger.warning(msg, extra=extra)
def report_error(self, msg: str = "", extra: Dict[str, Any] = None):
if extra is None:
extra = {}
extra.update(erep_version=utils.VERSION)
self.logger.error(msg, exc_info=True, stack_info=True, extra=extra)
def sleep(self, seconds: Union[int, float, Decimal]): def sleep(self, seconds: Union[int, float, Decimal]):
if seconds < 0: if seconds < 0:
@ -539,9 +553,15 @@ class BaseCitizen(access_points.CitizenAPI):
def dump_instance(self): def dump_instance(self):
filename = f"{self.__class__.__name__}__dump.json" filename = f"{self.__class__.__name__}__dump.json"
cookie_attrs = [
"version", "name", "value", "port", "domain", "path", "secure",
"expires", "discard", "comment", "comment_url", "rfc2109"
]
cookies = [{attr: getattr(cookie, attr) for attr in cookie_attrs} for cookie in self._req.cookies]
with open(filename, 'w') as f: with open(filename, 'w') as f:
utils.json_dump(dict(config=self.config, cookies=self._req.cookies.get_dict(), utils.json_dump(dict(config=self.config, cookies=cookies,
user_agent=self._req.headers.get("User-Agent")), f,) user_agent=self._req.headers.get("User-Agent")), f)
self.logger.debug(f"Session saved to: '{filename}'") self.logger.debug(f"Session saved to: '{filename}'")
@classmethod @classmethod
@ -549,7 +569,8 @@ class BaseCitizen(access_points.CitizenAPI):
with open(dump_name) as f: with open(dump_name) as f:
data = utils.json.load(f, object_hook=utils.json_decode_object_hook) data = utils.json.load(f, object_hook=utils.json_decode_object_hook)
player = cls(data['config']['email'], "") player = cls(data['config']['email'], "")
player._req.cookies.update(data['cookies']) for cookie in data['cookies']:
player._req.cookies.set(**cookie)
player._req.headers.update({"User-Agent": data['user_agent']}) player._req.headers.update({"User-Agent": data['user_agent']})
for k, v in data.get('config', {}).items(): for k, v in data.get('config', {}).items():
if hasattr(player.config, k): if hasattr(player.config, k):
@ -568,7 +589,7 @@ class BaseCitizen(access_points.CitizenAPI):
self.write_log(f"Resumed as: {self.name}") self.write_log(f"Resumed as: {self.name}")
if re.search('<div id="accountSecurity" class="it-hurts-when-ip">', resp.text): if re.search('<div id="accountSecurity" class="it-hurts-when-ip">', resp.text):
self.restricted_ip = True self.restricted_ip = True
# self.report_error("eRepublik has blacklisted IP. Limited functionality!", True) self.write_warning("eRepublik has blacklisted IP. Limited functionality!")
self.logged_in = True self.logged_in = True
self.get_csrf_token() self.get_csrf_token()
@ -774,7 +795,7 @@ class BaseCitizen(access_points.CitizenAPI):
self.r = r self.r = r
if r.url == f"{self.url}/login": if r.url == f"{self.url}/login":
self.logger.error("Citizen email and/or password is incorrect!") self.report_error("Citizen email and/or password is incorrect!")
else: else:
re_name_id = re.search(r'<a data-fblog="profile_avatar" href="/en/citizen/profile/(\d+)" ' re_name_id = re.search(r'<a data-fblog="profile_avatar" href="/en/citizen/profile/(\d+)" '
r'class="user_avatar" title="(.*?)">', r.text) r'class="user_avatar" title="(.*?)">', r.text)
@ -785,7 +806,7 @@ class BaseCitizen(access_points.CitizenAPI):
self.get_csrf_token() self.get_csrf_token()
if re.search('<div id="accountSecurity" class="it-hurts-when-ip">', self.r.text): if re.search('<div id="accountSecurity" class="it-hurts-when-ip">', self.r.text):
self.restricted_ip = True self.restricted_ip = True
# self.report_error('eRepublik has blacklisted IP. Limited functionality!', True) self.write_warning('eRepublik has blacklisted IP. Limited functionality!', )
self.logged_in = True self.logged_in = True
@ -793,7 +814,7 @@ class BaseCitizen(access_points.CitizenAPI):
try: try:
j = response.json() j = response.json()
if j['error'] and j['message'] == 'Too many requests': if j['error'] and j['message'] == 'Too many requests':
self.logger.warning('Made too many requests! Sleeping for 30 seconds.') self.write_warning('Made too many requests! Sleeping for 30 seconds.')
self.sleep(30) self.sleep(30)
except (utils.json.JSONDecodeError, KeyError, TypeError): except (utils.json.JSONDecodeError, KeyError, TypeError):
pass pass
@ -806,20 +827,20 @@ class BaseCitizen(access_points.CitizenAPI):
if self.restricted_ip: if self.restricted_ip:
self._req.cookies.clear() self._req.cookies.clear()
return True return True
self.logger.warning('eRepublik servers are having internal troubles. Sleeping for 5 minutes') self.write_warning('eRepublik servers are having internal troubles. Sleeping for 1 minutes')
self.sleep(5 * 60) self.sleep(1 * 60)
else: else:
raise classes.ErepublikException(f"HTTP {response.status_code} error!") raise classes.ErepublikException(f"HTTP {response.status_code} error!")
if re.search(r'Occasionally there are a couple of things which we need to check or to implement in order make ' if re.search(r'Occasionally there are a couple of things which we need to check or to implement in order make '
r'your experience in eRepublik more pleasant. <strong>Don\'t worry about ongoing battles, timer ' r'your experience in eRepublik more pleasant. <strong>Don\'t worry about ongoing battles, timer '
r'will be stopped during maintenance.</strong>', response.text): r'will be stopped during maintenance.</strong>', response.text):
self.logger.warning('eRepublik is having maintenance. Sleeping for 5 mi#nutes') self.write_warning('eRepublik is having maintenance. Sleeping for 5 mi#nutes')
self.sleep(5 * 60) self.sleep(5 * 60)
return True return True
elif re.search('We are experiencing some tehnical dificulties', response.text): elif re.search('We are experiencing some tehnical dificulties', response.text):
self.logger.warning('eRepublik is having technical difficulties. Sleeping for 5 minutes') self.write_warning('eRepublik is having technical difficulties. Sleeping for 5 minutes')
self.sleep(5 * 60) self.sleep(5 * 60)
return True return True
@ -840,7 +861,7 @@ class BaseCitizen(access_points.CitizenAPI):
kwargs = utils.json_loads(utils.json_dumps(kwargs or {})) kwargs = utils.json_loads(utils.json_dumps(kwargs or {}))
action = action[:32] action = action[:32]
if msg.startswith('Unable to'): if msg.startswith('Unable to'):
self.logger.warning(msg) self.write_warning(msg)
else: else:
self.write_log(msg) self.write_log(msg)
if self.reporter.allowed: if self.reporter.allowed:
@ -868,7 +889,7 @@ class CitizenAnniversary(BaseCitizen):
def spin_wheel_of_fortune(self, max_cost=0, spin_count=0): def spin_wheel_of_fortune(self, max_cost=0, spin_count=0):
if not self.config.spin_wheel_of_fortune: if not self.config.spin_wheel_of_fortune:
self.logger.warning("Unable to spin wheel of fortune because 'config.spin_wheel_of_fortune' is False") self.write_warning("Unable to spin wheel of fortune because 'config.spin_wheel_of_fortune' is False")
return return
def _write_spin_data(cost: int, prize: str): def _write_spin_data(cost: int, prize: str):
@ -1074,7 +1095,7 @@ class CitizenCompanies(BaseCitizen):
if wam_list: if wam_list:
data.update(extra) data.update(extra)
if not self.details.current_region == wam_holding.region: if not self.details.current_region == wam_holding.region:
self.logger.warning("Unable to work as manager because of location - please travel!") self.write_warning("Unable to work as manager because of location - please travel!")
return return
employ_factories = self.my_companies.get_employable_factories() employ_factories = self.my_companies.get_employable_factories()
@ -1167,7 +1188,7 @@ class CitizenEconomy(CitizenTravel):
if buy is None: if buy is None:
pass pass
elif buy['error']: elif buy['error']:
self.logger.warning(f'Unable to buy q{q} house! \n{buy["message"]}') self.write_warning(f'Unable to buy q{q} house! {buy["message"]}')
else: else:
ok_to_activate = True ok_to_activate = True
else: else:
@ -1273,7 +1294,7 @@ class CitizenEconomy(CitizenTravel):
if isinstance(industry, str): if isinstance(industry, str):
industry = constants.INDUSTRIES[industry] industry = constants.INDUSTRIES[industry]
if not constants.INDUSTRIES[industry]: if not constants.INDUSTRIES[industry]:
self.logger.warning(f"Trying to sell unsupported industry {industry}") self.write_warning(f"Trying to sell unsupported industry {industry}")
_inv_qlt = quality if industry in [1, 2, 3, 4, 23] else 0 _inv_qlt = quality if industry in [1, 2, 3, 4, 23] else 0
final_kind = industry in [1, 2, 4, 23] final_kind = industry in [1, 2, 4, 23]
@ -1328,7 +1349,7 @@ class CitizenEconomy(CitizenTravel):
quality = 1 quality = 1
product_name = raw_short_names[product_name] product_name = raw_short_names[product_name]
elif not constants.INDUSTRIES[product_name]: elif not constants.INDUSTRIES[product_name]:
self.logger.error(f"Industry '{product_name}' not implemented") self.report_error(f"Industry '{product_name}' not implemented")
raise classes.ErepublikException(f"Industry '{product_name}' not implemented") raise classes.ErepublikException(f"Industry '{product_name}' not implemented")
offers: Dict[str, classes.OfferItem] = {} offers: Dict[str, classes.OfferItem] = {}
@ -1385,7 +1406,7 @@ class CitizenEconomy(CitizenTravel):
self.update_inventory() self.update_inventory()
else: else:
s = f"Don't have enough money! Needed: {amount * cheapest.price}cc, Have: {self.details.cc}cc" s = f"Don't have enough money! Needed: {amount * cheapest.price}cc, Have: {self.details.cc}cc"
self.logger.warning(s) self.write_warning(s)
self._report_action('BUY_FOOD', s) self._report_action('BUY_FOOD', s)
def get_monetary_offers(self, currency: int = 62) -> List[Dict[str, Union[int, float]]]: def get_monetary_offers(self, currency: int = 62) -> List[Dict[str, Union[int, float]]]:
@ -1449,7 +1470,7 @@ class CitizenEconomy(CitizenTravel):
self._report_action('DONATE_ITEMS', msg) self._report_action('DONATE_ITEMS', msg)
return amount return amount
elif re.search('You must wait 5 seconds before donating again', response.text): elif re.search('You must wait 5 seconds before donating again', response.text):
self.logger.warning('Previous donation failed! Must wait at least 5 seconds before next donation!') self.write_warning('Previous donation failed! Must wait at least 5 seconds before next donation!')
self.sleep(5) self.sleep(5)
return self.donate_items(citizen_id, int(amount), industry_id, quality) return self.donate_items(citizen_id, int(amount), industry_id, quality)
else: else:
@ -1601,7 +1622,7 @@ class CitizenMedia(BaseCitizen):
kwargs=article_data) kwargs=article_data)
self._get_main_delete_article(article_id) self._get_main_delete_article(article_id)
else: else:
self.logger.warning(f"Unable to delete article (#{article_id})!") self.write_warning(f"Unable to delete article (#{article_id})!")
class CitizenMilitary(CitizenTravel): class CitizenMilitary(CitizenTravel):
@ -1886,7 +1907,7 @@ class CitizenMilitary(CitizenTravel):
:rtype: int :rtype: int
""" """
if self.restricted_ip: if self.restricted_ip:
self.logger.warning('Fighting is not allowed from restricted IP!') self.write_warning('Fighting is not allowed from restricted IP!')
self._report_action('IP_BLACKLISTED', 'Fighting is not allowed from restricted IP!') self._report_action('IP_BLACKLISTED', 'Fighting is not allowed from restricted IP!')
return 1 return 1
if not division.is_air and self.config.boosters: if not division.is_air and self.config.boosters:
@ -1894,32 +1915,45 @@ class CitizenMilitary(CitizenTravel):
if side is None: if side is None:
side = battle.defender if self.details.citizenship in battle.defender.allies + [ side = battle.defender if self.details.citizenship in battle.defender.allies + [
battle.defender.country] else battle.invader battle.defender.country] else battle.invader
error_count = 0
ok_to_fight = True
if count is None: if count is None:
count = self.should_fight()[0] count = self.should_fight()[0]
self.write_log(f"Fighting in battle for {battle.region_name} on {side} side in d{division.div}") self.write_log(f"Fighting in battle for {battle.region_name} on {side} side in d{division.div}")
total_damage = 0 if self.now < utils.localize_dt(datetime(2021, 2, 8)):
total_hits = 0 error_count = total_damage = total_hits = 0
while ok_to_fight and error_count < 10 and count > 0: ok_to_fight = True
while all((count > 0, error_count < 10, self.energy.recovered >= 50)): while ok_to_fight and error_count < 10 and count > 0:
hits, error, damage = self._shoot(battle, division, side) while all((count > 0, error_count < 10, self.energy.recovered >= 50)):
count -= hits hits, error, damage = self._shoot(battle, division, side)
total_hits += hits count -= hits
total_damage += damage total_hits += hits
error_count += error total_damage += damage
else: error_count += error
self._eat('blue') else:
if count > 0 and self.energy.recovered < 50 and use_ebs: self._eat('blue')
self._eat('orange') if count > 0 and self.energy.recovered < 50 and use_ebs:
if self.energy.recovered < 50 or error_count >= 10 or count <= 0: self._eat('orange')
self.write_log(f"Hits: {total_hits:>4} | Damage: {total_damage}") if self.energy.recovered < 50 or error_count >= 10 or count <= 0:
ok_to_fight = False self.write_log(f"Hits: {total_hits:>4} | Damage: {total_damage}")
if total_damage: ok_to_fight = False
self.report_fighting(battle, not side.is_defender, division, total_damage, total_hits) if total_damage:
return error_count self.report_fighting(battle, not side.is_defender, division, total_damage, total_hits)
return error_count
else:
deployment_id = self.deploy(division, side, count * 10)
self.sleep(count // 3) # TODO: connect to eRepublik's WS and get from there when deploy ends
energy_used = 0
if deployment_id:
self.write_warning('If erepublik responds with HTTP 500 Internal Error, it is kind of ok, because deployment has not finished yet.')
deployment_data = self._post_military_fight_deploy_deploy_report_data(deployment_id).json()
if not deployment_data.get('error'):
data = deployment_data['data']
total_damage = int(data['damage'].replace(',', ''))
energy_used = int(data['energySpent'].replace(',', ''))
self.details.pp += int(data['rewards']['prestigePoints'].replace(',', ''))
self.report_fighting(battle, not side.is_defender, division, total_damage, energy_used // 10)
return energy_used
def _shoot(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide): def _shoot(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide):
if division.is_air: if division.is_air:
@ -1943,13 +1977,13 @@ class CitizenMilitary(CitizenTravel):
elif r_json.get('message') == 'NOT_ENOUGH_WEAPONS': elif r_json.get('message') == 'NOT_ENOUGH_WEAPONS':
self.set_default_weapon(battle, division) self.set_default_weapon(battle, division)
elif r_json.get('message') == "Cannot activate a zone with a non-native division": elif r_json.get('message') == "Cannot activate a zone with a non-native division":
self.logger.warning('Wrong division!!') self.write_warning('Wrong division!!')
return 0, 10, 0 return 0, 10, 0
elif r_json.get('message') == 'ZONE_INACTIVE': elif r_json.get('message') == 'ZONE_INACTIVE':
self.logger.warning('Wrong division!!') self.write_warning('Wrong division!!')
return 0, 10, 0 return 0, 10, 0
elif r_json.get('message') == 'NON_BELLIGERENT': elif r_json.get('message') == 'NON_BELLIGERENT':
self.logger.warning("Dictatorship/Liberation wars are not supported!") self.write_warning("Dictatorship/Liberation wars are not supported!")
return 0, 10, 0 return 0, 10, 0
elif r_json.get('message') in ['FIGHT_DISABLED', 'DEPLOYMENT_MODE']: elif r_json.get('message') in ['FIGHT_DISABLED', 'DEPLOYMENT_MODE']:
self._post_main_profile_update('options', self._post_main_profile_update('options',
@ -2049,7 +2083,7 @@ class CitizenMilitary(CitizenTravel):
""" """
resp = self._post_main_battlefield_change_division(battle.id, division.id, side.id if side else None) resp = self._post_main_battlefield_change_division(battle.id, division.id, side.id if side else None)
if resp.json().get('error'): if resp.json().get('error'):
self.logger.warning(resp.json().get('message')) self.write_warning(resp.json().get('message'))
return False return False
self._report_action('MILITARY_DIV_SWITCH', f"Switched to d{division.div} in battle {battle.id}", self._report_action('MILITARY_DIV_SWITCH', f"Switched to d{division.div} in battle {battle.id}",
kwargs=resp.json()) kwargs=resp.json())
@ -2296,11 +2330,16 @@ class CitizenMilitary(CitizenTravel):
# self.buy_food() # self.buy_food()
# return self.get_deploy_inventory(division, side) # return self.get_deploy_inventory(division, side)
if ret.get('captcha'): if ret.get('captcha'):
while not self.do_captcha_challenge(): self.do_captcha_challenge()
self.sleep(5) if ret.get('error'):
if ret.get('message') == 'Deployment disabled.':
self._post_main_profile_update('options', params='{"optionName":"enable_web_deploy","optionValue":"on"}')
return self.get_deploy_inventory(division, side)
else:
self.report_error(f"Unable to get deployment inventory because: {ret.get('message')}")
return ret return ret
def deploy(self, division: classes.BattleDivision, side: classes.BattleSide, energy: int): def deploy(self, division: classes.BattleDivision, side: classes.BattleSide, energy: int, _retry=0) -> int:
_energy = int(energy) _energy = int(energy)
deploy_inv = self.get_deploy_inventory(division, side) deploy_inv = self.get_deploy_inventory(division, side)
if not deploy_inv['minEnergy'] <= energy <= deploy_inv['maxEnergy']: if not deploy_inv['minEnergy'] <= energy <= deploy_inv['maxEnergy']:
@ -2312,8 +2351,8 @@ class CitizenMilitary(CitizenTravel):
if source['type'] == 'pool': if source['type'] == 'pool':
_energy -= source['energy'] _energy -= source['energy']
elif source['type'] in ['food', 'energy_bar']: elif source['type'] in ['food', 'energy_bar']:
recovers = source['energy'] / source['amount'] recovers = source['energy'] // source['amount']
amount = recoverable // recovers amount = (recoverable if source['type'] == 'food' else _energy) // recovers
amount = amount if amount < source['amount'] else source['amount'] amount = amount if amount < source['amount'] else source['amount']
if amount > 0: if amount > 0:
energy_sources.update({f'energySources[{source_idx}][quality]': source['quality']}) energy_sources.update({f'energySources[{source_idx}][quality]': source['quality']})
@ -2322,7 +2361,10 @@ class CitizenMilitary(CitizenTravel):
used_energy = amount * recovers used_energy = amount * recovers
recoverable -= used_energy recoverable -= used_energy
_energy -= used_energy _energy -= used_energy
energy -= _energy if _energy <= 0:
break
if _energy > 0:
energy -= _energy
weapon_q = -1 weapon_q = -1
weapon_strength = 0 weapon_strength = 0
if not division.is_air: if not division.is_air:
@ -2332,10 +2374,16 @@ class CitizenMilitary(CitizenTravel):
r = self._post_fight_deploy_start_deploy( r = self._post_fight_deploy_start_deploy(
division.battle.id, side.id, division.id, energy, weapon_q, **energy_sources division.battle.id, side.id, division.id, energy, weapon_q, **energy_sources
).json() ).json()
self.write_log(r.get('message'))
if r.get('error'): if r.get('error'):
self.logger.error(f"Deploy failed: '{r.get('message')}'") self.report_error(f"Deploy failed: '{r.get('message')}'")
return energy if r.get('message') == 'Deployment disabled.':
self._post_main_profile_update('options', params='{"optionName":"enable_web_deploy","optionValue":"on"}')
if _retry < 5:
return self.deploy(division, side, energy, _retry+1)
else:
self.report_error('Unable to deploy 5 times!')
return 0
return r.get('deploymentId')
class CitizenPolitics(BaseCitizen): class CitizenPolitics(BaseCitizen):
@ -2408,7 +2456,7 @@ class CitizenSocial(BaseCitizen):
def add_every_player_as_friend(self): def add_every_player_as_friend(self):
cities = [] cities = []
cities_dict = {} cities_dict = {}
self.logger.warning('his will take a lot of time.') self.write_warning('This will take a lot of time.')
rj = self._post_main_travel_data(regionId=662, check='getCountryRegions').json() rj = self._post_main_travel_data(regionId=662, check='getCountryRegions').json()
for region_data in rj.get('regions', {}).values(): for region_data in rj.get('regions', {}).values():
cities.append(region_data['cityId']) cities.append(region_data['cityId'])
@ -2519,7 +2567,7 @@ class CitizenTasks(CitizenEconomy):
self._eat('blue') self._eat('blue')
if self.energy.food_fights < 1: if self.energy.food_fights < 1:
seconds = (self.energy.reference_time - self.now).total_seconds() seconds = (self.energy.reference_time - self.now).total_seconds()
self.logger.warning(f"I don't have energy to work. Will sleep for {seconds}s") self.write_warning(f"I don't have energy to work. Will sleep for {seconds}s")
self.sleep(seconds) self.sleep(seconds)
self._eat('blue') self._eat('blue')
self.work() self.work()
@ -2549,7 +2597,7 @@ class CitizenTasks(CitizenEconomy):
if self.energy.food_fights < len(tgs): if self.energy.food_fights < len(tgs):
large = max(self.energy.reference_time, self.now) large = max(self.energy.reference_time, self.now)
sleep_seconds = utils.get_sleep_seconds(large) sleep_seconds = utils.get_sleep_seconds(large)
self.logger.warning(f"I don't have energy to train. Will sleep for {sleep_seconds} seconds") self.write_warning(f"I don't have energy to train. Will sleep for {sleep_seconds} seconds")
self.sleep(sleep_seconds) self.sleep(sleep_seconds)
self._eat('blue') self._eat('blue')
self.train() self.train()
@ -2573,7 +2621,7 @@ class CitizenTasks(CitizenEconomy):
if self.energy.food_fights < 1: if self.energy.food_fights < 1:
large = max(self.energy.reference_time, self.now) large = max(self.energy.reference_time, self.now)
sleep_seconds = utils.get_sleep_seconds(large) sleep_seconds = utils.get_sleep_seconds(large)
self.logger.warning(f"I don't have energy to work OT. Will sleep for {sleep_seconds}s") self.write_warning(f"I don't have energy to work OT. Will sleep for {sleep_seconds}s")
self.sleep(sleep_seconds) self.sleep(sleep_seconds)
self._eat('blue') self._eat('blue')
self.work_ot() self.work_ot()
@ -2653,7 +2701,7 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
if hasattr(self.config, key): if hasattr(self.config, key):
setattr(self.config, key, value) setattr(self.config, key, value)
else: else:
self.logger.warning(f"Unknown config parameter! ({key}={value})") self.write_warning(f"Unknown config parameter! ({key}={value})")
def login(self): def login(self):
self.get_csrf_token() self.get_csrf_token()
@ -2666,12 +2714,8 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
self.name) self.name)
self.telegram.send_message(f"*Started* {utils.now():%F %T}") self.telegram.send_message(f"*Started* {utils.now():%F %T}")
self.init_logger()
self.update_all(True) self.update_all(True)
for handler in self.logger.handlers:
if isinstance(handler, ErepublikErrorHTTTPHandler):
self.logger.removeHandler(handler)
break
self.logger.addHandler(ErepublikErrorHTTTPHandler(self.reporter))
def update_citizen_info(self, html: str = None): def update_citizen_info(self, html: str = None):
""" """
@ -2689,7 +2733,7 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
if self.details.gold >= 54: if self.details.gold >= 54:
self.buy_tg_contract() self.buy_tg_contract()
else: else:
self.logger.warning('Training ground contract active but ' self.write_warning('Training ground contract active but '
f"don't have enough gold ({self.details.gold}g {self.details.cc}cc)") f"don't have enough gold ({self.details.gold}g {self.details.cc}cc)")
if self.energy.is_energy_full and self.config.telegram: if self.energy.is_energy_full and self.config.telegram:
self.telegram.report_full_energy(self.energy.available, self.energy.limit, self.energy.interval) self.telegram.report_full_energy(self.energy.available, self.energy.limit, self.energy.interval)
@ -2849,15 +2893,15 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
else: else:
self.logger.debug("I don't want to eat right now!") self.logger.debug("I don't want to eat right now!")
else: else:
self.logger.warning(f"I'm out of food! But I'll try to buy some!\n{self.food}") self.write_warning(f"I'm out of food! But I'll try to buy some!\n{self.food}")
self.buy_food() self.buy_food()
if self.food['total'] > self.energy.interval: if self.food['total'] > self.energy.interval:
super().eat() super().eat()
else: else:
self.logger.warning('I failed to buy food') self.write_warning('I failed to buy food')
def eat_eb(self): def eat_eb(self):
self.logger.warning('Eating energy bar') self.write_warning('Eating energy bar')
if self.energy.recoverable: if self.energy.recoverable:
self._eat('blue') self._eat('blue')
self._eat('orange') self._eat('orange')
@ -2921,7 +2965,7 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
if not rj.get('error'): if not rj.get('error'):
amount_needed -= amount amount_needed -= amount
else: else:
self.logger.warning(rj.get('message', "")) self.write_warning(rj.get('message', ""))
self._report_action( self._report_action(
'ECONOMY_BUY', f"Unable to buy products! Reason: {rj.get('message')}", kwargs=rj 'ECONOMY_BUY', f"Unable to buy products! Reason: {rj.get('message')}", kwargs=rj
) )
@ -2940,11 +2984,11 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
self._wam(holding) self._wam(holding)
elif response.get('message') == 'tax_money': elif response.get('message') == 'tax_money':
self._report_action('WORK_AS_MANAGER', 'Not enough money to work as manager!', kwargs=response) self._report_action('WORK_AS_MANAGER', 'Not enough money to work as manager!', kwargs=response)
self.logger.warning('Not enough money to work as manager!') self.write_warning('Not enough money to work as manager!')
else: else:
msg = f'I was not able to wam and or employ because:\n{response}' msg = f'I was not able to wam and or employ because:\n{response}'
self._report_action('WORK_AS_MANAGER', f'Worked as manager failed: {msg}', kwargs=response) self._report_action('WORK_AS_MANAGER', f'Worked as manager failed: {msg}', kwargs=response)
self.logger.warning(msg) self.write_warning(msg)
def work_as_manager(self) -> bool: def work_as_manager(self) -> bool:
""" Does Work as Manager in all holdings with wam. If employees assigned - work them also """ Does Work as Manager in all holdings with wam. If employees assigned - work them also
@ -2986,7 +3030,7 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
self.travel_to_residence() self.travel_to_residence()
return bool(wam_count) return bool(wam_count)
else: else:
self.logger.warning('Did not WAM because I would mess up levelup!') self.write_warning('Did not WAM because I would mess up levelup!')
self.my_companies.ff_lockdown = 0 self.my_companies.ff_lockdown = 0
self.update_companies() self.update_companies()
@ -3028,7 +3072,7 @@ class Citizen(_Citizen):
def update_weekly_challenge(self): def update_weekly_challenge(self):
if not self._update_lock.wait(self._update_timeout): if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!' e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._update_lock.clear() self._update_lock.clear()
@ -3039,7 +3083,7 @@ class Citizen(_Citizen):
def update_companies(self): def update_companies(self):
if not self._update_lock.wait(self._update_timeout): if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!' e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._update_lock.clear() self._update_lock.clear()
@ -3050,7 +3094,7 @@ class Citizen(_Citizen):
def update_war_info(self): def update_war_info(self):
if not self._update_lock.wait(self._update_timeout): if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!' e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._update_lock.clear() self._update_lock.clear()
@ -3061,7 +3105,7 @@ class Citizen(_Citizen):
def update_job_info(self): def update_job_info(self):
if not self._update_lock.wait(self._update_timeout): if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!' e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._update_lock.clear() self._update_lock.clear()
@ -3072,7 +3116,7 @@ class Citizen(_Citizen):
def update_money(self, page: int = 0, currency: int = 62): def update_money(self, page: int = 0, currency: int = 62):
if not self._update_lock.wait(self._update_timeout): if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!' e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._update_lock.clear() self._update_lock.clear()
@ -3083,7 +3127,7 @@ class Citizen(_Citizen):
def update_inventory(self): def update_inventory(self):
if not self._update_lock.wait(self._update_timeout): if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!' e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._update_lock.clear() self._update_lock.clear()
@ -3094,7 +3138,7 @@ class Citizen(_Citizen):
def _work_as_manager(self, wam_holding: classes.Holding) -> Optional[Dict[str, Any]]: def _work_as_manager(self, wam_holding: classes.Holding) -> Optional[Dict[str, Any]]:
if not self._concurrency_lock.wait(self._concurrency_timeout): if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!' e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._concurrency_lock.clear() self._concurrency_lock.clear()
@ -3106,7 +3150,7 @@ class Citizen(_Citizen):
count: int = None, use_ebs: bool = False) -> Optional[int]: count: int = None, use_ebs: bool = False) -> Optional[int]:
if not self._concurrency_lock.wait(self._concurrency_timeout): if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!' e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._concurrency_lock.clear() self._concurrency_lock.clear()
@ -3118,7 +3162,7 @@ class Citizen(_Citizen):
count: int = 1) -> Optional[int]: count: int = 1) -> Optional[int]:
if not self._concurrency_lock.wait(self._concurrency_timeout): if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!' e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._concurrency_lock.clear() self._concurrency_lock.clear()
@ -3129,7 +3173,7 @@ class Citizen(_Citizen):
def buy_market_offer(self, offer: OfferItem, amount: int = None) -> Optional[Dict[str, Any]]: def buy_market_offer(self, offer: OfferItem, amount: int = None) -> Optional[Dict[str, Any]]:
if not self._concurrency_lock.wait(self._concurrency_timeout): if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!' e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.report_error(e, not self.debug) self.report_error(e)
return None return None
try: try:
self._concurrency_lock.clear() self._concurrency_lock.clear()

View File

@ -7,7 +7,7 @@ from typing import Any, Dict, Generator, Iterable, List, NamedTuple, NoReturn, U
from requests import Response, Session, post from requests import Response, Session, post
from . import constants, types, utils from . import constants, _types as types, utils
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException', __all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
'ErepublikNetworkException', 'EnergyToFight', 'Holding', 'Inventory', 'MyCompanies', 'OfferItem', 'Politics', 'ErepublikNetworkException', 'EnergyToFight', 'Holding', 'Inventory', 'MyCompanies', 'OfferItem', 'Politics',

View File

@ -296,7 +296,7 @@ def 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).replace(' ', '').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):
@ -305,7 +305,7 @@ def b64json(obj: Union[Dict[str, Union[int, List[str]]], List[str]]):
else: else:
from .classes import ErepublikException from .classes import ErepublikException
raise ErepublikException(f'Unhandled object type! obj is {type(obj)}') raise ErepublikException(f'Unhandled object type! obj is {type(obj)}')
return b64encode(json.dumps(obj).encode('utf-8')).decode('utf-8') return b64encode(json.dumps(obj).replace(' ', '').encode('utf-8')).decode('utf-8')
class ErepublikJSONEncoder(json.JSONEncoder): class ErepublikJSONEncoder(json.JSONEncoder):

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.24.0.4 current_version = 0.24.1
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+)?

View File

@ -50,6 +50,6 @@ setup(
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.4', version='0.24.1',
zip_safe=False, zip_safe=False,
) )