Compare commits

...

58 Commits

Author SHA1 Message Date
a4128b5d89 Bump version: 0.21.5.8 → 0.22.0 2020-10-21 14:46:24 +03:00
22c2a0ffd2 New Features!
Dump session to file!
Load session from file!
2020-10-21 14:45:29 +03:00
38f0335354 Bump version: 0.21.5.7 → 0.21.5.8 2020-10-07 09:22:01 +03:00
889435b94e House renewal bugfix 2020-10-07 09:21:36 +03:00
bb16c27674 Bump version: 0.21.5.6 → 0.21.5.7 2020-10-06 12:35:19 +03:00
963d7ca11a bugfix 2020-10-06 12:35:14 +03:00
36c7fefdf7 Bump version: 0.21.5.5 → 0.21.5.6 2020-09-30 08:40:14 +03:00
d9fa30b06e bugfix in default weapon switch 2020-09-30 08:35:35 +03:00
b53dc447f4 switch to deploy 2020-09-30 08:13:36 +03:00
233d8d83f8 Bump version: 0.21.5.4 → 0.21.5.5 2020-09-29 18:02:52 +03:00
ec62d90aa2 wheeloffortune bugfix 2020-09-29 18:02:52 +03:00
0c433a56da Bump version: 0.21.5.3 → 0.21.5.4 2020-09-29 17:38:44 +03:00
ad24338f4d wheeloffortune bugfix 2020-09-29 17:38:26 +03:00
6f4bc65d1b Bump version: 0.21.5.2 → 0.21.5.3 2020-09-29 17:21:00 +03:00
cc09ba7ee7 wheeloffortune argument bugfix 2020-09-29 17:20:53 +03:00
9e1166a460 Bump version: 0.21.5.1 → 0.21.5.2 2020-09-29 17:17:07 +03:00
fb0042c00d wheeloffortune url bugfix 2020-09-29 17:17:00 +03:00
bb800578e7 Bump version: 0.21.5 → 0.21.5.1 2020-09-29 15:06:39 +03:00
7025f750dc PySocks as requirement 2020-09-29 15:06:30 +03:00
bf77f21b60 MyCompanies export as dict optimised 2020-09-29 15:04:51 +03:00
6b7639d7fb Bump version: 0.21.4.8 → 0.21.5 2020-09-29 10:52:36 +03:00
3b1c1928fd Added proxy support 😉 2020-09-29 10:52:14 +03:00
2e26c2db79 Bump version: 0.21.4.7 → 0.21.4.8 2020-09-25 10:10:33 +03:00
78c055fee2 bugfix 2020-09-25 10:10:27 +03:00
fe41c4cdc6 Bump version: 0.21.4.6 → 0.21.4.7 2020-09-23 13:22:38 +03:00
123b6cf4ed Fight reporting unified and moved to Reporter.report_fighting 2020-09-23 13:22:31 +03:00
f652b02443 Fight reporting duplicate 2020-09-23 13:17:46 +03:00
73537e4742 Bump version: 0.21.4.5 → 0.21.4.6 2020-09-23 11:15:32 +03:00
955432e0d2 Check also for maintenance in BaseCitizen._errors_in_response() 2020-09-23 11:15:18 +03:00
1d93864dca Bump version: 0.21.4.4 → 0.21.4.5 2020-09-22 16:30:03 +03:00
c472d688be error logging 2020-09-22 16:29:50 +03:00
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
ad29045ace Bump version: 0.21.2.1 → 0.21.2.2 2020-07-29 11:40:41 +03:00
c919e46af5 PoliticsAPI extended and bugfixed 2020-07-29 11:40:32 +03:00
644b4d70e1 Bump version: 0.21.2 → 0.21.2.1 2020-07-29 11:23:31 +03:00
6dbbd054ba bugfix 2020-07-29 09:27:02 +03:00
0ee952e504 bugfix 2020-07-29 09:21:49 +03:00
bb9b198a53 Bump version: 0.21.1 → 0.21.2 2020-07-28 19:34:19 +03:00
cb22e631ca Merge branch 'memory-optimisation'
* memory-optimisation:
  Company cleanup optimisation
  JSON.dump sort_keys parameter throwing mysterious errors
  Fixed memory leak in Battle and MyCompanies classes
2020-07-28 19:33:52 +03:00
c43e20c8f6 Return all Non-Terrain divisions and their bh damage 2020-07-28 19:33:30 +03:00
c8f41b97af Company cleanup optimisation 2020-07-28 19:25:22 +03:00
d483bcbcb9 JSON.dump sort_keys parameter throwing mysterious errors 2020-07-28 18:29:25 +03:00
a316f277fb Fixed memory leak in Battle and MyCompanies classes 2020-07-28 18:28:03 +03:00
e8c81d17e6 Weapons kind should be singular - 'weapon' 2020-07-19 07:56:15 +03:00
12 changed files with 392 additions and 229 deletions

1
.gitignore vendored
View File

@ -104,3 +104,4 @@ ENV/
debug/ debug/
log/ log/
docs/ docs/
*dump.json

View File

@ -88,9 +88,3 @@ dist: clean ## builds source and wheel package
install: clean ## install the package to the active Python's site-packages install: clean ## install the package to the active Python's site-packages
python setup.py install python setup.py install
setcommit:
bash set_commit_id.sh
# commit=`git log -1 --pretty=format:%h`
# sed -i.bak -E "s|COMMIT_ID = \".+\"|COMMIT_ID = \"$(commit)\"|g" erepublik/utils.py
# mv erepublik/utils.py.bak erepublik/utils.py

