Compare commits

...

15 Commits

Author SHA1 Message Date
bff9a2bec9 Bump version: 0.21.4.3 → 0.21.4.4 2020-09-21 20:40:40 +03:00
973ea40e00 bugfix 2020-09-21 20:40:24 +03:00
52c85fdf28 Bump version: 0.21.4.2 → 0.21.4.3 2020-09-21 20:18:02 +03:00
a889e9efa1 bugfix 2020-09-21 20:17:56 +03:00
a9a0cdc6d5 Bump version: 0.21.4.1 → 0.21.4.2 2020-09-21 12:39:44 +03:00
1c102488b6 Test fix to not run if WC end is near 2020-09-21 12:39:27 +03:00
c38acef2a0 Bump version: 0.21.4 → 0.21.4.1 2020-09-21 11:34:59 +03:00
48b5e473aa doc generation bugfixes 2020-09-21 11:34:50 +03:00
7fadeb1a49 Bump version: 0.21.3 → 0.21.4 2020-09-21 11:26:51 +03:00
b723660f23 Fixups and type checks 2020-09-21 11:26:32 +03:00
f10eeec498 requirement update 2020-09-21 11:24:01 +03:00
230167f93d mypy bugfix 2020-09-21 11:23:43 +03:00
d5ed989e80 Bump version: 0.21.2.2 → 0.21.3 2020-08-18 16:37:27 +03:00
6fc24b8adf requirements 2020-08-18 16:37:07 +03:00
cf797f2f60 Fixes and updates 2020-08-18 13:14:41 +03:00
9 changed files with 169 additions and 142 deletions

View File

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

View File

@ -12,29 +12,31 @@ __all__ = ['SlowRequests', 'CitizenAPI']
class SlowRequests(Session): class SlowRequests(Session):
last_time: datetime.datetime last_time: datetime.datetime
timeout = datetime.timedelta(milliseconds=500) timeout: datetime.timedelta = datetime.timedelta(milliseconds=500)
uas = [ uas: List[str] = [
# Chrome # Chrome
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36', # noqa
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36', # noqa 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36', # noqa
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36', # noqa 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36', # noqa
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', # noqa 'Mozilla/5.0 (X11; Linux x86_64) 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/77.0.3865.90 Safari/537.36', # noqa
'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', 'Mozilla/5.0 (X11; Linux x86_64) 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/79.0.3945.88 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
# FireFox # FireFox
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.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:75.0) Gecko/20100101 Firefox/75.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:74.0) Gecko/20100101 Firefox/74.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:73.0) Gecko/20100101 Firefox/73.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:76.0) Gecko/20100101 Firefox/76.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:74.0) Gecko/20100101 Firefox/74.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.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:80.0) Gecko/20100101 Firefox/80.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0',
] ]
debug = False debug: bool = False
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -113,12 +115,13 @@ class SlowRequests(Session):
class CitizenBaseAPI: class CitizenBaseAPI:
url: str = "https://www.erepublik.com/en" url: str = "https://www.erepublik.com/en"
_req: SlowRequests = None _req: SlowRequests
token: str = "" token: str
def __init__(self): def __init__(self):
""" Class for unifying eRepublik known endpoints and their required/optional parameters """ """ Class for unifying eRepublik known endpoints and their required/optional parameters """
self._req = SlowRequests() self._req = SlowRequests()
self.token = ""
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

