Compare commits

...

23 Commits

Author SHA1 Message Date
1c47d169d2 Bump version: 0.18.2 → 0.18.3 2020-01-07 11:32:59 +02:00
ef44787bad make clean-pyc removes log/ and debug/ run 'artifacts' 2020-01-07 11:32:48 +02:00
42431134e1 UA update 2020-01-07 11:30:40 +02:00
bedaeeefd1 Battle division update 2020-01-07 11:15:40 +02:00
bbf304aa99 Bump version: 0.18.1 → 0.18.2 2020-01-05 10:53:39 +02:00
a2447959e7 fight must receive battle id as int, added warnings support 2020-01-05 10:53:26 +02:00
700bd8d98e Bump version: 0.18.0 → 0.18.1 2020-01-02 22:43:01 +02:00
3599dc40fc More logging, Citizen.get_raw_surplus() fixed and moved to Citizen.my_companies.get_wam_raw_usage() 2020-01-02 22:42:40 +02:00
6ba727a781 promo spam loop 2020-01-02 18:49:38 +02:00
7f1829a5d7 Bump version: 0.17.3 → 0.18.0 2019-12-18 16:26:10 +02:00
e374aa8a54 Implemented division switching,
improved multi bomb deploy with auto traveling,
Citizen.fight() simplified battle data gathering logic -> Citizen.shoot logic improved
Citizen.all_battles are now defaultdict with default value of empty/invalid battle, for times when trying to do things with battle which is not in all_battle dict
2019-12-18 16:25:52 +02:00
7edfa3b004 Bump version: 0.17.2 → 0.17.3 2019-12-18 11:45:02 +02:00
12aee23739 Variable and method redeclaration 2019-12-18 11:44:18 +02:00
b7f8182ef5 Bump version: 0.17.1 → 0.17.2 2019-12-13 19:32:29 +02:00
39093accd0 Type hinting. Class parameter defined lists where shared accross instances. 2019-12-13 19:30:43 +02:00
aba8c15fd3 AutoPost medals 2019-12-03 15:22:35 +02:00
f294506a2d Updated wars list, added default weapon choosing (q7 - ground, bare hands - air) 2019-12-03 09:52:53 +02:00
fd56c6c389 By default sort battles by time 2019-12-03 09:44:14 +02:00
4f613ee5ac remvoed unused variables 2019-12-03 09:43:55 +02:00
a7dd528597 Citizen get() and post() signature update, check if server isn't compaining about request flooding 2019-12-03 09:42:51 +02:00
24c755d414 code style 2019-12-03 09:41:07 +02:00
13b639dc5a Bump version: 0.17.0 → 0.17.1 2019-11-21 14:04:49 +02:00
ec1141a46e set serialization 2019-11-21 14:04:43 +02:00
8 changed files with 452 additions and 195 deletions

View File

@ -1,6 +1,6 @@
* eRepublik script version: * eRepublik script version:
* Python version: * Python version:
* Operating System: * Operating System:
### Description ### Description
@ -9,7 +9,7 @@ Tell us what happened, what went wrong, and what you expected to happen.
### What I Did ### What I Did
``` ``` python
Paste the command(s) you ran and the output. Paste the command(s) you ran and the output.
If there was a crash, please include the traceback here. If there was a crash, please include the traceback here.
``` ```

View File

@ -43,6 +43,8 @@ clean-pyc: ## remove Python file artifacts
find . -name '*.pyo' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} + find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} + find . -name '__pycache__' -exec rm -fr {} +
rm -rf debug/
rm -rf log/
clean-test: ## remove test and coverage artifacts clean-test: ## remove test and coverage artifacts
rm -fr .tox/ rm -fr .tox/

View File

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

View File