View File

@ -4,7 +4,7 @@
__author__ = """Eriks Karls""" __author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv' __email__ = 'eriks@72.lv'
__version__ = '0.21.1' __version__ = '0.22.0'
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,32 +12,36 @@ __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, proxies: Dict[str, str] = None):
super().__init__() super().__init__()
if proxies:
self.proxies = proxies
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({
@ -113,12 +117,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)
@ -129,6 +134,14 @@ class CitizenBaseAPI:
def _get_main(self) -> Response: def _get_main(self) -> Response:
return self.get(self.url) return self.get(self.url)
def set_socks_proxy(self, host: str, port: int, username: str = None, password: str = None):
url = f'socks5://{username}:{password}@{host}:{port}' if username and password else f'socks5://{host}:{port}'
self._req.proxies = dict(http=url, https=url)
def set_http_proxy(self, host: str, port: int, username: str = None, password: str = None):
url = f'http://{username}:{password}@{host}:{port}' if username and password else f'socks5://{host}:{port}'
self._req.proxies = dict(http=url)
class ErepublikAnniversaryAPI(CitizenBaseAPI): class ErepublikAnniversaryAPI(CitizenBaseAPI):
def _post_main_collect_anniversary_reward(self) -> Response: def _post_main_collect_anniversary_reward(self) -> Response:
@ -151,10 +164,10 @@ class ErepublikAnniversaryAPI(CitizenBaseAPI):
return self.post(f"{self.url}/main/map-rewards-claim", data=data) return self.post(f"{self.url}/main/map-rewards-claim", data=data)
def _post_main_wheel_of_fortune_spin(self, cost) -> Response: def _post_main_wheel_of_fortune_spin(self, cost) -> Response:
return self.post(f"{self.url}/wheeloffortune-spin", data={'_token': self.token, "cost": cost}) return self.post(f"{self.url}/main/wheeloffortune-spin", data={'_token': self.token, "_currentCost": cost})
def _post_main_wheel_of_fortune_build(self) -> Response: def _post_main_wheel_of_fortune_build(self) -> Response:
return self.post(f"{self.url}/wheeloffortune-build", data={'_token': self.token}) return self.post(f"{self.url}/main/wheeloffortune-build", data={'_token': self.token})
class ErepublikArticleAPI(CitizenBaseAPI): class ErepublikArticleAPI(CitizenBaseAPI):
@ -433,7 +446,7 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
class ErepublikPoliticsAPI(CitizenBaseAPI): class ErepublikPoliticsAPI(CitizenBaseAPI):
def _get_candidate_party(self, party_slug: str) -> Response: def _get_candidate_party(self, party_slug: str) -> Response:
return self.post(f"{self.url}/candidate/{party_slug}") return self.get(f"{self.url}/candidate/{party_slug}")
def _get_main_party_members(self, party_id: int) -> Response: def _get_main_party_members(self, party_id: int) -> Response:
return self.get(f"{self.url}/main/party-members/{party_id}") return self.get(f"{self.url}/main/party-members/{party_id}")
@ -448,6 +461,13 @@ class ErepublikPoliticsAPI(CitizenBaseAPI):
def _get_presidential_elections(self, country_id: int, timestamp: int) -> Response: def _get_presidential_elections(self, country_id: int, timestamp: int) -> Response:
return self.get(f"{self.url}/main/presidential-elections/{country_id}/{timestamp}") return self.get(f"{self.url}/main/presidential-elections/{country_id}/{timestamp}")
def _post_propose_president_candidate(self, party_slug: str, citizen_id: int) -> Response:
return self.post(f"{self.url}/propose-president-candidate/{party_slug}",
data=dict(_token=self.token, citizen=citizen_id))
def _get_auto_propose_president_candidate(self, party_slug: str) -> Response:
return self.get(f"{self.url}/auto-propose-president-candidate/{party_slug}")
class ErepublikPresidentAPI(CitizenBaseAPI): class ErepublikPresidentAPI(CitizenBaseAPI):
def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response: def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response:

View File

@ -6,7 +6,7 @@ from decimal import Decimal
from itertools import product from itertools import product
from threading import Event from threading import Event
from time import sleep from time import sleep
from typing import Any, Callable, 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 HTTPError, RequestException, Response
@ -393,7 +393,7 @@ class BaseCitizen(access_points.CitizenAPI):
sleep(seconds) sleep(seconds)
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, sort_keys=True) return utils.json.dumps(self, cls=classes.MyJSONEncoder, indent=4 if indent else None)
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()
@ -403,6 +403,41 @@ class BaseCitizen(access_points.CitizenAPI):
return_set.add(constants.COUNTRIES[country_data['id']]) return_set.add(constants.COUNTRIES[country_data['id']])
return return_set return return_set
def dump_instance(self):
filename = f"{self.__class__.__name__}__dump.json"
with open(filename, 'w') as f:
utils.json.dump(dict(email=self.config.email, password=self.config.password,
cookies=self._req.cookies.get_dict(), token=self.token,
user_agent=self._req.headers.get("User-Agent")), f, cls=classes.MyJSONEncoder)
self.write_log(f"Session saved to: '{filename}'")
@classmethod
def load_from_dump(cls, dump_name: str):
with open(dump_name) as f:
data = utils.json.load(f)
player = cls(data['email'], data['password'])
player._req.cookies.update(data['cookies'])
player._req.headers.update({"User-Agent": data['user_agent']})
player._resume_session()
return player
def _resume_session(self):
resp = self._req.get(self.url)
re_name_id = re.search(r'<a data-fblog="profile_avatar" href="/en/citizen/profile/(\d+)" '
r'class="user_avatar" title="(.*?)">', resp.text)
if re_name_id:
self.name = re_name_id.group(2)
self.details.citizen_id = re_name_id.group(1)
self.write_log(f"Resumed as: {self.name}")
if re.search('<div id="accountSecurity" class="it-hurts-when-ip">', resp.text):
self.restricted_ip = True
self.report_error("eRepublik has blacklisted IP. Limited functionality!", True)
self.logged_in = True
self.get_csrf_token()
else:
self._login()
def __str__(self) -> str: def __str__(self) -> str:
return f"Citizen {self.name}" return f"Citizen {self.name}"
@ -624,8 +659,16 @@ class BaseCitizen(access_points.CitizenAPI):
self.sleep(5 * 60) self.sleep(5 * 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 '
r'your experience in eRepublik more pleasant. <strong>Don\'t worry about ongoing battles, timer '
r'will be stopped during maintenance.</strong>', response.text):
self.write_log("eRepublik ss having maintenance. Sleeping for 5 minutes")
self.sleep(5 * 60)
return True
return bool(re.search(r'body id="error"|Internal Server Error|' return bool(re.search(r'body id="error"|Internal Server Error|'
r'CSRF attack detected|meta http-equiv="refresh"|not_authenticated', response.text)) r'CSRF attack detected|meta http-equiv="refresh"|'
r'not_authenticated', response.text))
def _report_action(self, action: str, msg: str, **kwargs: Optional[Dict[str, Any]]): def _report_action(self, action: str, msg: str, **kwargs: Optional[Dict[str, Any]]):
""" Report action to all available reporting channels """ Report action to all available reporting channels
@ -675,9 +718,7 @@ class CitizenAnniversary(BaseCitizen):
_write_spin_data(current_cost, r.get('account'), _write_spin_data(current_cost, r.get('account'),
base.get('prizes').get('prizes').get(str(r.get('result'))).get('tooltip')) base.get('prizes').get('prizes').get(str(r.get('result'))).get('tooltip'))
else: else:
is_cost: Callable[[], bool] = lambda: (max_cost != current_cost if max_cost else True) while max_cost >= current_cost if max_cost else spin_count >= current_count if spin_count else False:
is_count: Callable[[], bool] = lambda: (spin_count != current_count if spin_count else True)
while is_cost() or is_count():
r = self._spin_wheel_of_loosing(current_cost) r = self._spin_wheel_of_loosing(current_cost)
current_count += 1 current_count += 1
current_cost = r.get('cost') current_cost = r.get('cost')
@ -686,8 +727,8 @@ class CitizenAnniversary(BaseCitizen):
def _spin_wheel_of_loosing(self, current_cost: int) -> Dict[str, Any]: def _spin_wheel_of_loosing(self, current_cost: int) -> Dict[str, Any]:
r = self._post_main_wheel_of_fortune_spin(current_cost).json() r = self._post_main_wheel_of_fortune_spin(current_cost).json()
self.details.cc = r.get('account') self.details.cc = float(Decimal(r.get('account')))
return r.get('result') return r
class CitizenTravel(BaseCitizen): class CitizenTravel(BaseCitizen):
@ -833,23 +874,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)
@ -876,6 +915,8 @@ class CitizenCompanies(BaseCitizen):
self.my_companies.prepare_holdings(utils.json.loads(have_holdings.group(1))) self.my_companies.prepare_holdings(utils.json.loads(have_holdings.group(1)))
self.my_companies.prepare_companies(utils.json.loads(have_companies.group(1))) self.my_companies.prepare_companies(utils.json.loads(have_companies.group(1)))
self.reporter.report_action('COMPANIES', json_val=self.my_companies.as_dict)
def assign_company_to_holding(self, company: classes.Company, holding: classes.Holding) -> Response: def assign_company_to_holding(self, company: classes.Company, holding: classes.Holding) -> Response:
""" """
Assigns factory to new holding Assigns factory to new holding
@ -932,8 +973,10 @@ class CitizenEconomy(CitizenTravel):
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 + 200 < local_cheapest.price:
self._travel(global_cheapest.country) if self.travel_to_country(global_cheapest.country):
buy = self.buy_from_market(global_cheapest.offer_id, 1) buy = self.buy_from_market(global_cheapest.offer_id, 1)
else:
buy = {'error': True, 'message': 'Unable to travel!'}
else: else:
buy = self.buy_from_market(local_cheapest.offer_id, 1) buy = self.buy_from_market(local_cheapest.offer_id, 1)
if buy["error"]: if buy["error"]:
@ -1016,7 +1059,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 +1114,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"]:
@ -1140,7 +1183,6 @@ class CitizenEconomy(CitizenTravel):
self.details.gold = float(response.json().get("gold").get("value")) self.details.gold = float(response.json().get("gold").get("value"))
if response.json().get('error'): if response.json().get('error'):
self._report_action("BUY_GOLD", "Unable to buy gold!", kwargs=response.json()) self._report_action("BUY_GOLD", "Unable to buy gold!", kwargs=response.json())
self.stop_threads.wait()
return False return False
else: else:
self._report_action('BUY_GOLD', f'New amount {self.details.cc}cc, {self.details.gold}g', self._report_action('BUY_GOLD', f'New amount {self.details.cc}cc, {self.details.gold}g',
@ -1314,9 +1356,6 @@ class CitizenMilitary(CitizenTravel):
boosters: Dict[int, Dict[int, int]] = {100: {}, 50: {}} boosters: Dict[int, Dict[int, int]] = {100: {}, 50: {}}
def update_war_info(self): def update_war_info(self):
if not self.details.current_country:
self.update_citizen_info()
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',
0) + 30 > self.now.timestamp(): 0) + 30 > self.now.timestamp():
r_json = self.__last_war_update_data r_json = self.__last_war_update_data
@ -1341,7 +1380,10 @@ class CitizenMilitary(CitizenTravel):
all_battles = {} all_battles = {}
for battle_data in r_json.get("battles", {}).values(): for battle_data in r_json.get("battles", {}).values():
all_battles[battle_data.get('id')] = classes.Battle(battle_data) all_battles[battle_data.get('id')] = classes.Battle(battle_data)
old_all_battles = self.all_battles
self.all_battles = all_battles self.all_battles = all_battles
# for battle in old_all_battles.values():
# utils._clear_up_battle_memory(battle)
def get_battle_for_war(self, war_id: int) -> Optional[classes.Battle]: def get_battle_for_war(self, war_id: int) -> Optional[classes.Battle]:
self.update_war_info() self.update_war_info()
@ -1382,6 +1424,7 @@ class CitizenMilitary(CitizenTravel):
try: try:
if weapon['weaponQuantity'] > 30 and weapon['weaponInfluence'] > weapon_damage: if weapon['weaponQuantity'] > 30 and weapon['weaponInfluence'] > weapon_damage:
weapon_quality = int(weapon['weaponId']) weapon_quality = int(weapon['weaponId'])
weapon_damage = weapon['weaponInfluence']
except ValueError: except ValueError:
pass pass
return self.change_weapon(battle, weapon_quality, division) return self.change_weapon(battle, weapon_quality, division)
@ -1516,35 +1559,33 @@ class CitizenMilitary(CitizenTravel):
ret_battles = ret_battles + cs_battles + deployed_battles + other_battles ret_battles = ret_battles + cs_battles + deployed_battles + other_battles
return ret_battles return ret_battles
def get_cheap_tp_divisions(self) -> Optional[classes.BattleDivision]: def get_cheap_tp_divisions(self) -> Dict[str, List[Tuple[int, classes.BattleDivision]]]:
air_divs: List[Tuple[classes.BattleDivision, int]] = [] air_divs: List[Tuple[int, classes.BattleDivision]] = []
ground_divs: List[Tuple[classes.BattleDivision, int]] = [] ground_divs: List[Tuple[int, classes.BattleDivision]] = []
for battle in reversed(self.sorted_battles(True, True)): for battle in reversed(self.sorted_battles(True, True)):
for division in battle.div.values(): for division in battle.div.values():
if not division.terrain: is_start_ok = utils.good_timedelta(division.battle.start, timedelta(minutes=-1)) < self.now
if not division.terrain and is_start_ok and not division.div_end:
if division.is_air and self.config.air: if division.is_air and self.config.air:
medal = self.get_battle_round_data(division)[ division_medals = self.get_battle_round_data(division)
self.details.citizenship == division.battle.defender.id] medal = division_medals[self.details.citizenship == division.battle.defender.country]
if not medal and division.battle.start: if not medal:
return division air_divs.append((0, division))
else: else:
air_divs.append((division, medal.get('1').get('raw_value'))) air_divs.append((medal.get('1').get('raw_value'), division))
elif self.config.ground: elif not division.is_air and self.config.ground:
if not division.div == self.division and not self.maverick: if not division.div == self.division and not self.maverick:
continue continue
division_medals = self.get_battle_round_data(division) division_medals = self.get_battle_round_data(division)
medal = division_medals[self.details.citizenship == division.battle.defender.country] medal = division_medals[self.details.citizenship == division.battle.defender.country]
if not medal and division.battle.start: if not medal:
return division ground_divs.append((0, division))
else: else:
ground_divs.append((division, medal.get('1').get('raw_value'))) ground_divs.append((medal.get('1').get('raw_value'), division))
if self.config.air: air_divs.sort(key=lambda z: (z[0], z[1].battle.start))
return min(air_divs, key=lambda x: x[1])[0] ground_divs.sort(key=lambda z: (z[0], z[1].battle.start))
elif self.config.ground: return {'air': air_divs, 'ground': ground_divs}
return min(ground_divs, key=lambda x: x[1])[0]
else:
return
@property @property
def has_battle_contribution(self): def has_battle_contribution(self):
@ -1611,14 +1652,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
@ -1657,8 +1699,11 @@ 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=battle, side=side, dmg=total_damage, self.reporter.report_fighting(battle, not side.is_defender, division, total_damage, total_hits)
air=battle.has_air, hits=total_hits)) # 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,
# extra=dict(battle=battle, side=side)))
return error_count return error_count
def _shoot(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide): def _shoot(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide):
@ -1678,14 +1723,17 @@ class CitizenMilitary(CitizenTravel):
damage = 0 damage = 0
err = False err = False
if r_json.get("error"): if r_json.get("error"):
if r_json.get("message") == "SHOOT_LOCKOUT" or r_json.get("message") == "ZONE_INACTIVE": if r_json.get("message") == "SHOOT_LOCKOUT":
pass pass
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.write_log("Wrong division!!") self.write_log("Wrong division!!")
return 0, 10, 0 return 0, 10, 0
elif r_json.get("message") == "FIGHT_DISABLED": elif r_json.get("message") == "ZONE_INACTIVE":
self.write_log("Wrong division!!")
return 0, 10, 0
elif r_json.get("message") in ["FIGHT_DISABLED", "DEPLOYMENT_MODE"]:
self._post_main_profile_update('options', self._post_main_profile_update('options',
params='{"optionName":"enable_web_deploy","optionValue":"off"}') params='{"optionName":"enable_web_deploy","optionValue":"off"}')
self.set_default_weapon(battle, division) self.set_default_weapon(battle, division)
@ -1765,7 +1813,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,
@ -1965,6 +2014,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:
@ -1982,15 +2038,16 @@ class CitizenPolitics(BaseCitizen):
self._report_action('POLITIC_PARTY_PRESIDENT', 'Applied for party president elections') self._report_action('POLITIC_PARTY_PRESIDENT', 'Applied for party president elections')
return self._get_candidate_party(self.politics.party_slug) return self._get_candidate_party(self.politics.party_slug)
def get_country_president_election_result(self, country: constants.Country, year: int, month: int) -> Dict[str, int]: def get_country_president_election_result(
self, country: constants.Country, year: int, month: int
) -> Dict[str, int]:
timestamp = int(constants.erep_tz.localize(datetime(year, month, 5)).timestamp()) timestamp = int(constants.erep_tz.localize(datetime(year, month, 5)).timestamp())
resp = self._get_presidential_elections(country.id, timestamp) resp = self._get_presidential_elections(country.id, timestamp)
candidates = re.findall(r'class="candidate_info">(.*?)</li>', resp.text, re.S | re.M) candidates = re.findall(r'class="candidate_info">(.*?)</li>', resp.text, re.S | re.M)
ret = {} ret = {}
for candidate in candidates: for candidate in candidates:
name = re.search( name = re.search(r'<a hovercard=1 class="candidate_name" href="//www.erepublik.com/en/citizen/profile/\d+"'
r'<a hovercard=1 class="candidate_name" href="//www.erepublik.com/en/citizen/profile/\d+" title="(.*)">', r' title="(.*)">', candidate)
candidate)
name = name.group(1) name = name.group(1)
votes = re.search(r'<span class="votes">(\d+) votes</span>', candidate).group(1) votes = re.search(r'<span class="votes">(\d+) votes</span>', candidate).group(1)
ret.update({name: int(votes)}) ret.update({name: int(votes)})
@ -2250,6 +2307,16 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
if auto_login: if auto_login:
self.login() self.login()
@classmethod
def load_from_dump(cls, *args, **kwargs):
with open(f"{cls.__name__}__dump.json") as f:
data = utils.json.load(f)
player = cls(data['email'], data['password'], False)
player._req.cookies.update(data['cookies'])
player._req.headers.update({"User-Agent": data['user_agent']})
player._resume_session()
return player
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():
@ -2337,11 +2404,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
@ -2514,7 +2581,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)
@ -2530,7 +2598,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()
@ -2547,6 +2615,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