@ -833,23 +833,21 @@ 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)
def _work_as_manager(self, wam_holding: classes.Holding = None) -> 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
self.update_companies() self.update_companies()
self.update_inventory() self.update_inventory()
data = {"action_type": "production"} data = {"action_type": "production"}
extra = {} extra = {}
wam_list = [] raw_factories = wam_holding.get_wam_companies(raw_factory=True)
if wam_holding: fin_factories = wam_holding.get_wam_companies(raw_factory=False)
raw_factories = wam_holding.get_wam_companies(raw_factory=True)
fin_factories = wam_holding.get_wam_companies(raw_factory=False)
free_inventory = self.inventory["total"] - self.inventory["used"] free_inventory = self.inventory["total"] - self.inventory["used"]
wam_list = raw_factories + fin_factories wam_list = raw_factories + fin_factories
wam_list = wam_list[:self.energy.food_fights] wam_list = wam_list[:self.energy.food_fights]
while wam_list and free_inventory < self.my_companies.get_needed_inventory_usage(wam_list): while wam_list and free_inventory < self.my_companies.get_needed_inventory_usage(wam_list):
wam_list.pop(-1) wam_list.pop(-1)
if wam_list: if wam_list:
data.update(extra) data.update(extra)
@ -1016,7 +1014,7 @@ class CitizenEconomy(CitizenTravel):
self.write_log(f"Trying to sell unsupported industry {industry}") self.write_log(f"Trying to sell unsupported industry {industry}")
data = { data = {
"country_id": self.details.citizenship, "country_id": self.details.citizenship.id,
"industry": industry, "industry": industry,
"quality": quality, "quality": quality,
"amount": amount, "amount": amount,
@ -1071,7 +1069,7 @@ class CitizenEconomy(CitizenTravel):
start_dt = self.now start_dt = self.now
iterable = [countries, [quality] if quality else range(1, max_quality + 1)] iterable = [countries, [quality] if quality else range(1, max_quality + 1)]
for country, q in product(*iterable): for country, q in product(*iterable):
r = self._post_economy_marketplace(country, constants.INDUSTRIES[product_name], q).json() r = self._post_economy_marketplace(country.id, constants.INDUSTRIES[product_name], q).json()
obj = offers[f"q{q}"] obj = offers[f"q{q}"]
if not r.get("error", False): if not r.get("error", False):
for offer in r["offers"]: for offer in r["offers"]:
@ -1540,8 +1538,8 @@ class CitizenMilitary(CitizenTravel):
else: else:
ground_divs.append((medal.get('1').get('raw_value'), division)) ground_divs.append((medal.get('1').get('raw_value'), division))
air_divs.sort(key=lambda z: (z[0],z[1].battle.start)) air_divs.sort(key=lambda z: (z[0], z[1].battle.start))
ground_divs.sort(key=lambda z:(z[0],z[1].battle.start)) ground_divs.sort(key=lambda z: (z[0], z[1].battle.start))
return {'air': air_divs, 'ground': ground_divs} return {'air': air_divs, 'ground': ground_divs}
@property @property
@ -1609,14 +1607,15 @@ class CitizenMilitary(CitizenTravel):
self.travel_to_residence() self.travel_to_residence()
break break
def fight(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None, count: int = None) -> int: def fight(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None,
count: int = None) -> int:
"""Fight in a battle. """Fight in a battle.
Will auto activate booster and travel if allowed to do it. Will auto activate booster and travel if allowed to do it.
:param battle: Battle battle to fight in :param battle: Battle battle to fight in
:type battle: Battle :type battle: Battle
:param side: BattleSide or None. Battle side to fight in, If side not == invader id or not in invader deployed :param side: BattleSide or None. Battle side to fight in, If side not == invader id or not in invader deployed
allies list, then defender's side is chosen allies list, then defender's side is chosen
:type side: BattleSide :type side: BattleSide
:param count: How many hits to do, if not specified self.should_fight() is called. :param count: How many hits to do, if not specified self.should_fight() is called.
:type count: int :type count: int
@ -1655,6 +1654,9 @@ class CitizenMilitary(CitizenTravel):
self.write_log("Hits: {:>4} | Damage: {}".format(total_hits, total_damage)) self.write_log("Hits: {:>4} | Damage: {}".format(total_hits, total_damage))
ok_to_fight = False ok_to_fight = False
if total_damage: if total_damage:
self.reporter.report_action('FIGHT', dict(battle_id=battle.id, side=side, dmg=total_damage,
air=battle.has_air, hits=total_hits,
round=battle.zone_id))
self.reporter.report_action("FIGHT", dict(battle=str(battle), side=str(side), dmg=total_damage, self.reporter.report_action("FIGHT", dict(battle=str(battle), side=str(side), dmg=total_damage,
air=battle.has_air, hits=total_hits)) air=battle.has_air, hits=total_hits))
return error_count return error_count
@ -1763,7 +1765,8 @@ class CitizenMilitary(CitizenTravel):
if resp.json().get('error'): if resp.json().get('error'):
self.write_log(resp.json().get('message')) self.write_log(resp.json().get('message'))
return False return False
self._report_action("MILITARY_DIV_SWITCH", f"Switched to d{division.div} in battle {battle.id}", kwargs=resp.json()) self._report_action("MILITARY_DIV_SWITCH", f"Switched to d{division.div} in battle {battle.id}",
kwargs=resp.json())
return True return True
def get_ground_hit_dmg_value(self, rang: int = None, strength: float = None, elite: bool = None, ne: bool = False, def get_ground_hit_dmg_value(self, rang: int = None, strength: float = None, elite: bool = None, ne: bool = False,
@ -1963,6 +1966,13 @@ class CitizenMilitary(CitizenTravel):
return member.get('panelContents', {}).get('members', [{}])[0].get('dailyOrdersCompleted') return member.get('panelContents', {}).get('members', [{}])[0].get('dailyOrdersCompleted')
return 0 return 0
def get_possibly_empty_medals(self):
self.update_war_info()
for battle in self.all_battles.values():
for division in battle.div.values():
if division.wall['dom'] == 50 or division.wall['dom'] > 98:
yield division, division.wall['for'] == battle.invader.country.id
class CitizenPolitics(BaseCitizen): class CitizenPolitics(BaseCitizen):
def get_country_parties(self, country: constants.Country = None) -> dict: def get_country_parties(self, country: constants.Country = None) -> dict:
@ -2335,11 +2345,11 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self.reporter.report_action("NEW_MEDAL", info) self.reporter.report_action("NEW_MEDAL", info)
def set_debug(self, debug: bool): def set_debug(self, debug: bool):
self.debug = debug self.debug = bool(debug)
self._req.debug = debug self._req.debug = bool(debug)
def set_pin(self, pin: int): def set_pin(self, pin: int):
self.details.pin = pin self.details.pin = int(pin)
def update_all(self, force_update=False): def update_all(self, force_update=False):
# Do full update max every 5 min # Do full update max every 5 min
@ -2512,7 +2522,8 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
else: else:
if not start_place == (self.details.current_country, self.details.current_region): if not start_place == (self.details.current_country, self.details.current_region):
self.travel_to_holding(holding) self.travel_to_holding(holding)
return self._wam(holding) self._wam(holding)
return
elif response.get("message") == "not_enough_health_food": elif response.get("message") == "not_enough_health_food":
self.buy_food() self.buy_food()
self._wam(holding) self._wam(holding)
@ -2528,7 +2539,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
:rtype: bool :rtype: bool
""" """
if self.restricted_ip: if self.restricted_ip:
self._report_action("IP_BLACKLISTED", "Fighting is not allowed from restricted IP!") self._report_action("IP_BLACKLISTED", "Work as manager is not allowed from restricted IP!")
return False return False
self.update_citizen_info() self.update_citizen_info()
self.update_companies() self.update_companies()
@ -2545,6 +2556,10 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self.update_companies() self.update_companies()
for holding in regions.values(): for holding in regions.values():
raw_usage = holding.get_wam_raw_usage()
if (raw_usage['frm'] + raw_usage['wrm']) * 100 + self.inventory['used'] > self.inventory['total']:
self._report_action('WAM_UNAVAILABLE', 'Not enough storage!')
continue
self.travel_to_holding(holding) self.travel_to_holding(holding)
self._wam(holding) self._wam(holding)
self.update_companies() self.update_companies()

View File

@ -294,7 +294,7 @@ class MyCompanies:
def __clear_data(self): def __clear_data(self):
for holding in self.holdings.values(): for holding in self.holdings.values():
for company in holding.companies: for company in holding.companies: # noqa
del company del company
holding.companies.clear() holding.companies.clear()
self.companies.clear() self.companies.clear()
@ -549,7 +549,10 @@ class Reporter:
self._citizen = weakref.ref(citizen) self._citizen = weakref.ref(citizen)
self._req = Session() self._req = Session()
self.url = "https://api.erep.lv" self.url = "https://api.erep.lv"
self._req.headers.update({"user-agent": "Bot reporter v2"}) self._req.headers.update({"user-agent": "eRepublik Script Reporter v3",
'erep-version': utils.__version__,
'erep-user-id': str(self.citizen_id),
'erep-user-name': self.citizen.name})
self.__to_update = [] self.__to_update = []
self.__registered: bool = False self.__registered: bool = False
@ -569,8 +572,8 @@ class Reporter:
def __bot_update(self, data: dict) -> Response: def __bot_update(self, data: dict) -> Response:
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.load(utils.json.dumps(unreported_data, cls=MyJSONEncoder)) unreported_data = utils.json.loads(utils.json.dumps(unreported_data, cls=MyJSONEncoder))
self._req.post("{}/bot/update".format(self.url), json=unreported_data) self._req.post("{}/bot/update".format(self.url), 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=MyJSONEncoder))
@ -626,13 +629,13 @@ class Reporter:
except: # noqa except: # noqa
return [] return []
def fetch_tasks(self) -> Optional[Tuple[str, Tuple[Any]]]: def fetch_tasks(self) -> List[Union[str, Tuple[Any]]]:
try: try:
task_response = self._req.get(f'{self.url}/api/v1/command', task_response = self._req.get(f'{self.url}/api/v1/command',
params=dict(citizen=self.citizen_id, key=self.key)) params=dict(citizen=self.citizen_id, key=self.key))
return task_response.json().get('task_collection') return task_response.json().get('task_collection')
except: # noqa except: # noqa
return return []
class MyJSONEncoder(utils.json.JSONEncoder): class MyJSONEncoder(utils.json.JSONEncoder):
@ -669,7 +672,7 @@ class BattleSide:
battle: "Battle" battle: "Battle"
_battle: weakref.ReferenceType _battle: weakref.ReferenceType
country: constants.Country country: constants.Country
defender: bool is_defender: bool
def __init__(self, battle: "Battle", country: constants.Country, points: int, allies: List[constants.Country], def __init__(self, battle: "Battle", country: constants.Country, points: int, allies: List[constants.Country],
deployed: List[constants.Country], defender: bool): deployed: List[constants.Country], defender: bool):
@ -678,18 +681,18 @@ class BattleSide:
self.points = points self.points = points
self.allies = allies self.allies = allies
self.deployed = deployed self.deployed = deployed
self.defender = defender self.is_defender = defender
@property @property
def id(self) -> int: def id(self) -> int:
return self.country.id return self.country.id
def __repr__(self): def __repr__(self):
side_text = "Defender" if self.defender else "Invader " side_text = "Defender" if self.is_defender else "Invader "
return f"<BattleSide: {side_text} {self.country.name}|{self.points:02d}p>" return f"<BattleSide: {side_text} {self.country.name}|{self.points:02d}p>"
def __str__(self): def __str__(self):
side_text = "Defender" if self.defender else "Invader " side_text = "Defender" if self.is_defender else "Invader "
return f"{side_text} {self.country.name} - {self.points:02d} points" return f"{side_text} {self.country.name} - {self.points:02d} points"
def __format__(self, format_spec): def __format__(self, format_spec):
@ -697,8 +700,8 @@ class BattleSide:
@property @property
def as_dict(self): def as_dict(self):
return dict(points=self.points, country=self.country, defender=self.defender, allies=self.allies, return dict(points=self.points, country=self.country, is_defender=self.is_defender, allies=self.allies,
deployed=self.deployed, battle=repr(self.battle)) deployed=self.deployed)
@property @property
def battle(self): def battle(self):
@ -721,7 +724,7 @@ class BattleDivision:
@property @property
def as_dict(self): def as_dict(self):
return dict(id=self.id, division=self.div, terrain=(self.terrain, self.terrain_display), wall=self.wall, return dict(id=self.id, division=self.div, terrain=(self.terrain, self.terrain_display), wall=self.wall,
epic=self.epic, battle=str(self.battle), end=self.div_end) epic=self.epic, end=self.div_end)
@property @property
def is_air(self): def is_air(self):
@ -788,7 +791,7 @@ class Battle:
def as_dict(self): def as_dict(self):
return dict(id=self.id, war_id=self.war_id, divisions=self.div, zone=self.zone_id, rw=self.is_rw, return dict(id=self.id, war_id=self.war_id, divisions=self.div, zone=self.zone_id, rw=self.is_rw,
dict_lib=self.is_dict_lib, start=self.start, sides={'inv': self.invader, 'def': self.defender}, dict_lib=self.is_dict_lib, start=self.start, sides={'inv': self.invader, 'def': self.defender},
region=[self.region_id, self.region_name]) region=[self.region_id, self.region_name], link=self.link)
@property @property
def has_air(self) -> bool: def has_air(self) -> bool:
@ -797,6 +800,10 @@ class Battle:
return True return True
return not bool(self.zone_id % 4) return not bool(self.zone_id % 4)
@property
def has_started(self) -> bool:
return self.start <= utils.now()
@property @property
def has_ground(self) -> bool: def has_ground(self) -> bool:
for div in self.div.values(): for div in self.div.values():
@ -920,7 +927,7 @@ class TelegramBot:
def as_dict(self): def as_dict(self):
return {'chat_id': self.chat_id, 'api_url': self.api_url, 'player': self.player_name, return {'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, 'last_time': self._last_time, 'next_time': self._next_time, 'queue': self.__queue,
'initialized': self.__initialized, 'has_threads': bool(len(self._threads))} 'initialized': self.__initialized, 'has_threads': not self._threads}
def do_init(self, chat_id: int, token: str, player_name: str = ""): def do_init(self, chat_id: int, token: str, player_name: str = ""):
self.chat_id = chat_id self.chat_id = chat_id

View File

@ -1,4 +1,3 @@
import copy
import datetime import datetime
import inspect import inspect
import os import os
@ -10,7 +9,7 @@ import traceback
import unicodedata import unicodedata
from decimal import Decimal from decimal import Decimal
from pathlib import Path from pathlib import Path
from typing import Any, List, Mapping, Optional, Union, Dict from typing import Any, List, Optional, Union, Dict
import requests import requests
@ -43,12 +42,11 @@ def localize_timestamp(timestamp: int) -> datetime.datetime:
def localize_dt(dt: Union[datetime.date, datetime.datetime]) -> datetime.datetime: def localize_dt(dt: Union[datetime.date, datetime.datetime]) -> datetime.datetime:
try: if isinstance(dt, datetime.datetime):
try: return constants.erep_tz.localize(dt)
return constants.erep_tz.localize(dt) elif isinstance(dt, datetime.date):
except AttributeError: return constants.erep_tz.localize(datetime.datetime.combine(dt, datetime.time(0, 0, 0)))
return constants.erep_tz.localize(datetime.datetime.combine(dt, datetime.time(0, 0, 0))) else:
except ValueError:
return dt.astimezone(constants.erep_tz) return dt.astimezone(constants.erep_tz)
@ -117,10 +115,12 @@ def _write_log(msg, timestamp: bool = True, should_print: bool = False):
def write_interactive_log(*args, **kwargs): def write_interactive_log(*args, **kwargs):
kwargs.pop("should_print", None)
_write_log(should_print=True, *args, **kwargs) _write_log(should_print=True, *args, **kwargs)
def write_silent_log(*args, **kwargs): def write_silent_log(*args, **kwargs):
kwargs.pop("should_print", None)
_write_log(should_print=False, *args, **kwargs) _write_log(should_print=False, *args, **kwargs)
@ -278,16 +278,17 @@ def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commi
elif interactive is not None: elif interactive is not None:
write_silent_log(log_info) write_silent_log(log_info)
trace = inspect.trace() trace = inspect.trace()
local_vars = None
if trace: if trace:
trace = trace[-1][0].f_locals trace_local_vars = trace[-1][0].f_locals
if trace.get('__name__') == '__main__': if trace_local_vars.get('__name__') == '__main__':
trace = {'commit_id': trace.get('COMMIT_ID'), local_vars = {'commit_id': trace_local_vars.get('COMMIT_ID'),
'interactive': trace.get('INTERACTIVE'), 'interactive': trace_local_vars.get('INTERACTIVE'),
'version': trace.get('__version__'), 'version': trace_local_vars.get('__version__'),
'config': trace.get('CONFIG')} 'config': trace_local_vars.get('CONFIG')}
else: else:
trace = dict() local_vars = dict()
send_email(name, content, citizen, local_vars=trace) send_email(name, content, citizen, local_vars=local_vars)
def process_warning(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None): def process_warning(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None):
@ -307,10 +308,10 @@ def process_warning(log_info: str, name: str, exc_info: tuple, citizen=None, com
trace = inspect.trace() trace = inspect.trace()
if trace: if trace:
trace = trace[-1][0].f_locals local_vars = trace[-1][0].f_locals
else: else:
trace = dict() local_vars = dict()
send_email(name, content, citizen, local_vars=trace) send_email(name, content, citizen, local_vars=local_vars)
def slugify(value, allow_unicode=False) -> str: def slugify(value, allow_unicode=False) -> str:
@ -371,8 +372,6 @@ def get_air_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_pat
def _clear_up_battle_memory(battle): def _clear_up_battle_memory(battle):
from . import classes
battle: classes.Battle
del battle.invader._battle, battle.defender._battle del battle.invader._battle, battle.defender._battle
for div_id, division in battle.div.items(): for div_id, division in battle.div.items():
del division._battle del division._battle

View File

@ -1,17 +1,18 @@
bump2version==1.0.0 bump2version==1.0.0
coverage==5.2 coverage==5.3
edx-sphinx-theme==1.5.0 edx-sphinx-theme==1.5.0
flake8==3.8.3 flake8==3.8.3
ipython==7.16.1 ipython==7.18.1
isort==5.0.9 isort==5.5.3
pip==20.1.1 pip==20.2.3
PyInstaller==3.6 PyInstaller==4.0
pytz==2020.1 pytz==2020.1
pytest==5.4.3 pytest==6.0.2
responses==0.10.15 responses==0.12.0
setuptools==49.2.0 setuptools==50.3.0
Sphinx==3.1.2 Sphinx==3.2.1
tox==3.16.1 requests==2.24.0
tox==3.20.0
twine==3.2.0 twine==3.2.0
watchdog==0.10.3 watchdog==0.10.3
wheel==0.34.2 wheel==0.35.1

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.21.2.2 current_version = 0.21.4.4
commit = True commit = True
tag = True tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)? parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
@ -33,7 +33,6 @@ ignore_missing_imports = False
warn_unused_ignores = True warn_unused_ignores = True
warn_redundant_casts = True warn_redundant_casts = True
warn_unused_configs = True warn_unused_configs = True
plugins = mypy_django_plugin.main
[isort] [isort]
multi_line_output = 2 multi_line_output = 2

View File

@ -43,6 +43,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.21.2.2', version='0.21.4.4',
zip_safe=False, zip_safe=False,
) )

View File

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Tests for `erepublik` package.""" """Tests for `erepublik` package."""
from typing import Callable
from erepublik import Citizen from erepublik import Citizen
@ -65,6 +66,7 @@ class TestErepublik(unittest.TestCase):
self.assertEqual(self.citizen.next_reachable_energy, 0) self.assertEqual(self.citizen.next_reachable_energy, 0)
def test_should_fight(self): def test_should_fight(self):
is_wc_close: Callable[[], bool] = lambda: self.citizen.max_time_till_full_ff > self.citizen.time_till_week_change
self.citizen.config.fight = False self.citizen.config.fight = False
self.assertEqual(self.citizen.should_fight(), (0, "Fighting not allowed!", False)) self.assertEqual(self.citizen.should_fight(), (0, "Fighting not allowed!", False))
@ -73,62 +75,63 @@ class TestErepublik(unittest.TestCase):
# Level up # Level up
self.citizen.energy.limit = 3000 self.citizen.energy.limit = 3000
self.citizen.details.xp = 24705 self.citizen.details.xp = 24705
self.assertEqual(self.citizen.should_fight(), (0, 'Level up', False)) if not is_wc_close:
self.assertEqual(self.citizen.should_fight(), (0, 'Level up', False))
self.citizen.energy.recovered = 3000 self.citizen.energy.recovered = 3000
self.citizen.energy.recoverable = 2950 self.citizen.energy.recoverable = 2950
self.citizen.energy.interval = 30 self.citizen.energy.interval = 30
self.assertEqual(self.citizen.should_fight(), (900, 'Level up', True)) self.assertEqual(self.citizen.should_fight(), (900, 'Level up', True))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), (900, 'Level up', True)) self.assertEqual(self.citizen.should_fight(), (900, 'Level up', True))
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
# Level up reachable # Level up reachable
self.citizen.details.xp = 24400 self.citizen.details.xp = 24400
self.assertEqual(self.citizen.should_fight(), (305, 'Fighting for close Levelup. Doing 305 hits', True)) self.assertEqual(self.citizen.should_fight(), (305, 'Fighting for close Levelup. Doing 305 hits', True))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), (305, 'Fighting for close Levelup. Doing 305 hits', True)) self.assertEqual(self.citizen.should_fight(), (305, 'Fighting for close Levelup. Doing 305 hits', True))
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.details.xp = 21000 self.citizen.details.xp = 21000
self.assertEqual(self.citizen.should_fight(), (75, 'Obligatory fighting for at least 75pp', True)) self.assertEqual(self.citizen.should_fight(), (75, 'Obligatory fighting for at least 75pp', True))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), (75, 'Obligatory fighting for at least 75pp', True)) self.assertEqual(self.citizen.should_fight(), (75, 'Obligatory fighting for at least 75pp', True))
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.details.pp = 80 self.citizen.details.pp = 80
# All-in (type = all-in and full ff) # All-in (type = all-in and full ff)
self.citizen.config.all_in = True self.citizen.config.all_in = True
self.assertEqual(self.citizen.should_fight(), (595, 'Fighting all-in. Doing 595 hits', False)) self.assertEqual(self.citizen.should_fight(), (595, 'Fighting all-in. Doing 595 hits', False))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), ( self.assertEqual(self.citizen.should_fight(), (
435, 'Fight count modified (old count: 595 | FF: 595 | WAM ff_lockdown: 160 | New count: 435)', False 435, 'Fight count modified (old count: 595 | FF: 595 | WAM ff_lockdown: 160 | New count: 435)', False
)) ))
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.config.air = True self.citizen.config.air = True
self.citizen.energy.recoverable = 1000 self.citizen.energy.recoverable = 1000
self.assertEqual(self.citizen.should_fight(), (400, 'Fighting all-in in AIR. Doing 400 hits', False)) self.assertEqual(self.citizen.should_fight(), (400, 'Fighting all-in in AIR. Doing 400 hits', False))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), ( self.assertEqual(self.citizen.should_fight(), (
240, 'Fight count modified (old count: 400 | FF: 400 | WAM ff_lockdown: 160 | New count: 240)', False 240, 'Fight count modified (old count: 400 | FF: 400 | WAM ff_lockdown: 160 | New count: 240)', False
)) ))
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.config.all_in = False self.citizen.config.all_in = False
self.citizen.config.next_energy = True self.citizen.config.next_energy = True
self.citizen.energy.limit = 5000 self.citizen.energy.limit = 5000
self.citizen.details.next_pp = [100, 150, 250, 400, 500] self.citizen.details.next_pp = [100, 150, 250, 400, 500]
self.assertEqual(self.citizen.should_fight(), (320, 'Fighting for +1 energy. Doing 320 hits', False)) self.assertEqual(self.citizen.should_fight(), (320, 'Fighting for +1 energy. Doing 320 hits', False))
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), ( self.assertEqual(self.citizen.should_fight(), (
160, 'Fight count modified (old count: 320 | FF: 400 | WAM ff_lockdown: 160 | New count: 160)', False 160, 'Fight count modified (old count: 320 | FF: 400 | WAM ff_lockdown: 160 | New count: 160)', False
)) ))
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
self.citizen.energy.limit = 3000 self.citizen.energy.limit = 3000
self.citizen.details.next_pp = [19250, 20000] self.citizen.details.next_pp = [19250, 20000]
self.citizen.config.next_energy = False self.citizen.config.next_energy = False
# 1h worth of energy # 1h worth of energy
self.citizen.energy.recoverable = 2910 self.citizen.energy.recoverable = 2910
self.assertEqual(self.citizen.should_fight(), (30, 'Fighting for 1h energy. Doing 30 hits', True)) self.assertEqual(self.citizen.should_fight(), (30, 'Fighting for 1h energy. Doing 30 hits', True))