Battle initialization without valid data should be avoided to not run into strange and hard to trace bugs.

Jsonification updates - if simplejson is available some packages are importing simplejson with try-except and later throwing simplejson errors which should be cought when calling .json() on every request.
Fixed error logging
This commit is contained in:
Eriks Karls 2020-01-13 21:33:50 +02:00
parent 69d0e7df0a
commit 15e6deebda
3 changed files with 84 additions and 63 deletions

View File

@ -1,22 +1,22 @@
import json
import re import re
import sys import sys
from collections import defaultdict
from datetime import datetime, timedelta from datetime import datetime, timedelta
from itertools import product from itertools import product
from json import dumps, loads
from threading import Event from threading import Event
from time import sleep from time import sleep
from typing import Any, Dict, List, Optional, Set, Tuple, Union from typing import Any, Dict, List, Optional, Set, Tuple, Union
from requests import RequestException, Response from requests import RequestException, Response
from erepublik.classes import (Battle, BattleDivision, CitizenAPI, Config, from erepublik.classes import (Battle, BattleDivision, CitizenAPI, Config, Details, Energy, ErepublikException,
Details, Energy, ErepublikException, MyCompanies, MyJSONEncoder, Politics, Reporter, TelegramBot)
MyCompanies, MyJSONEncoder, Politics, Reporter,
TelegramBot)
from erepublik.utils import * from erepublik.utils import *
try:
import simplejson as json
except ImportError:
import json
class Citizen(CitizenAPI): class Citizen(CitizenAPI):
division = 0 division = 0
@ -116,6 +116,7 @@ class Citizen(CitizenAPI):
def __repr__(self): def __repr__(self):
return self.__str__() return self.__str__()
@property
def __dict__(self): def __dict__(self):
ret = super().__dict__.copy() ret = super().__dict__.copy()
ret.pop('stop_threads', None) ret.pop('stop_threads', None)
@ -332,7 +333,7 @@ class Citizen(CitizenAPI):
self._get_main() self._get_main()
return return
ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1) ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1)
promos = loads(normalize_html_json(ugly_js)) promos = json.loads(normalize_html_json(ugly_js))
if self.promos is None: if self.promos is None:
self.promos = {} self.promos = {}
else: else:
@ -367,7 +368,7 @@ class Citizen(CitizenAPI):
) )
ugly_js = re.search(r"var erepublik = ({.*}),\s+", html).group(1) ugly_js = re.search(r"var erepublik = ({.*}),\s+", html).group(1)
citizen_js = loads(ugly_js) citizen_js = json.loads(ugly_js)
citizen = citizen_js.get("citizen", {}) citizen = citizen_js.get("citizen", {})
self.eday = citizen_js.get("settings").get("eDay") self.eday = citizen_js.get("settings").get("eDay")
@ -423,14 +424,14 @@ class Citizen(CitizenAPI):
def update_companies(self): def update_companies(self):
html = self._get_economy_my_companies().text html = self._get_economy_my_companies().text
page_details = loads(re.search(r"var pageDetails\s+= ({.*});", html).group(1)) page_details = json.loads(re.search(r"var pageDetails\s+= ({.*});", html).group(1))
self.my_companies.work_units = int(page_details.get("total_works", 0)) self.my_companies.work_units = int(page_details.get("total_works", 0))
have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html) have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html)
have_companies = re.search(r"var companies\s+= ({.*}});", html) have_companies = re.search(r"var companies\s+= ({.*}});", html)
if have_holdings and have_companies: if have_holdings and have_companies:
self.my_companies.prepare_companies(loads(have_companies.group(1))) self.my_companies.prepare_companies(json.loads(have_companies.group(1)))
self.my_companies.prepare_holdings(loads(have_holdings.group(1))) self.my_companies.prepare_holdings(json.loads(have_holdings.group(1)))
self.my_companies.update_holding_companies() self.my_companies.update_holding_companies()
def update_inventory(self) -> Dict[str, Any]: def update_inventory(self) -> Dict[str, Any]:
@ -573,7 +574,7 @@ class Citizen(CitizenAPI):
def update_weekly_challenge(self): def update_weekly_challenge(self):
data = self._get_main_weekly_challenge_data().json() data = self._get_main_weekly_challenge_data().json()
self.details.pp = data.get("player", {}).get("prestigePoints", 0) self.details.pp = data.get("player", {}).get("prestigePoints", 0)
self.details.next_pp = [] self.details.next_pp.clear()
for reward in data.get("rewards", {}).get("normal", {}): for reward in data.get("rewards", {}).get("normal", {}):
status = reward.get("status", "") status = reward.get("status", "")
if status == "rewarded": if status == "rewarded":
@ -2060,33 +2061,32 @@ class Citizen(CitizenAPI):
return {battle.invader.id: r.json().get(str(battle.invader.id)).get("fighterData"), return {battle.invader.id: r.json().get(str(battle.invader.id)).get("fighterData"),
battle.defender.id: r.json().get(str(battle.defender.id)).get("fighterData")} battle.defender.id: r.json().get(str(battle.defender.id)).get("fighterData")}
def contribute_cc_to_country(self, amount=0.) -> bool: def contribute_cc_to_country(self, amount=0., country_id: int = 71) -> bool:
self.update_money() self.update_money()
amount = int(amount) amount = int(amount)
if self.details.cc < amount or amount < 20: if self.details.cc < amount or amount < 20:
return False return False
data = dict(country=71, action='currency', value=amount) data = dict(country=country_id, action='currency', value=amount)
self.telegram.send_message(f"Donated {amount}cc to {COUNTRIES[71]}")
self.reporter.report_action("CONTRIBUTE_CC", data, str(amount)) self.reporter.report_action("CONTRIBUTE_CC", data, str(amount))
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error') return r.json().get('status') or not r.json().get('error')
def contribute_food_to_country(self, amount: int = 0, quality: int = 1) -> bool: def contribute_food_to_country(self, amount: int = 0, quality: int = 1, country_id: int = 71) -> bool:
self.update_inventory() self.update_inventory()
amount = amount // 1 amount = amount // 1
if self.food["q" + str(quality)] < amount or amount < 10: if self.food["q" + str(quality)] < amount or amount < 10:
return False return False
data = dict(country=71, action='food', value=amount, quality=quality) data = dict(country=country_id, action='food', value=amount, quality=quality)
self.reporter.report_action("CONTRIBUTE_FOOD", data, FOOD_ENERGY[quality] * amount) self.reporter.report_action("CONTRIBUTE_FOOD", data, FOOD_ENERGY[quality] * amount)
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error') return r.json().get('status') or not r.json().get('error')
def contribute_gold_to_country(self, amount: int) -> bool: def contribute_gold_to_country(self, amount: int, country_id: int = 71) -> bool:
self.update_money() self.update_money()
if self.details.cc < amount: if self.details.cc < amount:
return False return False
data = dict(country=71, action='gold', value=amount) data = dict(country=country_id, action='gold', value=amount)
self.reporter.report_action("CONTRIBUTE_GOLD", data, str(amount)) self.reporter.report_action("CONTRIBUTE_GOLD", data, str(amount))
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error') return r.json().get('status') or not r.json().get('error')
@ -2120,7 +2120,7 @@ class Citizen(CitizenAPI):
# return ret # return ret
def to_json(self, indent: bool = False) -> str: def to_json(self, indent: bool = False) -> str:
return dumps(self.__dict__, cls=MyJSONEncoder, indent=4 if indent else None, sort_keys=True) return json.dumps(self.__dict__, cls=MyJSONEncoder, indent=4 if indent else None, sort_keys=True)
def get_game_token_offers(self): def get_game_token_offers(self):
r = self._post_economy_game_tokens_market('retrieve').json() r = self._post_economy_game_tokens_market('retrieve').json()

