Compare commits

..

12 Commits

Author SHA1 Message Date
995b45464f Bump version: 0.23.4.11 → 0.23.4.12 2021-01-20 16:12:51 +02:00
ebe28c948a requirement update 2021-01-20 16:12:40 +02:00
34d7230faf Response serialization update to include status code. Json encoder and decoder updates 2021-01-20 16:07:19 +02:00
3235991cce User agent updates 2021-01-20 15:15:31 +02:00
3fba1d6b3d Bump version: 0.23.4.10 → 0.23.4.11 2021-01-18 16:35:35 +02:00
88c8d5a9a0 Unified all event setting 2021-01-18 16:35:31 +02:00
382749a8d8 Bump version: 0.23.4.9 → 0.23.4.10 2021-01-18 16:12:31 +02:00
0c877e315b as_dict updates 2021-01-18 16:12:26 +02:00
c95f642fee Citizen.as_dict rewrite 2021-01-18 16:00:29 +02:00
da0276f9a6 Removed unused CitizenMilitary.boosters property
Explicitly updated all Citizen*.as_dict properties
2021-01-18 15:22:53 +02:00
f89f91e969 Concurrency checks placed directly in Citizen class
Added concurrency checks also for citizen updates

Sentry: EREPUBLIK-BOT-77
2021-01-18 15:11:40 +02:00
56c2ca1b6e House renewal price check bugfix 2021-01-12 08:05:57 +02:00
8 changed files with 274 additions and 65 deletions

View File

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

View File