@ -1,8 +1,9 @@
import datetime import datetime
import hashlib import hashlib
import threading import threading
import weakref
from decimal import Decimal from decimal import Decimal
from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union, NoReturn
from requests import Response, Session, post from requests import Response, Session, post
@ -27,12 +28,23 @@ class Holding:
id: int id: int
region: int region: int
companies: List["Company"] companies: List["Company"]
name: str
_citizen = weakref.ReferenceType
def __init__(self, _id: int, region: int, citizen): def __init__(self, _id: int, region: int, citizen, name: str = None):
self.citizen = citizen self._citizen = weakref.ref(citizen)
self.id: int = _id self.id: int = _id
self.region: int = region self.region: int = region
self.companies: List["Company"] = list() self.companies: List["Company"] = list()
if name:
self.name = name
else:
name = f"Holding (#{self.id}) with {len(self.companies)} "
if len(self.companies) == 1:
name += "company"
else:
name += "companies"
self.name = name
@property @property
def wam_count(self) -> int: def wam_count(self) -> int:
@ -46,7 +58,7 @@ class Holding:
def employable_companies(self) -> List["Company"]: def employable_companies(self) -> List["Company"]:
return [company for company in self.companies if company.preset_works] return [company for company in self.companies if company.preset_works]
def add_company(self, company: "Company"): def add_company(self, company: "Company") -> NoReturn:
self.companies.append(company) self.companies.append(company)
self.companies.sort() self.companies.sort()
@ -60,7 +72,7 @@ class Holding:
wrm += company.raw_usage wrm += company.raw_usage
return dict(frm=frm, wrm=wrm) return dict(frm=frm, wrm=wrm)
def get_wam_companies(self, raw_factory: bool = None): def get_wam_companies(self, raw_factory: bool = None) -> Optional[List["Company"]]:
raw = [] raw = []
factory = [] factory = []
for company in self.wam_companies: for company in self.wam_companies:
@ -78,7 +90,7 @@ class Holding:
else: else:
raise ErepublikException("raw_factory should be True/False/None") raise ErepublikException("raw_factory should be True/False/None")
def __str__(self): def __str__(self) -> str:
name = f"Holding (#{self.id}) with {len(self.companies)} " name = f"Holding (#{self.id}) with {len(self.companies)} "
if len(self.companies) % 10 == 1: if len(self.companies) % 10 == 1:
name += "company" name += "company"
@ -90,11 +102,17 @@ class Holding:
return str(self) return str(self)
@property @property
def as_dict(self): def as_dict(self) -> Dict[str, Union[str, int, List[Dict[str, Union[str, int, bool, float, Decimal]]]]]:
return dict(name=str(self), id=self.id, region=self.region, companies=self.companies, wam_count=self.wam_count) return dict(name=self.name, id=self.id, region=self.region,
companies=[c.as_dict for c in self.companies], wam_count=self.wam_count)
@property
def citizen(self):
return self._citizen()
class Company: class Company:
_holding: weakref.ReferenceType
holding: Holding holding: Holding
id: int id: int
quality: int quality: int
@ -113,7 +131,7 @@ class Company:
base_production: Decimal, wam_enabled: bool, can_wam: bool, cannot_wam_reason: str, industry: int, base_production: Decimal, wam_enabled: bool, can_wam: bool, cannot_wam_reason: str, industry: int,
already_worked: bool, preset_works: int already_worked: bool, preset_works: int
): ):
self.holding: Holding = holding self._holding = weakref.ref(holding)
self.id: int = _id self.id: int = _id
self.industry: int = industry self.industry: int = industry
self.quality: int = self._get_real_quality(quality) self.quality: int = self._get_real_quality(quality)
@ -196,7 +214,7 @@ class Company:
return str(self) return str(self)
@property @property
def as_dict(self): def as_dict(self) -> Dict[str, Union[str, int, bool, float, Decimal]]:
return dict(name=str(self), holding=self.holding.id, id=self.id, quality=self.quality, is_raw=self.is_raw, return dict(name=str(self), holding=self.holding.id, id=self.id, quality=self.quality, is_raw=self.is_raw,
raw_usage=self.raw_usage, products_made=self.products_made, wam_enabled=self.wam_enabled, raw_usage=self.raw_usage, products_made=self.products_made, wam_enabled=self.wam_enabled,
can_wam=self.can_wam, cannot_wam_reason=self.cannot_wam_reason, industry=self.industry, can_wam=self.can_wam, cannot_wam_reason=self.cannot_wam_reason, industry=self.industry,
@ -211,6 +229,10 @@ class Company:
# noinspection PyProtectedMember # noinspection PyProtectedMember
return self.holding.citizen._post_economy_upgrade_company(self.id, level, self.holding.citizen.details.pin) return self.holding.citizen._post_economy_upgrade_company(self.id, level, self.holding.citizen.details.pin)
@property
def holding(self) -> Holding:
return self._holding()
class MyCompanies: class MyCompanies:
work_units: int = 0 work_units: int = 0
@ -218,13 +240,13 @@ class MyCompanies:
ff_lockdown: int = 0 ff_lockdown: int = 0
holdings: Dict[int, Holding] holdings: Dict[int, Holding]
companies: List[Company] companies: weakref.WeakSet
_citizen: weakref.ReferenceType
def __init__(self, citizen): def __init__(self, citizen):
from erepublik import Citizen self._citizen = weakref.ref(citizen)
self.citizen: Citizen = citizen
self.holdings: Dict[int, Holding] = dict() self.holdings: Dict[int, Holding] = dict()
self.companies: List[Company] = list() self.companies: weakref.WeakSet = weakref.WeakSet()
self.next_ot_time = utils.now() self.next_ot_time = utils.now()
def prepare_holdings(self, holdings: Dict[str, Dict[str, Any]]): def prepare_holdings(self, holdings: Dict[str, Dict[str, Any]]):
@ -233,10 +255,11 @@ class MyCompanies:
""" """
for holding in holdings.values(): for holding in holdings.values():
if holding.get('id') not in self.holdings: if holding.get('id') not in self.holdings:
self.holdings.update( self.holdings.update({
{int(holding.get('id')): Holding(holding['id'], holding['region_id'], self.citizen)}) int(holding.get('id')): Holding(holding['id'], holding['region_id'], self.citizen, holding['name'])
})
if not self.holdings.get(0): if not self.holdings.get(0):
self.holdings.update({0: Holding(0, 0, self.citizen)}) # unassigned self.holdings.update({0: Holding(0, 0, self.citizen, 'Unassigned')}) # unassigned
def prepare_companies(self, companies: Dict[str, Dict[str, Any]]): def prepare_companies(self, companies: Dict[str, Dict[str, Any]]):
""" """
@ -258,7 +281,7 @@ class MyCompanies:
company_dict.get('can_work_as_manager'), company_dict.get('cannot_work_as_manager_reason'), company_dict.get('can_work_as_manager'), company_dict.get('cannot_work_as_manager_reason'),
company_dict.get('industry_id'), company_dict.get('already_worked'), company_dict.get('preset_works') company_dict.get('industry_id'), company_dict.get('already_worked'), company_dict.get('preset_works')
) )
self.companies.append(company) self.companies.add(company)
holding.add_company(company) holding.add_company(company)
def get_employable_factories(self) -> Dict[int, int]: def get_employable_factories(self) -> Dict[int, int]:
@ -282,13 +305,21 @@ 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: # noqa
del company
holding.companies.clear() holding.companies.clear()
self.companies.clear() self.companies.clear()
@property @property
def as_dict(self): def as_dict(self) -> Dict[str, Union[str, int, datetime.datetime, Dict[str, Dict[str, Union[str, int, List[Dict[str, Union[str, int, bool, float, Decimal]]]]]]]]:
return dict(name=str(self), work_units=self.work_units, next_ot_time=self.next_ot_time, return dict(name=str(self), work_units=self.work_units, next_ot_time=self.next_ot_time,
ff_lockdown=self.ff_lockdown, holdings=self.holdings, company_count=len(self.companies)) ff_lockdown=self.ff_lockdown,
holdings={str(hi): h.as_dict for hi, h in self.holdings.items()},
company_count=len(self.companies))
@property
def citizen(self):
return self._citizen()
class Config: class Config:
@ -420,24 +451,25 @@ class Energy:
class Details: class Details:
xp = 0 xp: int = 0
cc = 0 cc: float = 0
pp = 0 pp: int = 0
pin = None pin: str = None
gold = 0 gold: float = 0
next_pp: List[int] = None next_pp: List[int] = None
citizen_id = 0 citizen_id: int = 0
citizenship: constants.Country citizenship: constants.Country
current_region = 0 current_region: int = 0
current_country: constants.Country current_country: constants.Country
residence_region = 0 residence_region: int = 0
residence_country: constants.Country residence_country: constants.Country
daily_task_done = False daily_task_done: bool = False
daily_task_reward = False daily_task_reward: bool = False
mayhem_skills = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, } mayhem_skills: Dict[int, int]
def __init__(self): def __init__(self):
self.next_pp = [] self.next_pp = []
self.mayhem_skills = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0}
@property @property
def xp_till_level_up(self): def xp_till_level_up(self):
@ -528,10 +560,13 @@ class Reporter:
queue=self.__to_update) queue=self.__to_update)
def __init__(self, citizen): def __init__(self, citizen):
self.citizen = 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
@ -541,6 +576,10 @@ class Reporter:
self.register_account() self.register_account()
self.allowed = True self.allowed = True
@property
def citizen(self):
return self._citizen()
def __update_key(self): def __update_key(self):
self.key = hashlib.md5(bytes(f"{self.name}:{self.email}", encoding="UTF-8")).hexdigest() self.key = hashlib.md5(bytes(f"{self.name}:{self.email}", encoding="UTF-8")).hexdigest()
@ -548,10 +587,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.load(utils.json.dumps(unreported_data, cls=MyJSONEncoder, sort_keys=True)) 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, sort_keys=True)) data = utils.json.loads(utils.json.dumps(data, cls=MyJSONEncoder))
r = self._req.post("{}/bot/update".format(self.url), json=data) r = self._req.post("{}/bot/update".format(self.url), json=data)
return r return r
@ -593,6 +632,12 @@ class Reporter:
else: else:
self.__to_update.append(json_data) self.__to_update.append(json_data)
def report_fighting(self, battle: "Battle", invader: bool, division: "BattleDivision", damage: float, hits: int):
side = battle.invader if invader else battle.defender
self.report_action('FIGHT', dict(battle_id=battle.id, side=side, dmg=damage,
air=battle.has_air, hits=hits,
round=battle.zone_id, extra=dict(battle=battle, side=side, division=division)))
def report_promo(self, kind: str, time_until: datetime.datetime): def report_promo(self, kind: str, time_until: datetime.datetime):
self._req.post(f"{self.url}/promos/add/", data=dict(kind=kind, time_untill=time_until)) self._req.post(f"{self.url}/promos/add/", data=dict(kind=kind, time_untill=time_until))
@ -604,13 +649,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):
@ -645,28 +690,29 @@ class BattleSide:
deployed: List[constants.Country] deployed: List[constants.Country]
allies: List[constants.Country] allies: List[constants.Country]
battle: "Battle" battle: "Battle"
_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):
self.battle = battle self._battle = weakref.ref(battle)
self.country = country self.country = country
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):
@ -674,8 +720,12 @@ 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
def battle(self):
return self._battle()
class BattleDivision: class BattleDivision:
@ -689,11 +739,12 @@ class BattleDivision:
terrain: int terrain: int
div: int div: int
battle: "Battle" battle: "Battle"
_battle: weakref.ReferenceType
@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):
@ -715,7 +766,7 @@ class BattleDivision:
:type wall_for: int :type wall_for: int
:type wall_dom: float :type wall_dom: float
""" """
self.battle = battle self._battle = weakref.ref(battle)
self.id = div_id self.id = div_id
self.end = end self.end = end
self.epic = epic self.epic = epic
@ -738,6 +789,10 @@ class BattleDivision:
def __repr__(self): def __repr__(self):
return f"<BattleDivision #{self.id} (battle #{self.battle.id})>" return f"<BattleDivision #{self.id} (battle #{self.battle.id})>"
@property
def battle(self):
return self._battle()
class Battle: class Battle:
id: int id: int
@ -756,7 +811,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:
@ -765,6 +820,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():
@ -888,7 +947,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