View File

@ -5,13 +5,17 @@ import random
import threading import threading
import time import time
from collections import defaultdict, deque from collections import defaultdict, deque
from json import JSONDecodeError, JSONEncoder, loads
from typing import Any, Dict, Iterable, List, Mapping, Tuple, Union from typing import Any, Dict, Iterable, List, Mapping, Tuple, Union
from requests import Response, Session, post from requests import Response, Session, post
from erepublik import utils from erepublik import utils
try:
import simplejson as json
except ImportError:
import json
class ErepublikException(Exception): class ErepublikException(Exception):
def __init__(self, message): def __init__(self, message):
@ -281,9 +285,9 @@ class SlowRequests(Session):
} }
try: try:
loads(resp.text) json.loads(resp.text)
file_data.update({"ext": "json"}) file_data.update({"ext": "json"})
except JSONDecodeError: except json.JSONDecodeError:
file_data.update({"ext": "html"}) file_data.update({"ext": "html"})
filename = 'debug/requests/{time}_{name}{extra}.{ext}'.format(**file_data) filename = 'debug/requests/{time}_{name}{extra}.{ext}'.format(**file_data)
@ -356,6 +360,18 @@ class Config:
self.telegram_chat_id = 0 self.telegram_chat_id = 0
self.telegram_token = "" self.telegram_token = ""
@property
def __dict__(self):
return dict(email=self.email, work=self.work, train=self.train, wam=self.wam,
auto_sell=self.auto_sell, auto_sell_all=self.auto_sell_all, employees=self.employees,
fight=self.fight, air=self.air, ground=self.ground, all_in=self.all_in,
next_energy=self.next_energy, boosters=self.boosters, travel_to_fight=self.travel_to_fight,
always_travel=self.always_travel, epic_hunt=self.epic_hunt, epic_hunt_ebs=self.epic_hunt_ebs,
rw_def_side=self.rw_def_side, interactive=self.interactive,
continuous_fighting=self.continuous_fighting, auto_buy_raw=self.auto_buy_raw,
force_wam=self.force_wam, sort_battles_time=self.sort_battles_time, force_travel=self.force_travel,
telegram=self.telegram, telegram_chat_id=self.telegram_chat_id, telegram_token=self.telegram_token)
class Energy: class Energy:
limit = 500 # energyToRecover limit = 500 # energyToRecover
@ -1045,16 +1061,16 @@ class Reporter:
self.__to_update.append(json_data) self.__to_update.append(json_data)
class MyJSONEncoder(JSONEncoder): class MyJSONEncoder(json.JSONEncoder):
def default(self, o): def default(self, o):
from erepublik.citizen import Citizen from erepublik.citizen import Citizen
if isinstance(o, decimal.Decimal): if isinstance(o, decimal.Decimal):
return float("{:.02f}".format(o)) return float("{:.02f}".format(o))
elif isinstance(o, datetime.datetime): elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', year=o.year, month=o.month, day=o.day, hour=o.hour, minute=o.minute, return dict(__type__='datetime', date=o.strftime("%Y-%m-%d"), time=o.strftime("%H:%M:%S"),
second=o.second, microsecond=o.microsecond, tzinfo=o.tzinfo.zone if o.tzinfo else None) tzinfo=o.tzinfo.zone if o.tzinfo else None)
elif isinstance(o, datetime.date): elif isinstance(o, datetime.date):
return dict(__type__='date', year=o.year, month=o.month, day=o.day) return dict(__type__='date', date=o.strftime("%Y-%m-%d"))
elif isinstance(o, datetime.timedelta): elif isinstance(o, datetime.timedelta):
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())
@ -1131,26 +1147,15 @@ class Battle:
def is_air(self) -> bool: def is_air(self) -> bool:
return not bool(self.zone_id % 4) return not bool(self.zone_id % 4)
def __init__(self, battle: Dict[str, Any] = None): def __init__(self, battle: Dict[str, Any]):
"""Object representing eRepublik battle. """Object representing eRepublik battle.
:param battle: Dict object for single battle from '/military/campaignsJson/list' response's 'battles' object :param battle: Dict object for single battle from '/military/campaignsJson/list' response's 'battles' object
""" """
if battle is None:
battle = {} self.id = int(battle.get('id'))
self.id = 0 self.war_id = int(battle.get('war_id'))
self.war_id = 0 self.zone_id = int(battle.get('zone_id'))
self.zone_id = 0
self.is_rw = False
self.is_as = False
self.is_dict_lib = False
self.start = utils.now().min
self.invader = BattleSide(0, 0, [], [])
self.defender = BattleSide(0, 0, [], [])
else:
self.id = int(battle.get('id', 0))
self.war_id = int(battle.get('war_id', 0))
self.zone_id = int(battle.get('zone_id', 0))
self.is_rw = bool(battle.get('is_rw')) self.is_rw = bool(battle.get('is_rw'))
self.is_as = bool(battle.get('is_as')) self.is_as = bool(battle.get('is_as'))
self.is_dict_lib = bool(battle.get('is_dict')) or bool(battle.get('is_lib')) self.is_dict_lib = bool(battle.get('is_dict')) or bool(battle.get('is_lib'))
@ -1198,10 +1203,11 @@ class Battle:
time_part = " {}".format(now - self.start) time_part = " {}".format(now - self.start)
else: else:
time_part = "-{}".format(self.start - now) time_part = "-{}".format(self.start - now)
return f"Battle {self.id} | " \ return f"Battle {self.id} | " \
f"{utils.COUNTRIES[self.invader.id]:>21.21}:{utils.COUNTRIES[self.defender.id]:<21.21} | " \ f"{utils.ISO_CC[self.invader.id]} : {utils.ISO_CC[self.defender.id]} | " \
f"Round {self.zone_id:2} | " \ f"Round {self.zone_id:2} | " \
f"Time since start {time_part}" f"Round time {time_part}"
class EnergyToFight: class EnergyToFight:
@ -1252,6 +1258,7 @@ class TelegramBot:
self.__queue = [] self.__queue = []
self.__thread_stopper = threading.Event() if stop_event is None else stop_event self.__thread_stopper = threading.Event() if stop_event is None else stop_event
@property
def __dict__(self): def __dict__(self):
return dict(chat_id=self.chat_id, api_url=self.api_url, player=self.player_name, last_time=self._last_time, return dict(chat_id=self.chat_id, api_url=self.api_url, player=self.player_name, last_time=self._last_time,
next_time=self._next_time, queue=self.__queue, initialized=self.__initialized, next_time=self._next_time, queue=self.__queue, initialized=self.__initialized,

View File

@ -1,6 +1,5 @@
import datetime import datetime
import inspect import inspect
import json
import os import os
import re import re
import sys import sys
@ -13,6 +12,12 @@ from typing import Any, List, Mapping, NoReturn, Optional, Union
import pytz import pytz
import requests import requests
try:
import simplejson as json
except ImportError:
import json
__all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", 'COUNTRY_LINK', __all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", 'COUNTRY_LINK',
"now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday", "now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday",
"get_sleep_seconds", "interactive_sleep", "silent_sleep", "get_sleep_seconds", "interactive_sleep", "silent_sleep",
@ -97,6 +102,15 @@ COUNTRY_LINK = {1: 'Romania', 9: 'Brazil', 11: 'France', 12: 'Germany', 13: 'Hun
81: 'Republic-of-China-Taiwan', 166: 'United-Arab-Emirates', 167: 'Albania', 69: 'Bosnia-Herzegovina', 81: 'Republic-of-China-Taiwan', 166: 'United-Arab-Emirates', 167: 'Albania', 69: 'Bosnia-Herzegovina',
169: 'Armenia', 83: 'Belarus', 84: 'New-Zealand', 164: 'Saudi-Arabia', 170: 'Nigeria', } 169: 'Armenia', 83: 'Belarus', 84: 'New-Zealand', 164: 'Saudi-Arabia', 170: 'Nigeria', }
ISO_CC = {1: 'ROU', 9: 'BRA', 10: 'ITA', 11: 'FRA', 12: 'DEU', 13: 'HUN', 14: 'CHN', 15: 'ESP', 23: 'CAN', 24: 'USA',
26: 'MEX', 27: 'ARG', 28: 'VEN', 29: 'GBR', 30: 'CHE', 31: 'NLD', 32: 'BEL', 33: 'AUT', 34: 'CZE', 35: 'POL',
36: 'SVK', 37: 'NOR', 38: 'SWE', 39: 'FIN', 40: 'UKR', 41: 'RUS', 42: 'BGR', 43: 'TUR', 44: 'GRC', 45: 'JPN',
47: 'KOR', 48: 'IND', 49: 'IDN', 50: 'AUS', 51: 'ZAF', 52: 'MDA', 53: 'PRT', 54: 'IRL', 55: 'DNK', 56: 'IRN',
57: 'PAK', 58: 'ISR', 59: 'THA', 61: 'SVN', 63: 'HRV', 64: 'CHL', 65: 'SRB', 66: 'MYS', 67: 'PHL', 68: 'SGP',
69: 'BiH', 70: 'EST', 71: 'LVA', 72: 'LTU', 73: 'PRK', 74: 'URY', 75: 'PRY', 76: 'BOL', 77: 'PER', 78: 'COL',
79: 'MKD', 80: 'MNE', 81: 'TWN', 82: 'CYP', 83: 'BLR', 84: 'NZL', 164: 'SAU', 165: 'EGY', 166: 'UAE',
167: 'ALB', 168: 'GEO', 169: 'ARM', 170: 'NGA', 171: 'CUB'}
def now() -> datetime.datetime: def now() -> datetime.datetime:
return datetime.datetime.now(erep_tz).replace(microsecond=0) return datetime.datetime.now(erep_tz).replace(microsecond=0)