@ -13,43 +13,43 @@ __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] = [ _uas: List[str] = [
# Chrome # 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/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/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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 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/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/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', 'Mozilla/5.0 (X11; Linux x86_64) 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/80.0.3987.106 Safari/537.36',
# FireFox # 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: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: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 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.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: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:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',
] ]
debug: bool = False debug: bool = False
def __init__(self, proxies: Dict[str, str] = None): def __init__(self, proxies: Dict[str, str] = None, user_agent: str = None):
super().__init__() super().__init__()
if proxies: if proxies:
self.proxies = proxies self.proxies = proxies
if user_agent is None:
user_agent = random.choice(self._uas)
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({ self.headers.update({'User-Agent': user_agent})
'User-Agent': random.choice(self.uas)
})
@property @property
def as_dict(self): def as_dict(self):
return dict(last_time=self.last_time, timeout=self.timeout, user_agent=self.headers['User-Agent'], return dict(last_time=self.last_time, timeout=self.timeout, cookies=self.cookies.get_dict(), debug=self.debug,
request_log_name=self.request_log_name, 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()
@ -120,6 +120,10 @@ class CitizenBaseAPI:
self._req = SlowRequests() self._req = SlowRequests()
self.token = "" self.token = ""
@property
def as_dict(self):
return dict(url=self.url, request=self._req.as_dict, token=self.token)
def post(self, url: str, data=None, json=None, **kwargs) -> Response: def post(self, url: str, data=None, json=None, **kwargs) -> Response:
return self._req.post(url, data, json, **kwargs) return self._req.post(url, data, json, **kwargs)

View File

@ -38,9 +38,9 @@ class BaseCitizen(access_points.CitizenAPI):
energy: classes.Energy = None energy: classes.Energy = None
details: classes.Details = None details: classes.Details = None
politics: classes.Politics = None politics: classes.Politics = None
my_companies: classes.MyCompanies = None
reporter: classes.Reporter = None reporter: classes.Reporter = None
stop_threads: Event = None stop_threads: Event = None
concurrency_available: Event = None
telegram: classes.TelegramReporter = None telegram: classes.TelegramReporter = None
r: Response = None r: Response = None
@ -57,8 +57,6 @@ class BaseCitizen(access_points.CitizenAPI):
self.my_companies = classes.MyCompanies(self) self.my_companies = classes.MyCompanies(self)
self.reporter = classes.Reporter(self) self.reporter = classes.Reporter(self)
self.stop_threads = Event() self.stop_threads = Event()
self.concurrency_available = Event()
self.concurrency_available.set()
self.telegram = classes.TelegramReporter(stop_event=self.stop_threads) self.telegram = classes.TelegramReporter(stop_event=self.stop_threads)
self.config.email = email self.config.email = email
@ -470,7 +468,7 @@ class BaseCitizen(access_points.CitizenAPI):
self._req.debug = bool(debug) self._req.debug = bool(debug)
def to_json(self, indent: bool = False) -> str: def to_json(self, indent: bool = False) -> str:
return utils.json.dumps(self, cls=classes.MyJSONEncoder, indent=4 if indent else None) return utils.json.dumps(self, cls=classes.ErepublikJSONEncoder, indent=4 if indent else None, sort_keys=True)
def get_countries_with_regions(self) -> Set[constants.Country]: def get_countries_with_regions(self) -> Set[constants.Country]:
r_json = self._post_main_travel_data().json() r_json = self._post_main_travel_data().json()
@ -484,13 +482,13 @@ class BaseCitizen(access_points.CitizenAPI):
filename = f"{self.__class__.__name__}__dump.json" filename = f"{self.__class__.__name__}__dump.json"
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=self._req.cookies.get_dict(),
user_agent=self._req.headers.get("User-Agent")), f, cls=classes.MyJSONEncoder) user_agent=self._req.headers.get("User-Agent")), f, cls=classes.ErepublikJSONEncoder)
self.write_log(f"Session saved to: '{filename}'") self.write_log(f"Session saved to: '{filename}'")
@classmethod @classmethod
def load_from_dump(cls, dump_name: str): def load_from_dump(cls, dump_name: str):
with open(dump_name) as f: with open(dump_name) as f:
data = utils.json.load(f) 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']) player._req.cookies.update(data['cookies'])
player._req.headers.update({"User-Agent": data['user_agent']}) player._req.headers.update({"User-Agent": data['user_agent']})
@ -525,18 +523,29 @@ class BaseCitizen(access_points.CitizenAPI):
@property @property
def as_dict(self): def as_dict(self):
ret = self.__dict__.copy() ret = super().as_dict
ret.pop('stop_threads', None) ret.update(
ret.pop('_CitizenMilitary__last_war_update_data', None) name=self.name, __str__=self.__str__(),
ret.update(_properties=dict( ebs=dict(normal=self.eb_normal, double=self.eb_double, small=self.eb_small),
promos=self.promos, inventory=self._inventory.as_dict, ot_points=self.ot_points, food=self.food,
division=self.division, maveric=self.maverick, eday=self.eday, wheel_of_fortune=self.wheel_of_fortune,
debug=self.debug,
logged_in=self.logged_in, restricted_ip=self.restricted_ip, _properties=dict(
now=self.now, should_do_levelup=self.should_do_levelup, is_levelup_reachable=self.is_levelup_reachable, now=self.now, should_do_levelup=self.should_do_levelup, is_levelup_reachable=self.is_levelup_reachable,
max_time_till_full_ff=self.max_time_till_full_ff, is_levelup_close=self.is_levelup_close, max_time_till_full_ff=self.max_time_till_full_ff, is_levelup_close=self.is_levelup_close,
time_till_full_ff=self.time_till_full_ff, time_till_week_change=self.time_till_week_change, time_till_full_ff=self.time_till_full_ff, time_till_week_change=self.time_till_week_change,
next_wc_start=self.next_wc_start, next_reachable_energy=self.next_reachable_energy, next_wc_start=self.next_wc_start, next_reachable_energy=self.next_reachable_energy,
health_info=self.health_info)) health_info=self.health_info),
_last_full_update=self._last_full_update, _last_inventory_update=self._last_inventory_update,
config=self.config.as_dict, energy=self.energy.as_dict, details=self.details.as_dict,
politics=self.politics.as_dict, my_companies=self.my_companies.as_dict, reporter=self.reporter.as_dict,
telegram=self.telegram.as_dict, stop_threads=self.stop_threads.is_set(), response=self.r,
)
return ret return ret
def set_locks(self):
self.stop_threads.set()
@property @property
def health_info(self): def health_info(self):
ret = f"{self.energy.recovered}/{self.energy.limit} + {self.energy.recoverable}, " \ ret = f"{self.energy.recovered}/{self.energy.limit} + {self.energy.recoverable}, " \
@ -769,7 +778,7 @@ class BaseCitizen(access_points.CitizenAPI):
:param msg: Message about the action :param msg: Message about the action
:param kwargs: Extra information regarding action :param kwargs: Extra information regarding action
""" """
kwargs = utils.json.loads(utils.json.dumps(kwargs or {}, cls=classes.MyJSONEncoder)) kwargs = utils.json.loads(utils.json.dumps(kwargs or {}, cls=classes.ErepublikJSONEncoder))
action = action[:32] action = action[:32]
self.write_log(msg) self.write_log(msg)
if self.reporter.allowed: if self.reporter.allowed:
@ -980,7 +989,6 @@ class CitizenCompanies(BaseCitizen):
def work_as_manager_in_holding(self, holding: classes.Holding) -> Optional[Dict[str, Any]]: def work_as_manager_in_holding(self, holding: classes.Holding) -> Optional[Dict[str, Any]]:
return self._work_as_manager(holding) return self._work_as_manager(holding)
@utils.wait_for_lock
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 self.restricted_ip: if self.restricted_ip:
return None return None
@ -1050,7 +1058,7 @@ class CitizenCompanies(BaseCitizen):
class CitizenEconomy(CitizenTravel): class CitizenEconomy(CitizenTravel):
def update_money(self, page: int = 0, currency: int = 62) -> Dict[str, Any]: def update_money(self, page: int = 0, currency: int = 62):
""" """
Gets monetary market offers to get exact amount of CC and Gold available Gets monetary market offers to get exact amount of CC and Gold available
""" """
@ -1060,7 +1068,6 @@ class CitizenEconomy(CitizenTravel):
resp_data = resp.json() resp_data = resp.json()
self.details.cc = float(resp_data.get("ecash").get("value")) self.details.cc = float(resp_data.get("ecash").get("value"))
self.details.gold = float(resp_data.get("gold").get("value")) self.details.gold = float(resp_data.get("gold").get("value"))
return resp_data
def check_house_durability(self) -> Dict[int, datetime]: def check_house_durability(self) -> Dict[int, datetime]:
ret = {} ret = {}
@ -1082,7 +1089,7 @@ class CitizenEconomy(CitizenTravel):
local_cheapest = sorted(offers, key=lambda o: o.price)[0] local_cheapest = sorted(offers, key=lambda o: o.price)[0]
global_cheapest = self.get_market_offers("House", q)[f"q{q}"] global_cheapest = self.get_market_offers("House", q)[f"q{q}"]
if global_cheapest.price + 200 < local_cheapest.price: if global_cheapest.price + 2000 < local_cheapest.price:
if global_cheapest.price + 2000 < self.details.cc: if global_cheapest.price + 2000 < self.details.cc:
if self.travel_to_country(global_cheapest.country): if self.travel_to_country(global_cheapest.country):
buy = self.buy_market_offer(global_cheapest, 1) buy = self.buy_market_offer(global_cheapest, 1)
@ -1244,7 +1251,6 @@ class CitizenEconomy(CitizenTravel):
self._report_action("BOUGHT_PRODUCTS", json_ret.get('message'), kwargs=json_ret) self._report_action("BOUGHT_PRODUCTS", json_ret.get('message'), kwargs=json_ret)
return json_ret return json_ret
@utils.wait_for_lock
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 amount is None or amount > offer.amount: if amount is None or amount > offer.amount:
amount = offer.amount amount = offer.amount
@ -1548,7 +1554,12 @@ class CitizenMilitary(CitizenTravel):
__last_war_update_data = None __last_war_update_data = None
active_fs: bool = False active_fs: bool = False
boosters: Dict[int, Dict[int, int]] = {100: {}, 50: {}}
@property
def as_dict(self):
d = super().as_dict
d.update(active_fs=self.active_fs, all_battles=self.all_battles)
return d
def update_war_info(self): def update_war_info(self):
if self.__last_war_update_data and self.__last_war_update_data.get('last_updated', if self.__last_war_update_data and self.__last_war_update_data.get('last_updated',
@ -1810,7 +1821,6 @@ class CitizenMilitary(CitizenTravel):
self.travel_to_residence() self.travel_to_residence()
break break
@utils.wait_for_lock
def fight(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None, def fight(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None,
count: int = None, use_ebs: bool = False) -> Optional[int]: count: int = None, use_ebs: bool = False) -> Optional[int]:
"""Fight in a battle. """Fight in a battle.
@ -1927,7 +1937,6 @@ class CitizenMilitary(CitizenTravel):
return hits, err, damage return hits, err, damage
@utils.wait_for_lock
def deploy_bomb(self, battle: classes.Battle, division: classes.BattleDivision, bomb_id: int, inv_side: bool, def deploy_bomb(self, battle: classes.Battle, division: classes.BattleDivision, bomb_id: int, inv_side: bool,
count: int = 1) -> Optional[int]: count: int = 1) -> Optional[int]:
"""Deploy bombs in a battle for given side. """Deploy bombs in a battle for given side.
@ -2385,6 +2394,12 @@ class CitizenTasks(CitizenEconomy):
ot_points: int = 0 ot_points: int = 0
next_ot_time: datetime = None next_ot_time: datetime = None
@property
def as_dict(self):
d = super().as_dict
d.update(tg_contract=self.tg_contract, ot_points=self.ot_points, next_ot_time=self.next_ot_time)
return d
def eat(self): def eat(self):
""" Eat food """ """ Eat food """
self._eat("blue") self._eat("blue")
@ -2512,7 +2527,7 @@ class CitizenTasks(CitizenEconomy):
self.ot_points = ot.get("points", 0) self.ot_points = ot.get("points", 0)
class Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard, class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
CitizenMedia, CitizenPolitics, CitizenSocial, CitizenMilitary, CitizenTasks): CitizenMedia, CitizenPolitics, CitizenSocial, CitizenMilitary, CitizenTasks):
def __init__(self, email: str = "", password: str = "", auto_login: bool = False): def __init__(self, email: str = "", password: str = "", auto_login: bool = False):
super().__init__(email, password) super().__init__(email, password)
@ -2524,10 +2539,18 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
@classmethod @classmethod
def load_from_dump(cls, dump_name: str = ""): def load_from_dump(cls, dump_name: str = ""):
filename = dump_name if dump_name else f"{cls.__name__}__dump.json" filename = dump_name if dump_name else f"{cls.__name__}__dump.json"
player: Citizen = super().load_from_dump(filename) # noqa player: _Citizen = super().load_from_dump(filename) # noqa
player.login() player.login()
return player return player
def _eat(self, colour: str = "blue") -> Response:
resp = super()._eat(colour)
if not any([resp.json().get("units_consumed").values()]):
if colour == 'orange' and resp.json().get('food_remaining'):
self.eat()
return self._eat(colour)
return resp
def config_setup(self, **kwargs): def config_setup(self, **kwargs):
self.config.reset() self.config.reset()
for key, value in kwargs.items(): for key, value in kwargs.items():
@ -2885,3 +2908,161 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
self.stop_threads.wait(90) self.stop_threads.wait(90)
except: # noqa except: # noqa
self.report_error('Command central is broken') self.report_error('Command central is broken')
class Citizen(_Citizen):
_concurrency_lock: Event
_update_lock: Event
_update_timeout: int = 10
_concurrency_timeout: int = 600
def __init__(self, *args, **kwargs):
self._concurrency_lock = Event()
self._concurrency_lock.set()
self._update_lock = Event()
self._update_lock.set()
super().__init__(*args, **kwargs)
def update_weekly_challenge(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_weekly_challenge()
finally:
self._update_lock.set()
def update_companies(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_companies()
finally:
self._update_lock.set()
def update_war_info(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_war_info()
finally:
self._update_lock.set()
def update_job_info(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_job_info()
finally:
self._update_lock.set()
def update_money(self, page: int = 0, currency: int = 62):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_money(page, currency)
finally:
self._update_lock.set()
def update_inventory(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_inventory()
finally:
self._update_lock.set()
def _work_as_manager(self, wam_holding: classes.Holding) -> Optional[Dict[str, Any]]:
if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._concurrency_lock.clear()
return super()._work_as_manager(wam_holding)
finally:
self._concurrency_lock.set()
def fight(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None,
count: int = None, use_ebs: bool = False) -> Optional[int]:
if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._concurrency_lock.clear()
return super().fight(battle, division, side, count, use_ebs)
finally:
self._concurrency_lock.set()
def deploy_bomb(self, battle: classes.Battle, division: classes.BattleDivision, bomb_id: int, inv_side: bool,
count: int = 1) -> Optional[int]:
if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._concurrency_lock.clear()
return super().deploy_bomb(battle, division, bomb_id, inv_side, count)
finally:
self._concurrency_lock.set()
def buy_market_offer(self, offer: OfferItem, amount: int = None) -> Optional[Dict[str, Any]]:
if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._concurrency_lock.clear()
return super().buy_market_offer(offer, amount)
finally:
self._concurrency_lock.set()
@property
def as_dict(self):
d = super().as_dict
d.update(locks=dict(concurrency_lock=self._concurrency_lock.is_set(), update_lock=self._update_lock.is_set(),
concurrency_timeout=self._concurrency_timeout, update_timeout=self._update_timeout))
return d
def set_locks(self):
super().set_locks()
self._concurrency_lock.set()
self._update_lock.set()

View File

@ -10,9 +10,8 @@ from requests import Response, Session, post
from . import constants, types, utils from . import constants, types, utils
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException', __all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
'ErepublikNetworkException', 'EnergyToFight', 'ErepublikJSONEncoder', 'ErepublikNetworkException', 'EnergyToFight', 'Holding', 'Inventory', 'MyCompanies',
'Holding', 'MyCompanies', 'MyJSONEncoder', 'OfferItem', 'Politics', 'Reporter', 'TelegramReporter', 'OfferItem', 'Politics', 'Reporter', 'TelegramReporter', ]
'Inventory']
class ErepublikException(Exception): class ErepublikException(Exception):
@ -599,10 +598,10 @@ class Reporter:
if self.__to_update: if self.__to_update:
for unreported_data in self.__to_update: for unreported_data in self.__to_update:
unreported_data.update(player_id=self.citizen_id, key=self.key) unreported_data.update(player_id=self.citizen_id, key=self.key)
unreported_data = utils.json.loads(utils.json.dumps(unreported_data, cls=MyJSONEncoder)) unreported_data = utils.json.loads(utils.json.dumps(unreported_data, cls=ErepublikJSONEncoder))
self._req.post(f"{self.url}/bot/update", json=unreported_data) self._req.post(f"{self.url}/bot/update", json=unreported_data)
self.__to_update.clear() self.__to_update.clear()
data = utils.json.loads(utils.json.dumps(data, cls=MyJSONEncoder)) data = utils.json.loads(utils.json.dumps(data, cls=ErepublikJSONEncoder))
r = self._req.post(f"{self.url}/bot/update", json=data) r = self._req.post(f"{self.url}/bot/update", json=data)
return r return r
@ -680,7 +679,7 @@ class Reporter:
return [] return []
class MyJSONEncoder(utils.json.JSONEncoder): class ErepublikJSONEncoder(utils.json.JSONEncoder):
def default(self, o): def default(self, o):
from erepublik.citizen import Citizen from erepublik.citizen import Citizen
if isinstance(o, Decimal): if isinstance(o, Decimal):
@ -694,7 +693,7 @@ class MyJSONEncoder(utils.json.JSONEncoder):
return dict(__type__='timedelta', days=o.days, seconds=o.seconds, return dict(__type__='timedelta', days=o.days, seconds=o.seconds,
microseconds=o.microseconds, total_seconds=o.total_seconds()) microseconds=o.microseconds, total_seconds=o.total_seconds())
elif isinstance(o, Response): elif isinstance(o, Response):
return dict(headers=o.headers.__dict__, url=o.url, text=o.text) return dict(headers=dict(o.__dict__['headers']), url=o.url, text=o.text, status_code=o.status_code)
elif hasattr(o, 'as_dict'): elif hasattr(o, 'as_dict'):
return o.as_dict return o.as_dict
elif isinstance(o, set): elif isinstance(o, set):

View File

@ -12,6 +12,7 @@ from decimal import Decimal
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 requests import requests
from . import __version__, constants from . import __version__, constants
@ -25,11 +26,8 @@ __all__ = ['VERSION', 'calculate_hit', 'caught_error', 'date_from_eday', 'eday_f
'get_air_hit_dmg_value', 'get_file', 'get_ground_hit_dmg_value', 'get_sleep_seconds', 'good_timedelta', 'get_air_hit_dmg_value', 'get_file', 'get_ground_hit_dmg_value', 'get_sleep_seconds', 'good_timedelta',
'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now', 'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now',
'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file', 'write_request', 'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file', 'write_request',
'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock'] 'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock',
'json_decode_object_hook', 'json_load', 'json_loads']
if not sys.version_info >= (3, 6):
raise AssertionError('This script requires Python version 3.6 and higher\n'
'But Your version is v{}.{}.{}'.format(*sys.version_info))
VERSION: str = __version__ VERSION: str = __version__
@ -234,8 +232,8 @@ def send_email(name: str, content: List[Any], player=None, local_vars: Dict[str,
if isinstance(local_vars.get('citizen'), Citizen): if isinstance(local_vars.get('citizen'), Citizen):
local_vars['citizen'] = repr(local_vars['citizen']) local_vars['citizen'] = repr(local_vars['citizen'])
from erepublik.classes import MyJSONEncoder from erepublik.classes import ErepublikJSONEncoder
files.append(('file', ("local_vars.json", json.dumps(local_vars, cls=MyJSONEncoder), files.append(('file', ("local_vars.json", json.dumps(local_vars, cls=ErepublikJSONEncoder),
"application/json"))) "application/json")))
if isinstance(player, Citizen): if isinstance(player, Citizen):
files.append(('file', ("instance.json", player.to_json(indent=True), "application/json"))) files.append(('file', ("instance.json", player.to_json(indent=True), "application/json")))
@ -380,12 +378,6 @@ def get_final_hit_dmg(base_dmg: Union[Decimal, float, str], rang: int,
return Decimal(dmg) return Decimal(dmg)
# def _clear_up_battle_memory(battle):
# del battle.invader._battle, battle.defender._battle
# for div_id, division in battle.div.items():
# del division._battle
def deprecation(message): def deprecation(message):
warnings.warn(message, DeprecationWarning, stacklevel=2) warnings.warn(message, DeprecationWarning, stacklevel=2)
@ -409,3 +401,36 @@ def wait_for_lock(function):
return ret return ret
return wrapper return wrapper
def json_decode_object_hook(
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]:
""" Convert classes.ErepublikJSONEncoder datetime, date and timedelta to their python objects
:param o:
:return: Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta]
"""
if o.get('__type__'):
_type = o.get('__type__')
if _type == 'datetime':
dt = datetime.datetime.strptime(f"{o['date']} {o['time']}", "%Y-%m-%d %H:%M:%S")
if o.get('tzinfo'):
dt = pytz.timezone(o['tzinfo']).localize(dt)
return dt
elif _type == 'date':
dt = datetime.datetime.strptime(f"{o['date']}", "%Y-%m-%d")
return dt.date()
elif _type == 'timedelta':
return datetime.timedelta(seconds=o['total_seconds'])
return o
def json_load(f, **kwargs):
kwargs.update(object_hook=json_decode_object_hook)
return json.load(f, **kwargs)
def json_loads(s: str, **kwargs):
kwargs.update(object_hook=json_decode_object_hook)
return json.loads(s, **kwargs)

View File

@ -1,19 +1,19 @@
bump2version==1.0.1 bump2version==1.0.1
coverage==5.3.1 coverage==5.3.1
edx-sphinx-theme==1.6.0 edx-sphinx-theme==1.6.1
flake8==3.8.4 flake8==3.8.4
ipython>=7.19.0 ipython>=7.19.0
isort==5.7.0 isort==5.7.0
pip==20.3.3 pip==20.3.3
pre-commit==2.9.3 pre-commit==2.9.3
pur==5.3.0 pur==5.3.0
PyInstaller==4.1 PyInstaller==4.2
PySocks==1.7.1 PySocks==1.7.1
pytest==6.2.1 pytest==6.2.1
pytz>=2020.5 pytz>=2020.5
requests>=2.25.1 requests>=2.25.1
responses==0.12.1 responses==0.12.1
setuptools==51.1.2 setuptools==51.3.3
Sphinx==3.4.3 Sphinx==3.4.3
twine==3.3.0 twine==3.3.0
wheel==0.36.2 wheel==0.36.2

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.23.4.9 current_version = 0.23.4.12
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.23.4.9', version='0.23.4.12',
zip_safe=False, zip_safe=False,
) )