@ -1,10 +1,11 @@
import re import re
import sys import sys
from threading import Event import warnings
from collections import defaultdict
from itertools import product
from datetime import datetime, timedelta from datetime import datetime, timedelta
from itertools import product
from json import loads, dumps from json import loads, dumps
from threading import Event
from time import sleep from time import sleep
from typing import Dict, List, Tuple, Any, Union, Set from typing import Dict, List, Tuple, Any, Union, Set
@ -12,8 +13,8 @@ from requests import Response, RequestException
from erepublik.classes import (CitizenAPI, Battle, Reporter, Config, Energy, Details, Politics, MyCompanies, from erepublik.classes import (CitizenAPI, Battle, Reporter, Config, Energy, Details, Politics, MyCompanies,
TelegramBot, ErepublikException, BattleDivision, MyJSONEncoder) TelegramBot, ErepublikException, BattleDivision, MyJSONEncoder)
from erepublik.utils import * from erepublik.utils import *
from erepublik.utils import process_warning
class Citizen(CitizenAPI): class Citizen(CitizenAPI):
@ -26,9 +27,9 @@ class Citizen(CitizenAPI):
active_fs: bool = False active_fs: bool = False
food = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0} food: Dict[str, int] = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
inventory = {"used": 0, "total": 0} inventory: Dict[str, int] = {"used": 0, "total": 0}
boosters = {100: {}, 50: {}} boosters: Dict[int, Dict[int, int]] = {100: {}, 50: {}}
eb_normal = 0 eb_normal = 0
eb_double = 0 eb_double = 0
@ -38,11 +39,19 @@ class Citizen(CitizenAPI):
ot_points = 0 ot_points = 0
tg_contract = None tg_contract = None
promos = None promos: Dict[str, datetime] = None
eday = 0 eday = 0
r: Response energy: Energy = None
details: Details = None
politics: Politics = None
my_companies: MyCompanies = None
reporter: Reporter = None
stop_threads: Event = None
telegram: TelegramBot = None
r: Response = None
name = "Not logged in!" name = "Not logged in!"
debug = False debug = False
__registered = False __registered = False
@ -164,6 +173,13 @@ class Citizen(CitizenAPI):
self.logged_in = True self.logged_in = True
def _errors_in_response(self, response: Response): def _errors_in_response(self, response: Response):
try:
j = response.json()
if j['error'] and j["message"] == 'Too many requests':
self.write_log("Made too many requests! Sleeping for 30 seconds.")
self.sleep(30)
except:
pass
if response.status_code >= 400: if response.status_code >= 400:
self.r = response self.r = response
if response.status_code >= 500: if response.status_code >= 500:
@ -174,7 +190,7 @@ class Citizen(CitizenAPI):
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"|not_authenticated', response.text))
def get(self, url: str, *args, **kwargs) -> Response: def get(self, url: str, **kwargs) -> Response:
if (self.now - self._req.last_time).seconds >= 15 * 60: if (self.now - self._req.last_time).seconds >= 15 * 60:
self.get_csrf_token() self.get_csrf_token()
if "params" in kwargs: if "params" in kwargs:
@ -188,7 +204,7 @@ class Citizen(CitizenAPI):
except RequestException as e: except RequestException as e:
self.write_log("Network error while issuing GET request", e) self.write_log("Network error while issuing GET request", e)
self.sleep(60) self.sleep(60)
return self.get(url, *args, **kwargs) return self.get(url, **kwargs)
try: try:
self.update_citizen_info(html=response.text) self.update_citizen_info(html=response.text)
@ -221,7 +237,7 @@ class Citizen(CitizenAPI):
except RequestException as e: except RequestException as e:
self.write_log("Network error while issuing POST request", e) self.write_log("Network error while issuing POST request", e)
self.sleep(60) self.sleep(60)
return self.post(url, data, json, **kwargs) return self.post(url, data=data, json=json, **kwargs)
try: try:
resp_data = response.json() resp_data = response.json()
@ -254,6 +270,9 @@ class Citizen(CitizenAPI):
r"<div title=\"(.*?)\">", medal, re.M | re.S) r"<div title=\"(.*?)\">", medal, re.M | re.S)
about = info.group(1).strip() about = info.group(1).strip()
title = info.group(2).strip() title = info.group(2).strip()
award_id = re.search(r'"wall_enable_alerts_(\d+)', medal)
if award_id:
self._post_main_wall_post_automatic(**{'message': title, 'awardId': award_id.group(1)})
reward, currency = info.group(3).strip().split(" ") reward, currency = info.group(3).strip().split(" ")
while not isinstance(reward, float): while not isinstance(reward, float):
try: try:
@ -310,7 +329,10 @@ class Citizen(CitizenAPI):
return return
ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1) ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1)
promos = loads(normalize_html_json(ugly_js)) promos = loads(normalize_html_json(ugly_js))
self.promos = {k: v for k, v in (self.promos.items() if self.promos else {}) if v > self.now} if self.promos is None:
self.promos = {}
else:
self.promos = {k: v for k, v in self.promos.items() if v > self.now}
send_mail = False send_mail = False
for promo in promos: for promo in promos:
promo_name = promo.get("id") promo_name = promo.get("id")
@ -367,7 +389,7 @@ class Citizen(CitizenAPI):
self._post_military_group_missions() self._post_military_group_missions()
self.details.next_pp.sort() self.details.next_pp.sort()
for id_, skill in citizen.get("mySkills", {}).items(): for skill in citizen.get("mySkills", {}).values():
self.details.mayhem_skills.update({int(skill["terrain_id"]): int(skill["skill_points"])}) self.details.mayhem_skills.update({int(skill["terrain_id"]): int(skill["skill_points"])})
if citizen.get('party', []): if citizen.get('party', []):
@ -537,10 +559,18 @@ class Citizen(CitizenAPI):
if not self.details.current_country: if not self.details.current_country:
self.update_citizen_info() self.update_citizen_info()
resp_json = self._get_military_campaigns().json() resp_json = self._get_military_campaigns_json_list().json()
if resp_json.get("countries"): if resp_json.get("countries"):
self.all_battles = {} if self.all_battles is None:
self.countries = {} self.all_battles = defaultdict(Battle)
else:
self.all_battles.clear()
if self.countries is None:
self.countries = {}
else:
self.countries.clear()
for c_id, c_data in resp_json.get("countries").items(): for c_id, c_data in resp_json.get("countries").items():
if int(c_id) not in self.countries: if int(c_id) not in self.countries:
self.countries.update({ self.countries.update({
@ -550,8 +580,8 @@ class Citizen(CitizenAPI):
self.countries[int(c_id)].update(allies=c_data.get("allies")) self.countries[int(c_id)].update(allies=c_data.get("allies"))
self.__last_war_update_data = resp_json self.__last_war_update_data = resp_json
if resp_json.get("battles"): if resp_json.get("battles"):
for battle_id, battle_data in resp_json.get("battles", {}).items(): for battle_data in resp_json.get("battles", {}).values():
self.all_battles.update({int(battle_id): Battle(battle_data)}) self.all_battles[battle_data.get('id')] = Battle(battle_data)
def eat(self): def eat(self):
""" """
@ -583,8 +613,7 @@ class Citizen(CitizenAPI):
r_json = response.json() r_json = response.json()
next_recovery = r_json.get("food_remaining_reset").split(":") next_recovery = r_json.get("food_remaining_reset").split(":")
self.energy.set_reference_time( self.energy.set_reference_time(
good_timedelta(self.now, good_timedelta(self.now, timedelta(seconds=int(next_recovery[1]) * 60 + int(next_recovery[2])))
timedelta(seconds=int(next_recovery[1]) * 60 + int(next_recovery[2])))
) )
self.energy.recovered = r_json.get("health") self.energy.recovered = r_json.get("health")
self.energy.recoverable = r_json.get("food_remaining") self.energy.recoverable = r_json.get("food_remaining")
@ -646,8 +675,7 @@ class Citizen(CitizenAPI):
break break
error_count = 0 error_count = 0
while self.energy.food_fights > 5 and error_count < 20: while self.energy.food_fights > 5 and error_count < 20:
errors = self.fight(battle_id, side_id=side, is_air=False, errors = self.fight(battle_id, side_id=side, count=self.energy.food_fights - 5)
count=self.energy.food_fights - 5)
if errors: if errors:
error_count += errors error_count += errors
if self.config.epic_hunt_ebs: if self.config.epic_hunt_ebs:
@ -659,7 +687,7 @@ class Citizen(CitizenAPI):
self.active_fs = active_fs self.active_fs = active_fs
def sorted_battles(self, sort_by_time: bool = False) -> List[int]: def sorted_battles(self, sort_by_time: bool = True) -> List[int]:
cs_battles_air: List[int] = [] cs_battles_air: List[int] = []
cs_battles_ground: List[int] = [] cs_battles_ground: List[int] = []
deployed_battles_air: List[int] = [] deployed_battles_air: List[int] = []
@ -670,8 +698,12 @@ class Citizen(CitizenAPI):
other_battles_ground: List[int] = [] other_battles_ground: List[int] = []
ret_battles = [] ret_battles = []
for bid, battle in sorted(self.all_battles.items(), key=lambda b: b[1].start if sort_by_time else b[0], if sort_by_time:
reverse=sort_by_time): battle_list = sorted(self.all_battles.values(), key=lambda b: b.start)
battle_list.reverse()
else:
battle_list = sorted(self.all_battles.values(), key=lambda b: b.id)
for battle in battle_list:
battle_sides = [battle.invader.id, battle.defender.id] battle_sides = [battle.invader.id, battle.defender.id]
# Previous battles # Previous battles
@ -682,35 +714,35 @@ class Citizen(CitizenAPI):
# CS Battles # CS Battles
elif self.details.citizenship in battle_sides: elif self.details.citizenship in battle_sides:
if battle.is_air: if battle.is_air:
cs_battles_ground.append(battle.id)
else:
cs_battles_air.append(battle.id) cs_battles_air.append(battle.id)
else:
cs_battles_ground.append(battle.id)
# Current location battles: # Current location battles:
elif self.details.current_country in battle_sides: elif self.details.current_country in battle_sides:
if battle.is_air: if battle.is_air:
deployed_battles_ground.append(battle.id)
else:
deployed_battles_air.append(battle.id) deployed_battles_air.append(battle.id)
else:
deployed_battles_ground.append(battle.id)
# Deployed battles and allied battles: # Deployed battles and allied battles:
elif self.details.current_country in battle.invader.allies + battle.defender.allies + battle_sides: elif self.details.current_country in battle.invader.allies + battle.defender.allies + battle_sides:
if self.details.current_country in battle.invader.deployed + battle.defender.deployed: if self.details.current_country in battle.invader.deployed + battle.defender.deployed:
if battle.is_air: if battle.is_air:
deployed_battles_ground.append(battle.id)
else:
deployed_battles_air.append(battle.id) deployed_battles_air.append(battle.id)
else:
deployed_battles_ground.append(battle.id)
# Allied battles: # Allied battles:
else: else:
if battle.is_air: if battle.is_air:
ally_battles_ground.append(battle.id)
else:
ally_battles_air.append(battle.id) ally_battles_air.append(battle.id)
else:
ally_battles_ground.append(battle.id)
else: else:
if battle.is_air: if battle.is_air:
other_battles_ground.append(battle.id)
else:
other_battles_air.append(battle.id) other_battles_air.append(battle.id)
else:
other_battles_ground.append(battle.id)
ret_battles += (cs_battles_air + cs_battles_ground + ret_battles += (cs_battles_air + cs_battles_ground +
deployed_battles_air + deployed_battles_ground + deployed_battles_air + deployed_battles_ground +
@ -779,15 +811,31 @@ class Citizen(CitizenAPI):
if not self.travel_to_battle(battle_id, country_ids_to_travel): if not self.travel_to_battle(battle_id, country_ids_to_travel):
break break
self.fight(battle_id, side_id, battle.is_air) self.fight(battle_id, side_id)
self.travel_to_residence() self.travel_to_residence()
self.collect_weekly_reward() self.collect_weekly_reward()
break break
def fight(self, battle_id: int, side_id: int, is_air: bool = False, count: int = None): def fight(self, battle_id: int, side_id: int = None, count: int = None) -> int:
if not is_air and self.config.boosters: """Fight in a battle.
Will auto activate booster and travel if allowed to do it and
in the beginning will switch to default weapon (air - bare hands, ground - q7, if count > 30, else bare hands.
:param battle_id: int BattleId - battle to fight in
:param side_id: int or None. Battle side to fight in, If side_id not == invader id or not in invader deployed allies list, then defender's side is chosen
:param count: How many hits to do, if not specified self.should_fight() is called.
:return: None if no errors while fighting, otherwise error count.
"""
if not isinstance(battle_id, int):
self.report_error(f"WARNINNG! Parameter battle_id should be 'int', but it is '{type(battle_id).__name__}'")
battle_id = int(battle_id)
if battle_id not in self.all_battles:
self.update_war_info()
battle = self.all_battles[battle_id]
zone_id = battle.div[11 if battle.is_air else self.division].battle_zone_id
if not battle.is_air and self.config.boosters:
self.activate_dmg_booster() self.activate_dmg_booster()
data = dict(sideId=side_id, battleId=battle_id) self.set_default_weapon(battle_id)
error_count = 0 error_count = 0
ok_to_fight = True ok_to_fight = True
if count is None: if count is None:
@ -795,10 +843,10 @@ class Citizen(CitizenAPI):
total_damage = 0 total_damage = 0
total_hits = 0 total_hits = 0
side = battle.invader.id == side_id or side_id in battle.invader.deployed
while ok_to_fight and error_count < 10 and count > 0: while ok_to_fight and error_count < 10 and count > 0:
while all((count > 0, error_count < 10, self.energy.recovered >= 50)): while all((count > 0, error_count < 10, self.energy.recovered >= 50)):
hits, error, damage = self._shoot(is_air, data) hits, error, damage = self._shoot(battle_id, side, zone_id)
count -= hits count -= hits
total_hits += hits total_hits += hits
total_damage += damage total_damage += damage
@ -809,16 +857,17 @@ class Citizen(CitizenAPI):
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(json_val=dict(battle=battle_id, side=side_id, dmg=total_damage, self.reporter.report_action("FIGHT", dict(battle=battle_id, side=side_id, dmg=total_damage,
air=is_air, hits=total_hits), action="FIGHT") air=battle.is_air, hits=total_hits))
if error_count: return error_count
return error_count
def _shoot(self, air: bool, data: dict): def _shoot(self, battle_id: int, inv_side: bool, zone_id: int):
if air: battle = self.all_battles[battle_id]
response = self._post_military_fight_air(data['battleId'], data['sideId']) side_id = battle.invader.id if inv_side else battle.defender.id
if battle.is_air:
response = self._post_military_fight_air(battle_id, side_id, zone_id)
else: else:
response = self._post_military_fight_ground(data['battleId'], data['sideId']) response = self._post_military_fight_ground(battle_id, side_id, zone_id)
if "Zone is not meant for " in response.text: if "Zone is not meant for " in response.text:
self.sleep(5) self.sleep(5)
@ -833,9 +882,14 @@ class Citizen(CitizenAPI):
if j_resp.get("error"): if j_resp.get("error"):
if j_resp.get("message") == "SHOOT_LOCKOUT" or j_resp.get("message") == "ZONE_INACTIVE": if j_resp.get("message") == "SHOOT_LOCKOUT" or j_resp.get("message") == "ZONE_INACTIVE":
pass pass
elif j_resp.get("message") == "NOT_ENOUGH_WEAPONS":
self.set_default_weapon(battle_id)
else: else:
if j_resp.get("message") == "UNKNOWN_SIDE": if j_resp.get("message") == "UNKNOWN_SIDE":
self._rw_choose_side(data["battleId"], data["sideId"]) self._rw_choose_side(battle_id, side_id)
elif j_resp.get("message") == "CHANGE_LOCATION":
countries = [side_id] + battle.invader.deployed if inv_side else battle.defender.deployed
self.travel_to_battle(battle_id, countries)
err = True err = True
elif j_resp.get("message") == "ENEMY_KILLED": elif j_resp.get("message") == "ENEMY_KILLED":
hits = (self.energy.recovered - j_resp["details"]["wellness"]) // 10 hits = (self.energy.recovered - j_resp["details"]["wellness"]) // 10
@ -847,9 +901,53 @@ class Citizen(CitizenAPI):
return hits, err, damage return hits, err, damage
def deploy_bomb(self, battle_id: int, bomb_id: int): def deploy_bomb(self, battle_id: int, bomb_id: int, inv_side: bool = None, count: int = 1) -> int:
r = self._post_military_deploy_bomb(battle_id, bomb_id).json() """Deploy bombs in a battle for given side.
return not r.get('error')
:param battle_id: int battle id
:param bomb_id: int bomb id
:param inv_side: should deploy on invader side, if None then will deploy in currently available side
:param count: int how many bombs to deploy
:return: Deployed count
:rtype: int
"""
if not isinstance(count, int) or count < 1:
count = 1
has_traveled = False
battle = self.all_battles[battle_id]
if inv_side:
good_countries = [battle.invader.id] + battle.invader.deployed
if self.details.current_country not in good_countries:
has_traveled = self.travel_to_battle(battle_id, good_countries)
elif inv_side is not None:
good_countries = [battle.defender.id] + battle.defender.deployed
if self.details.current_country not in good_countries:
has_traveled = self.travel_to_battle(battle_id, good_countries)
else:
involved = [battle.invader.id, battle.defender.id] + battle.invader.deployed + battle.defender.deployed
if self.details.current_country not in involved:
count = 0
errors = deployed_count = 0
while (not deployed_count == count) and errors < 10:
r = self._post_military_deploy_bomb(battle_id, bomb_id).json()
if not r.get('error'):
deployed_count += 1
elif r.get('message') == 'LOCKED':
sleep(0.5)
if has_traveled:
self.travel_to_residence()
return deployed_count
def change_division(self, battle_id: int, division_to: int):
"""Change division.
:param battle_id: int battle id
:param division_to: int target division to switch to
:return:
"""
battle = self.all_battles[battle_id]
self._post_main_battlefield_change_division(battle_id, battle.div[division_to].battle_zone_id)
def work_ot(self): def work_ot(self):
# I"m not checking for 1h cooldown. Beware of nightshift work, if calling more than once every 60min # I"m not checking for 1h cooldown. Beware of nightshift work, if calling more than once every 60min
@ -1012,8 +1110,8 @@ class Citizen(CitizenAPI):
if not self.travel_to_region(wam_holding['region_id']): if not self.travel_to_region(wam_holding['region_id']):
return False return False
response = self._post_economy_work("production", wam=wam_list, employ=employee_companies).json() response = self._post_economy_work("production", wam=wam_list, employ=employee_companies).json()
self.reporter.report_action("WORK_WAM_EMPLOYEES", response)
if response.get("status"): if response.get("status"):
self.reporter.report_action("WORK_WAM_EMPLOYEES", response)
if self.config.auto_sell: if self.config.auto_sell:
for kind, data in response.get("result", {}).get("production", {}).items(): for kind, data in response.get("result", {}).get("production", {}).items():
if kind in self.config.auto_sell and data: if kind in self.config.auto_sell and data:
@ -1049,7 +1147,9 @@ class Citizen(CitizenAPI):
self.buy_food() self.buy_food()
return self._do_wam_and_employee_work(wam_holding_id, employee_companies) return self._do_wam_and_employee_work(wam_holding_id, employee_companies)
else: else:
self.write_log("I was not able to wam and or employ because:\n{}".format(response)) msg = "I was not able to wam and or employ because:\n{}".format(response)
self.reporter.report_action("WORK_WAM_EMPLOYEES", response, msg)
self.write_log(msg)
wam_count = self.my_companies.get_total_wam_count() wam_count = self.my_companies.get_total_wam_count()
if wam_count: if wam_count:
self.write_log("Wam ff lockdown is now {}, was {}".format(wam_count, self.my_companies.ff_lockdown)) self.write_log("Wam ff lockdown is now {}, was {}".format(wam_count, self.my_companies.ff_lockdown))
@ -1323,7 +1423,7 @@ class Citizen(CitizenAPI):
if not self.get_active_ground_damage_booster(): if not self.get_active_ground_damage_booster():
duration = 0 duration = 0
for length, amount in self.boosters[50].items(): for length, amount in self.boosters[50].items():
if amount > 1: if amount > 2:
duration = length duration = length
break break
if duration: if duration:
@ -1471,7 +1571,7 @@ class Citizen(CitizenAPI):
if self.max_time_till_full_ff > self.time_till_week_change: if self.max_time_till_full_ff > self.time_till_week_change:
max_count = (int(self.time_till_week_change.total_seconds()) // 360 * self.energy.interval) // 10 max_count = (int(self.time_till_week_change.total_seconds()) // 360 * self.energy.interval) // 10
log_msg = ("End for Weekly challenge is near " log_msg = ("End for Weekly challenge is near "
f"(Recoverable until WC end {max_count}hp | want to do {count}hits)") f"(Recoverable until WC end {max_count}hp | want to do {count}hits)")
count = count if max_count > count else max_count count = count if max_count > count else max_count
@ -1494,8 +1594,7 @@ class Citizen(CitizenAPI):
@property @property
def next_wc_start(self) -> datetime: def next_wc_start(self) -> datetime:
days = 1 - self.now.weekday() if 1 - self.now.weekday() > 0 else 1 - self.now.weekday() + 7 days = 1 - self.now.weekday() if 1 - self.now.weekday() > 0 else 1 - self.now.weekday() + 7
return good_timedelta(self.now.replace(hour=0, minute=0, second=0, microsecond=0), return good_timedelta(self.now.replace(hour=0, minute=0, second=0, microsecond=0), timedelta(days=days))
timedelta(days=days))
@property @property
def time_till_week_change(self) -> timedelta: def time_till_week_change(self) -> timedelta:
@ -1565,12 +1664,9 @@ class Citizen(CitizenAPI):
if kind in kinds: if kind in kinds:
return self._post_main_write_article(title, content, self.details.citizenship, kind) return self._post_main_write_article(title, content, self.details.citizenship, kind)
else: else:
raise ErepublikException( raise ErepublikException("Article kind must be one of:\n{}\n'{}' is not supported".format(
"Article kind must be one of:\n{}\n'{}' is not supported".format( "\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), kind
"\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), ))
kind
)
)
def post_market_offer(self, industry: int, quality: int, amount: int, price: float) -> Response: def post_market_offer(self, industry: int, quality: int, amount: int, price: float) -> Response:
if industry not in self.available_industries.values(): if industry not in self.available_industries.values():
@ -1601,32 +1697,15 @@ class Citizen(CitizenAPI):
self.reporter.report_action("BUY_PRODUCT", ret.json()) self.reporter.report_action("BUY_PRODUCT", ret.json())
return json_ret return json_ret
def get_raw_surplus(self) -> (float, float):
frm = 0.00
wrm = 0.00
for cdata in sorted(self.my_companies.companies.values()):
if cdata["industry_token"] == "FOOD":
raw = frm
elif cdata["industry_token"] == "WEAPON":
raw = wrm
else:
continue
effective_bonus = cdata["effective_bonus"]
base_prod = float(cdata["base_production"])
if cdata["is_raw"]:
raw += base_prod * effective_bonus / 100
else:
raw -= effective_bonus / 100 * base_prod * cdata["upgrades"][str(cdata["quality"])]["raw_usage"]
if cdata["industry_token"] == "FOOD":
frm = raw
elif cdata["industry_token"] == "WEAPON":
wrm = raw
return frm, wrm
def assign_factory_to_holding(self, factory_id: int, holding_id: int) -> Response: def assign_factory_to_holding(self, factory_id: int, holding_id: int) -> Response:
""" """
Assigns factory to new holding Assigns factory to new holding
""" """
company = self.my_companies.companies[factory_id]
company_name = self.factories[company['industry_id']]
if not company['is_raw']:
company_name += f" q{company['quality']}"
self.write_log(f"{company_name} moved to {holding_id}")
return self._post_economy_assign_to_holding(factory_id, holding_id) return self._post_economy_assign_to_holding(factory_id, holding_id)
def upgrade_factory(self, factory_id: int, level: int) -> Response: def upgrade_factory(self, factory_id: int, level: int) -> Response:
@ -1640,9 +1719,18 @@ class Citizen(CitizenAPI):
Storage={1000: 1, 2000: 2} <- Building_type 2 Storage={1000: 1, 2000: 2} <- Building_type 2
""" """
company_name = self.factories[industry_id]
if building_type == 2:
company_name = f"Storage"
self.write_log(f"{company_name} created!")
return self._post_economy_create_company(industry_id, building_type) return self._post_economy_create_company(industry_id, building_type)
def dissolve_factory(self, factory_id: int) -> Response: def dissolve_factory(self, factory_id: int) -> Response:
company = self.my_companies.companies[factory_id]
company_name = self.factories[company['industry_id']]
if not company['is_raw']:
company_name += f" q{company['quality']}"
self.write_log(f"{company_name} dissolved!")
return self._post_economy_sell_company(factory_id, self.details.pin, sell=False) return self._post_economy_sell_company(factory_id, self.details.pin, sell=False)
@property @property
@ -1654,6 +1742,17 @@ class Citizen(CitizenAPI):
return {"food": 1, "weapon": 2, "house": 4, "aircraft": 23, return {"food": 1, "weapon": 2, "house": 4, "aircraft": 23,
"foodRaw": 7, "weaponRaw": 12, "houseRaw": 17, "airplaneRaw": 24} "foodRaw": 7, "weaponRaw": 12, "houseRaw": 17, "airplaneRaw": 24}
@property
def factories(self) -> Dict[int, str]:
"""Returns factory industries as dict(id: name)
:return: dict
"""
return {1: "Food", 2: "Weapons", 4: "House", 23: "Aircraft",
7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
12: "WRM q1", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
18: "HRM q1", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
24: "ARM q1", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5", }
def get_industry_id(self, industry_name: str) -> int: def get_industry_id(self, industry_name: str) -> int:
""" """
Returns industry id Returns industry id
@ -1784,12 +1883,12 @@ class Citizen(CitizenAPI):
if reg_re.findall(html): if reg_re.findall(html):
ret.update(regions={}, can_attack=True) ret.update(regions={}, can_attack=True)
for reg in reg_re.findall(html): for reg in reg_re.findall(html):
ret["regions"].update({str(reg[0]): reg[1]}) ret["regions"].update({int(reg[0]): reg[1]})
elif re.search(r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" ' elif re.search(r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" '
r'class="join" title="Join"><span>Join</span></a>', html): r'class="join" title="Join"><span>Join</span></a>', html):
battle_id = re.search(r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" ' battle_id = re.search(r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" '
r'class="join" title="Join"><span>Join</span></a>', html).group(1) r'class="join" title="Join"><span>Join</span></a>', html).group(1)
ret.update(can_attack=False, battle_id=battle_id) ret.update(can_attack=False, battle_id=int(battle_id))
elif re.search(r'This war is no longer active.', html): elif re.search(r'This war is no longer active.', html):
ret.update(can_attack=False, ended=True) ret.update(can_attack=False, ended=True)
else: else:
@ -1914,7 +2013,7 @@ class Citizen(CitizenAPI):
return False return False
data = dict(country=71, action='currency', value=amount) data = dict(country=71, action='currency', value=amount)
self.telegram.send_message(f"Donated {amount}cc to {COUNTRIES[71]}") self.telegram.send_message(f"Donated {amount}cc to {COUNTRIES[71]}")
self.reporter.report_action("CONTRIBUTE_CC", data) self.reporter.report_action("CONTRIBUTE_CC", data, str(amount))
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error') return r.json().get('status') or not r.json().get('error')
@ -1924,7 +2023,7 @@ class Citizen(CitizenAPI):
if self.food["q" + str(quality)] < amount or amount < 10: if self.food["q" + str(quality)] < amount or amount < 10:
return False return False
data = dict(country=71, action='food', value=amount, quality=quality) data = dict(country=71, action='food', value=amount, quality=quality)
self.reporter.report_action("CONTRIBUTE_FOOD", data) self.reporter.report_action("CONTRIBUTE_FOOD", data, FOOD_ENERGY[quality] * amount)
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error') return r.json().get('status') or not r.json().get('error')
@ -1934,7 +2033,7 @@ class Citizen(CitizenAPI):
if self.details.cc < amount: if self.details.cc < amount:
return False return False
data = dict(country=71, action='gold', value=amount) data = dict(country=71, action='gold', value=amount)
self.reporter.report_action("CONTRIBUTE_GOLD", data) self.reporter.report_action("CONTRIBUTE_GOLD", data, str(amount))
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error') return r.json().get('status') or not r.json().get('error')
@ -1945,23 +2044,26 @@ class Citizen(CitizenAPI):
r = self._post_main_country_post_create(message, max(post_to_wall_as, key=int) if post_to_wall_as else 0) r = self._post_main_country_post_create(message, max(post_to_wall_as, key=int) if post_to_wall_as else 0)
return r.json() return r.json()
def report_error(self, msg: str = ""): def report_error(self, msg: str = "", is_warning: bool = False):
process_error(msg, self.name, sys.exc_info(), self, self.commit_id, False) if is_warning:
process_warning(msg, self.name, sys.exc_info(), self, self.commit_id)
else:
process_error(msg, self.name, sys.exc_info(), self, self.commit_id, None)
def get_battle_top_10(self, battle_id: int) -> Dict[int, List[Tuple[int, int]]]: def get_battle_top_10(self, battle_id: int) -> Dict[int, List[Tuple[int, int]]]:
battle = self.all_battles.get(battle_id) return {}
round_id = battle.get('zone_id') # battle = self.all_battles.get(battle_id)
division = self.division if round_id % 4 else 11 # round_id = battle.zone_id
# division = self.division if round_id % 4 else 11
resp = self._post_military_battle_console(battle_id, round_id, division).json() #
resp.pop('rounds', None) # resp = self._post_military_battle_console(battle_id, 'battleStatistics', round_id, division).json()
ret = dict() # resp.pop('rounds', None)
for country_id, data in resp.items(): # ret = dict()
ret.update({int(country_id): []}) # for country_id, data in resp.items():
for place in sorted(data.get("fighterData", {}).values(), key=lambda _: -_['raw_value']): # ret.update({int(country_id): []})
ret[int(country_id)].append((place['citizenId'], place['raw_value'])) # for place in sorted(data.get("fighterData", {}).values(), key=lambda _: -_['raw_value']):
# ret[int(country_id)].append((place['citizenId'], place['raw_value']))
return ret # return ret
def to_json(self, indent: bool = False) -> str: def to_json(self, indent: bool = False) -> str:
return dumps(self.__dict__, cls=MyJSONEncoder, indent=4 if indent else None, sort_keys=True) return dumps(self.__dict__, cls=MyJSONEncoder, indent=4 if indent else None, sort_keys=True)
@ -2029,3 +2131,23 @@ class Citizen(CitizenAPI):
def speedup_map_quest_node(self, node_id: int): def speedup_map_quest_node(self, node_id: int):
node = self.get_anniversary_quest_data().get('cities', {}).get(str(node_id), {}) node = self.get_anniversary_quest_data().get('cities', {}).get(str(node_id), {})
return self._post_map_rewards_speedup(node_id, node.get("skipCost", 0)) return self._post_map_rewards_speedup(node_id, node.get("skipCost", 0))
def get_available_weapons(self, battle_id: int):
return self._get_military_show_weapons(battle_id).json()
def set_default_weapon(self, battle_id: int) -> int:
battle = self.all_battles[battle_id]
available_weapons = self._get_military_show_weapons(battle_id).json()
weapon_quality = -1
if not battle.is_air:
for weapon in available_weapons:
if weapon['weaponId'] == 7 and weapon['weaponQuantity'] > 30:
weapon_quality = 7
break
return self.change_weapon(battle_id, weapon_quality)
def change_weapon(self, battle_id: int, weapon_quality: int) -> int:
battle = self.all_battles[battle_id]
battle_zone = battle.div[11 if battle.is_air else self.division].battle_zone_id
r = self._post_military_change_weapon(battle_id, battle_zone, weapon_quality)
return r.json().get('weaponInfluence')

View File

@ -4,7 +4,7 @@ import hashlib
import random import random
import threading import threading
import time import time
from collections import deque from collections import deque, defaultdict
from json import JSONDecodeError, loads, JSONEncoder from json import JSONDecodeError, loads, JSONEncoder
from typing import Any, Dict, List, Union, Mapping, Iterable, Tuple from typing import Any, Dict, List, Union, Mapping, Iterable, Tuple
@ -40,7 +40,7 @@ class MyCompanies:
""" """
:param holdings: Parsed JSON to dict from en/economy/myCompanies :param holdings: Parsed JSON to dict from en/economy/myCompanies
""" """
self.holdings = {} self.holdings.clear()
template = dict(id=0, num_factories=0, region_id=0, companies=[]) template = dict(id=0, num_factories=0, region_id=0, companies=[])
for holding_id, holding in holdings.items(): for holding_id, holding in holdings.items():
@ -57,18 +57,20 @@ class MyCompanies:
""" """
:param companies: Parsed JSON to dict from en/economy/myCompanies :param companies: Parsed JSON to dict from en/economy/myCompanies
""" """
self.companies = {} self.companies.clear()
template = dict(id=None, quality=0, is_raw=False, resource_bonus=0, effective_bonus=0, raw_usage=0, template = dict(id=None, quality=0, is_raw=False, resource_bonus=0, effective_bonus=0, raw_usage=0,
production=0, base_production=0, wam_enabled=False, can_work_as_manager=False, base_production=0, wam_enabled=False, can_work_as_manager=False, industry_id=0, todays_works=0,
preset_own_work=0, already_worked=False, can_assign_employees=False, preset_works=0, preset_own_work=0, already_worked=False, can_assign_employees=False, preset_works=0,
todays_works=0, holding_company_id=None, is_assigned_to_holding=False, holding_company_id=None, is_assigned_to_holding=False, cannot_work_as_manager_reason=False)
cannot_work_as_manager_reason=False, industry_id=0)
for c_id, company in companies.items(): for c_id, company in companies.items():
tmp = {} tmp = {}
for key in template.keys(): for key in template.keys():
if key in ['id', 'holding_company_id']: if key in ['id', 'holding_company_id']:
company[key] = int(company[key]) company[key] = int(company[key])
elif key == "raw_usage":
if not company.get("is_raw") and company.get('upgrades'):
company[key] = company.get('upgrades').get(str(company["quality"])).get('raw_usage')
tmp.update({key: company[key]}) tmp.update({key: company[key]})
self.companies.update({int(c_id): tmp}) self.companies.update({int(c_id): tmp})
@ -76,9 +78,8 @@ class MyCompanies:
for company_id, company_data in self.companies.items(): for company_id, company_data in self.companies.items():
if company_id not in self.holdings[company_data['holding_company_id']]['companies']: if company_id not in self.holdings[company_data['holding_company_id']]['companies']:
self.holdings[company_data['holding_company_id']]['companies'].append(company_id) self.holdings[company_data['holding_company_id']]['companies'].append(company_id)
else: for holding_id in self.holdings:
for holding_id in self.holdings: self.holdings[holding_id]['companies'].sort()
self.holdings[holding_id]['companies'].sort()
def get_employable_factories(self) -> Dict[int, int]: def get_employable_factories(self) -> Dict[int, int]:
ret = {} ret = {}
@ -160,6 +161,31 @@ class MyCompanies:
raise ErepublikException("Wrong function call") raise ErepublikException("Wrong function call")
def get_wam_raw_usage(self) -> Dict[str, float]:
frm = 0.00
wrm = 0.00
for company in self.companies.values():
if company['wam_enabled']:
effective_bonus = float(company["effective_bonus"])
base_prod = float(company["base_production"])
raw = base_prod * effective_bonus / 100
if not company["is_raw"]:
raw *= -company["raw_usage"]
if company["industry_id"] in [1, 7, 8, 9, 10, 11]:
frm += raw
elif company["industry_id"] in [2, 12, 13, 14, 15, 16]:
wrm += raw
return {'frm': int(frm * 1000) / 1000, 'wrm': int(wrm * 1000) / 1000}
def __str__(self):
name = []
for holding_id in sorted(self.holdings.keys()):
if not holding_id:
name.append(f"Unassigned - {len(self.holdings[0]['companies'])}")
else:
name.append(f"{holding_id} - {len(self.holdings[holding_id]['companies'])}")
return " | ".join(name)
# @property # @property
# def __dict__(self): # def __dict__(self):
# ret = {} # ret = {}
@ -174,19 +200,19 @@ class SlowRequests(Session):
timeout = datetime.timedelta(milliseconds=500) timeout = datetime.timedelta(milliseconds=500)
uas = [ uas = [
# Chrome # Chrome
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 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', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 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', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36',
# FireFox # FireFox
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0',
] ]
debug = False debug = False
@ -271,7 +297,7 @@ class Config:
work = True work = True
train = True train = True
wam = False wam = False
auto_sell: List[str] = list() auto_sell: List[str] = None
auto_sell_all = False auto_sell_all = False
employees = False employees = False
fight = False fight = False
@ -295,6 +321,9 @@ class Config:
telegram_chat_id = 0 telegram_chat_id = 0
telegram_token = "" telegram_token = ""
def __init__(self):
self.auto_sell = []
@property @property
def wt(self): def wt(self):
return self.work and self.train return self.work and self.train
@ -379,7 +408,7 @@ class Details:
pp = 0 pp = 0
pin = None pin = None
gold = 0 gold = 0
next_pp: List[int] = [] next_pp: List[int] = None
citizen_id = 0 citizen_id = 0
citizenship = 0 citizenship = 0
current_region = 0 current_region = 0
@ -390,6 +419,9 @@ class Details:
daily_task_reward = False daily_task_reward = 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 = {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, }
def __init__(self):
self.next_pp = []
@property @property
def xp_till_level_up(self): def xp_till_level_up(self):
if self.xp >= 10000: if self.xp >= 10000:
@ -452,8 +484,8 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
""" """
self._req = SlowRequests() self._req = SlowRequests()
def post(self, url: str, *args, **kwargs) -> Response: def post(self, url: str, data=None, json=None, **kwargs) -> Response:
return self._req.post(url, *args, **kwargs) return self._req.post(url, data, json, **kwargs)
def get(self, url: str, **kwargs) -> Response: def get(self, url: str, **kwargs) -> Response:
return self._req.get(url, **kwargs) return self._req.get(url, **kwargs)
@ -464,6 +496,9 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_military_battlefield_choose_side(self, battle: int, side: int) -> Response: def _get_military_battlefield_choose_side(self, battle: int, side: int) -> Response:
return self.get("{}/military/battlefield-choose-side/{}/{}".format(self.url, battle, side)) return self.get("{}/military/battlefield-choose-side/{}/{}".format(self.url, battle, side))
def _get_military_show_weapons(self, battle: int) -> Response:
return self.get("{}/military/show-weapons".format(self.url), params={'_token': self.token, 'battleId': battle})
def _get_candidate_party(self, party_slug: str) -> Response: def _get_candidate_party(self, party_slug: str) -> Response:
return self.post("{}/candidate/{}".format(self.url, party_slug)) return self.post("{}/candidate/{}".format(self.url, party_slug))
@ -529,6 +564,9 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_military_campaigns(self) -> Response: def _get_military_campaigns(self) -> Response:
return self.get("{}/military/campaigns-new/".format(self.url)) return self.get("{}/military/campaigns-new/".format(self.url))
def _get_military_campaigns_json_list(self) -> Response:
return self.get("{}/military/campaignsJson/list".format(self.url))
def _get_military_show_weapons(self, battle_id: int) -> Response: def _get_military_show_weapons(self, battle_id: int) -> Response:
params = {"_token": self.token, "battleId": battle_id} params = {"_token": self.token, "battleId": battle_id}
return self.get("{}/military/show-weapons".format(self.url), params=params) return self.get("{}/military/show-weapons".format(self.url), params=params)
@ -587,6 +625,10 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(_token=self.token, sideCountryId=side_id, battleId=battle_id) data = dict(_token=self.token, sideCountryId=side_id, battleId=battle_id)
return self.post("{}/main/battlefieldTravel".format(self.url), data=data) return self.post("{}/main/battlefieldTravel".format(self.url), data=data)
def _post_main_battlefield_change_division(self, battle_id: int, division_id: int) -> Response:
data = dict(_token=self.token, battleZoneId=division_id, battleId=battle_id)
return self.post("{}/main/battlefieldTravel".format(self.url), data=data)
def _post_main_buy_gold_items(self, currency: str, item: str, amount: int) -> Response: def _post_main_buy_gold_items(self, currency: str, item: str, amount: int) -> Response:
data = dict(itemId=item, currency=currency, amount=amount, _token=self.token) data = dict(itemId=item, currency=currency, amount=amount, _token=self.token)
return self.post("{}/main/buyGoldItems".format(self.url), data=data) return self.post("{}/main/buyGoldItems".format(self.url), data=data)
@ -757,6 +799,10 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(type=kind, quality=quality, duration=duration, battleId=battle, _token=self.token) data = dict(type=kind, quality=quality, duration=duration, battleId=battle, _token=self.token)
return self.post("{}/military/fight-activateBooster".format(self.url), data=data) return self.post("{}/military/fight-activateBooster".format(self.url), data=data)
def _post_military_change_weapon(self, battle: int, battle_zone: int, weapon_level: int,) -> Response:
data = dict(battleId=battle, _token=self.token, battleZoneId=battle_zone, customizationLevel=weapon_level)
return self.post("{}/military/change-weapon".format(self.url), data=data)
def _post_login(self, email: str, password: str) -> Response: def _post_login(self, email: str, password: str) -> Response:
data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember='on') data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember='on')
return self.post("{}/login".format(self.url), data=data) return self.post("{}/login".format(self.url), data=data)
@ -780,21 +826,16 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data.update(page=page) data.update(page=page)
return self.post("{}/military/battle-console".format(self.url), data=data) return self.post("{}/military/battle-console".format(self.url), data=data)
def _post_military_change_weapon(self, battle_id: int, battle_zone_id: int, customization_level: int) -> Response:
data = dict(_token=self.token, battleZoneId=battle_zone_id, battleId=battle_id,
customizationLevel=customization_level)
return self.post("{}/military/change-weapon".format(self.url), data=data)
def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response: def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response:
data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token) data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token)
return self.post("{}/military/deploy-bomb".format(self.url), data=data) return self.post("{}/military/deploy-bomb".format(self.url), data=data)
def _post_military_fight_air(self, battle_id: int, side_id: int) -> Response: def _post_military_fight_air(self, battle_id: int, side_id: int, zone_id: int) -> Response:
data = dict(sideId=side_id, battleId=battle_id, _token=self.token) data = dict(sideId=side_id, battleId=battle_id, _token=self.token, battleZoneId=zone_id)
return self.post("{}/military/fight-shoooot/{}".format(self.url, battle_id), data=data) return self.post("{}/military/fight-shoooot/{}".format(self.url, battle_id), data=data)
def _post_military_fight_ground(self, battle_id: int, side_id: int) -> Response: def _post_military_fight_ground(self, battle_id: int, side_id: int, zone_id: int) -> Response:
data = dict(sideId=side_id, battleId=battle_id, _token=self.token) data = dict(sideId=side_id, battleId=battle_id, _token=self.token, battleZoneId=zone_id)
return self.post("{}/military/fight-shooot/{}".format(self.url, battle_id), data=data) return self.post("{}/military/fight-shooot/{}".format(self.url, battle_id), data=data)
def _post_military_group_missions(self) -> Response: def _post_military_group_missions(self) -> Response:
@ -894,6 +935,10 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = {"_token": self.token, "post_message": body} data = {"_token": self.token, "post_message": body}
return self.post("{}/main/wall-post/create/json".format(self.url), data=data) return self.post("{}/main/wall-post/create/json".format(self.url), data=data)
def _post_main_wall_post_automatic(self, **kwargs) -> Response:
kwargs.update(_token=self.token)
return self.post("{}/main/wall-post/create/json".format(self.url), data=kwargs)
def _post_main_wall_post_retrieve(self) -> Response: def _post_main_wall_post_retrieve(self) -> Response:
data = {"_token": self.token, "page": 1, "switchedFrom": False} data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post("{}/main/wall-post/retrieve/json".format(self.url), data=data) return self.post("{}/main/wall-post/retrieve/json".format(self.url), data=data)
@ -914,9 +959,19 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = {'nodeId': node_id, '_token': self.token} data = {'nodeId': node_id, '_token': self.token}
return self.post("{}/main/map-rewards-claim".format(self.url), data=data) return self.post("{}/main/map-rewards-claim".format(self.url), data=data)
def _post_new_war(self, self_country_id: int, attack_country_id: int, debate: str = "") -> Response:
data = dict(requirments=1, _token=self.token, debate=debate,
countryNameConfirm=utils.COUNTRY_LINK[attack_country_id])
return self.post("{}/{}/new-war".format(self.url, utils.COUNTRY_LINK[self_country_id]), data=data)
def _post_new_donation(self, country_id: int, amount: int, org_name: str, debate: str = "") -> Response:
data = dict(requirments=1, _token=self.token, debate=debate, currency=1, value=amount, commit='Propose',
type_name=org_name)
return self.post("{}/{}/new-donation".format(self.url, utils.COUNTRY_LINK[country_id]), data=data)
class Reporter: class Reporter:
__to_update: List[Dict[Any, Any]] = [] __to_update: List[Dict[Any, Any]] = None
name: str = "" name: str = ""
email: str = "" email: str = ""
citizen_id: int = 0 citizen_id: int = 0
@ -932,6 +987,7 @@ class Reporter:
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": "Bot reporter v2"})
self.__to_update = []
self.__registered: bool = False self.__registered: bool = False
def do_init(self, name: str = "", email: str = "", citizen_id: int = 0): def do_init(self, name: str = "", email: str = "", citizen_id: int = 0):
@ -1010,7 +1066,7 @@ class MyJSONEncoder(JSONEncoder):
return dict(headers=o.headers.__dict__, url=o.url, text=o.text) return dict(headers=o.headers.__dict__, url=o.url, text=o.text)
elif hasattr(o, '__dict__'): elif hasattr(o, '__dict__'):
return o.__dict__ return o.__dict__
elif isinstance(o, deque): elif isinstance(o, (deque, set)):
return list(o) return list(o)
elif isinstance(o, Citizen): elif isinstance(o, Citizen):
return o.to_json() return o.to_json()
@ -1033,53 +1089,90 @@ class BattleSide:
class BattleDivision: class BattleDivision:
end: datetime.datetime end: datetime.datetime
epic: bool epic: bool
dom_pts: Dict[str, int] = None dom_pts: Dict[str, int]
wall: Dict[str, Union[int, float]] = None wall: Dict[str, Union[int, float]]
battle_zone_id: int
def_medal: Dict[str, int]
inv_medal: Dict[str, int]
@property @property
def div_end(self) -> bool: def div_end(self) -> bool:
return utils.now() >= self.end return utils.now() >= self.end
def __init__(self, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int, wall_for: int, wall_dom: float): def __init__(self, **kwargs):
self.end = end """Battle division helper class
self.epic = epic
self.dom_pts = dict({"inv": inv_pts, "def": def_pts}) :param kwargs: must contain keys:
self.wall = dict({"for": wall_for, "dom": wall_dom}) div_id: int, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int,
wall_for: int, wall_dom: float, def_medal: Tuple[int, int], inv_medal: Tuple[int, int]
"""
self.battle_zone_id = kwargs.get("div_id", 0)
self.end = kwargs.get("end", 0)
self.epic = kwargs.get("epic", 0)
self.dom_pts = dict({"inv": kwargs.get("inv_pts", 0), "def": kwargs.get("def_pts", 0)})
self.wall = dict({"for": kwargs.get("wall_for", 0), "dom": kwargs.get("wall_dom", 0)})
self.def_medal = {"id": kwargs.get("def_medal", 0)[0], "dmg": kwargs.get("def_medal", 0)[1]}
self.inv_medal = {"id": kwargs.get("inv_medal", 0)[0], "dmg": kwargs.get("inv_medal", 0)[1]}
@property
def id(self):
return self.battle_zone_id
class Battle: class Battle:
id: int = 0 id: int
war_id: int = 0 war_id: int
zone_id: int = 0 zone_id: int
is_rw: bool = False is_rw: bool
is_dict_lib: bool = False is_dict_lib: bool
start: datetime.datetime = None start: datetime.datetime
invader: BattleSide = None invader: BattleSide
defender: BattleSide = None defender: BattleSide
div: Dict[int, BattleDivision] = None div: Dict[int, BattleDivision]
@property @property
def is_air(self) -> bool: def is_air(self) -> bool:
return not bool(self.zone_id % 4) return not bool(self.zone_id % 4)
def __init__(self, battle: Dict[str, Any]): def __init__(self, battle: Dict[str, Any] = None):
self.id = int(battle.get('id', 0)) """Object representing eRepublik battle.
self.war_id = int(battle.get('war_id', 0))
self.zone_id = int(battle.get('zone_id', 0))
self.is_rw = bool(battle.get('is_rw'))
self.is_as = bool(battle.get('is_as'))
self.is_dict_lib = bool(battle.get('is_dict')) or bool(battle.get('is_lib'))
self.start = datetime.datetime.fromtimestamp(int(battle.get('start', 0)), tz=utils.erep_tz)
self.invader = BattleSide(battle.get('inv', {}).get('id'), battle.get('inv', {}).get('points'), :param battle: Dict object for single battle from '/military/campaignsJson/list' response's 'battles' object
[row.get('id') for row in battle.get('inv', {}).get('ally_list')], """
[row.get('id') for row in battle.get('inv', {}).get('ally_list') if row['deployed']]) if battle is None:
battle = {}
self.id = 0
self.war_id = 0
self.zone_id = 0
self.is_rw = False
self.is_as = False
self.is_dict_lib = False
self.start = utils.now().min
self.invader = BattleSide(0, 0, [], [])
self.defender = BattleSide(0, 0, [], [])
else:
self.id = int(battle.get('id', 0))
self.war_id = int(battle.get('war_id', 0))
self.zone_id = int(battle.get('zone_id', 0))
self.is_rw = bool(battle.get('is_rw'))
self.is_as = bool(battle.get('is_as'))
self.is_dict_lib = bool(battle.get('is_dict')) or bool(battle.get('is_lib'))
self.start = datetime.datetime.fromtimestamp(int(battle.get('start', 0)), tz=utils.erep_tz)
self.defender = BattleSide(battle.get('def', {}).get('id'), battle.get('def', {}).get('points'), self.invader = BattleSide(
[row.get('id') for row in battle.get('def', {}).get('ally_list')], battle.get('inv', {}).get('id'), battle.get('inv', {}).get('points'),
[row.get('id') for row in battle.get('def', {}).get('ally_list') if row['deployed']]) [row.get('id') for row in battle.get('inv', {}).get('ally_list')],
[row.get('id') for row in battle.get('inv', {}).get('ally_list') if row['deployed']]
)
self.div = {} self.defender = BattleSide(
battle.get('def', {}).get('id'), battle.get('def', {}).get('points'),
[row.get('id') for row in battle.get('def', {}).get('ally_list')],
[row.get('id') for row in battle.get('def', {}).get('ally_list') if row['deployed']]
)
self.div = defaultdict(BattleDivision)
for div, data in battle.get('div', {}).items(): for div, data in battle.get('div', {}).items():
div = int(data.get('div')) div = int(data.get('div'))
if data.get('end'): if data.get('end'):
@ -1087,11 +1180,18 @@ class Battle:
else: else:
end = utils.localize_dt(datetime.datetime.max - datetime.timedelta(days=1)) end = utils.localize_dt(datetime.datetime.max - datetime.timedelta(days=1))
battle_div = BattleDivision( if not data['stats']['def']:
end=end, epic=data.get('epic_type') in [1, 5], def_medal = (0, 0)
inv_pts=data.get('dom_pts').get("inv"), def_pts=data.get('dom_pts').get("def"), else:
wall_for=data.get('wall').get("for"), wall_dom=data.get('wall').get("dom") def_medal = (data['stats']['def']['citizenId'], data['stats']['def']['damage'])
) if not data['stats']['inv']:
inv_medal = (0, 0)
else:
inv_medal = (data['stats']['inv']['citizenId'], data['stats']['inv']['damage'])
battle_div = BattleDivision(end=end, epic=data.get('epic_type') in [1, 5], div_id=data.get('id'),
inv_pts=data.get('dom_pts').get("inv"), def_pts=data.get('dom_pts').get("def"),
wall_for=data.get('wall').get("for"), wall_dom=data.get('wall').get("dom"),
def_medal=def_medal, inv_medal=inv_medal)
self.div.update({div: battle_div}) self.div.update({div: battle_div})
@ -1141,17 +1241,19 @@ class EnergyToFight:
class TelegramBot: class TelegramBot:
__initialized = False __initialized = False
__queue: List[str] = [] __queue: List[str]
chat_id = 0 chat_id = 0
api_url = "" api_url = ""
player_name = "" player_name = ""
__thread_stopper: threading.Event = None __thread_stopper: threading.Event
_last_time: datetime.datetime = None _last_time: datetime.datetime
_last_full_energy_report: datetime.datetime = None _last_full_energy_report: datetime.datetime
_next_time: datetime.datetime = None _next_time: datetime.datetime
_threads: List[threading.Thread] = [] _threads: List[threading.Thread]
def __init__(self, stop_event: threading.Event = None): def __init__(self, stop_event: threading.Event = None):
self._threads = []
self.__queue = []
self.__thread_stopper = threading.Event() if stop_event is None else stop_event self.__thread_stopper = threading.Event() if stop_event is None else stop_event
def __dict__(self): def __dict__(self):

View File

@ -8,7 +8,7 @@ import time
import traceback import traceback
import unicodedata import unicodedata
from pathlib import Path from pathlib import Path
from typing import Union, Any, List, NoReturn, Mapping from typing import Union, Any, List, NoReturn, Mapping, Optional
import pytz import pytz
import requests import requests
@ -17,7 +17,7 @@ __all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", 'COUNTRY_LINK',
"now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday", "now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday",
"get_sleep_seconds", "interactive_sleep", "silent_sleep", "get_sleep_seconds", "interactive_sleep", "silent_sleep",
"write_silent_log", "write_interactive_log", "get_file", "write_file", "write_silent_log", "write_interactive_log", "get_file", "write_file",
"send_email", "normalize_html_json", "process_error", 'report_promo', 'calculate_hit'] "send_email", "normalize_html_json", "process_error", "process_warning", 'report_promo', 'calculate_hit']
FOOD_ENERGY = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20) FOOD_ENERGY = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
COMMIT_ID = "7b92e19" COMMIT_ID = "7b92e19"
@ -288,10 +288,43 @@ def normalize_html_json(js: str) -> str:
def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None, def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None,
interactive: bool = False): interactive: Optional[bool] = None):
""" """
Process error logging and email sending to developer Process error logging and email sending to developer
:param interactive: Should print interactively :param interactive: Should print interactively
:type interactive: bool
:param log_info: String to be written in output
:type log_info: str
:param name: String Instance name
:type name: str
:param exc_info: tuple output from sys.exc_info()
:type exc_info: tuple
:param citizen: Citizen instance
:type citizen: Citizen
:param commit_id: Code's version by commit id
:type commit_id: str
"""
type_, value_, traceback_ = exc_info
content = [log_info]
if commit_id:
content += ["Commit id: %s" % commit_id]
content += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))]
if interactive:
write_interactive_log(log_info)
elif interactive is not None:
write_silent_log(log_info)
trace = inspect.trace()
if trace:
trace = trace[-1][0].f_locals
else:
trace = dict()
send_email(name, content, citizen, local_vars=trace)
def process_warning(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None):
"""
Process error logging and email sending to developer
:param log_info: String to be written in output :param log_info: String to be written in output
:param name: String Instance name :param name: String Instance name
:param exc_info: tuple output from sys.exc_info() :param exc_info: tuple output from sys.exc_info()
@ -299,19 +332,17 @@ def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commi
:param commit_id: Code's version by commit id :param commit_id: Code's version by commit id
""" """
type_, value_, traceback_ = exc_info type_, value_, traceback_ = exc_info
bugtrace = [] if not commit_id else ["Commit id: %s" % commit_id, ] content = [log_info]
bugtrace += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))] if commit_id:
content += ["Commit id: %s" % commit_id]
content += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))]
if interactive:
write_interactive_log(log_info)
else:
write_silent_log(log_info)
trace = inspect.trace() trace = inspect.trace()
if trace: if trace:
trace = trace[-1][0].f_locals trace = trace[-1][0].f_locals
else: else:
trace = dict() trace = dict()
send_email(name, bugtrace, citizen, local_vars=trace) send_email(name, content, citizen, local_vars=trace)
def report_promo(kind: str, time_untill: datetime.datetime) -> NoReturn: def report_promo(kind: str, time_untill: datetime.datetime) -> NoReturn:

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.17.0 current_version = 0.18.3
commit = True commit = True
tag = True tag = True

View File

@ -42,6 +42,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.17.0', version='0.18.3',
zip_safe=False, zip_safe=False,
) )