@ -49,16 +49,16 @@ class Country:
class Industries: class Industries:
__by_name = {'food': 1, 'weapons': 2, 'house': 4, 'aircraft': 23, __by_name = {'food': 1, 'weapon': 2, 'house': 4, 'aircraft': 23,
'foodraw': 7, 'weaponraw': 12, 'weaponsraw': 12, 'houseraw': 18, 'aircraftraw': 24, 'foodraw': 7, 'weaponraw': 12, 'houseraw': 18, 'aircraftraw': 24,
'frm': 7, 'wrm': 12, 'hrm': 18, 'arm': 24, 'frm': 7, 'wrm': 12, 'hrm': 18, 'arm': 24,
'frm q1': 7, 'frm q2': 8, 'frm q3': 9, 'frm q4': 10, 'frm q5': 11, 'frm q1': 7, 'frm q2': 8, 'frm q3': 9, 'frm q4': 10, 'frm q5': 11,
'wrm q1': 12, 'wrm q2': 13, 'wrm q3': 14, 'wrm q4': 15, 'wrm q5': 16, 'wrm q1': 12, 'wrm q2': 13, 'wrm q3': 14, 'wrm q4': 15, 'wrm q5': 16,
'hrm q1': 18, 'hrm q2': 19, 'hrm q3': 20, 'hrm q4': 21, 'hrm q5': 22, 'hrm q1': 18, 'hrm q2': 19, 'hrm q3': 20, 'hrm q4': 21, 'hrm q5': 22,
'arm q1': 24, 'arm q2': 25, 'arm q3': 26, 'arm q4': 27, 'arm q5': 28} 'arm q1': 24, 'arm q2': 25, 'arm q3': 26, 'arm q4': 27, 'arm q5': 28}
__by_id = {1: "Food", 2: "Weapons", 4: "House", 23: "Aircraft", __by_id = {1: "Food", 2: "Weapon", 4: "House", 23: "Aircraft",
7: "foodRaw", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5", 7: "foodRaw", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
12: "weaponsRaw", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5", 12: "weaponRaw", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
18: "houseRaw", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5", 18: "houseRaw", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
24: "aircraftRaw", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5"} 24: "aircraftRaw", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5"}

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)
@ -230,7 +230,7 @@ def send_email(name: str, content: List[Any], player=None, local_vars: Dict[str,
local_vars['citizen'] = repr(local_vars['citizen']) local_vars['citizen'] = repr(local_vars['citizen'])
from erepublik.classes import MyJSONEncoder from erepublik.classes import MyJSONEncoder
files.append(('file', ("local_vars.json", json.dumps(local_vars, cls=MyJSONEncoder, sort_keys=True), files.append(('file', ("local_vars.json", json.dumps(local_vars, cls=MyJSONEncoder),
"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")))
@ -279,15 +279,15 @@ def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commi
write_silent_log(log_info) write_silent_log(log_info)
trace = inspect.trace() trace = inspect.trace()
if trace: if trace:
trace = trace[-1][0].f_locals local_vars = trace[-1][0].f_locals
if trace.get('__name__') == '__main__': if local_vars.get('__name__') == '__main__':
trace = {'commit_id': trace.get('COMMIT_ID'), local_vars.update({'commit_id': local_vars.get('COMMIT_ID'),
'interactive': trace.get('INTERACTIVE'), 'interactive': local_vars.get('INTERACTIVE'),
'version': trace.get('__version__'), 'version': local_vars.get('__version__'),
'config': trace.get('CONFIG')} 'config': 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 +307,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:
@ -368,3 +368,9 @@ def get_air_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_pat
rang = r['military']['militaryData']['aircraft']['rankNumber'] rang = r['military']['militaryData']['aircraft']['rankNumber']
elite = r['citizenAttributes']['level'] > 100 elite = r['citizenAttributes']['level'] > 100
return calculate_hit(0, rang, true_patriot, elite, natural_enemy, booster, weapon_power) return calculate_hit(0, rang, true_patriot, elite, natural_enemy, booster, weapon_power)
def _clear_up_battle_memory(battle):
del battle.invader._battle, battle.defender._battle
for div_id, division in battle.div.items():
del division._battle

View File

@ -1,17 +1,19 @@
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.1.0
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
PySocks==1.7.1
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.1 current_version = 0.22.0
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

@ -11,11 +11,18 @@ with open('README.rst') as readme_file:
with open('HISTORY.rst') as history_file: with open('HISTORY.rst') as history_file:
history = history_file.read() history = history_file.read()
requirements = ['pytz==2020.1', 'requests==2.24.0'] requirements = [
'pytz==2020.1',
'requests==2.24.0',
'PySocks==1.7.1'
]
setup_requirements = [] setup_requirements = []
test_requirements = [] test_requirements = [
"pytest==6.1.0",
"responses==0.12.0"
]
setup( setup(
author="Eriks Karls", author="Eriks Karls",
@ -43,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.21.1', version='0.22.0',
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))