Compare commits

...

15 Commits

Author SHA1 Message Date
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
10 changed files with 129 additions and 78 deletions

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
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"""
__email__ = 'eriks@72.lv'
__version__ = '0.21.1'
__version__ = '0.21.3'
from erepublik import classes, utils, constants
from erepublik.citizen import Citizen

View File

@ -15,24 +15,26 @@ class SlowRequests(Session):
timeout = datetime.timedelta(milliseconds=500)
uas = [
# 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/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 (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/84.0.4147.125 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/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
'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:75.0) Gecko/20100101 Firefox/75.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:73.0) Gecko/20100101 Firefox/73.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:74.0) Gecko/20100101 Firefox/74.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.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:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (X11; Linux x86_64; rv: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
@ -433,7 +435,7 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
class ErepublikPoliticsAPI(CitizenBaseAPI):
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:
return self.get(f"{self.url}/main/party-members/{party_id}")
@ -448,6 +450,13 @@ class ErepublikPoliticsAPI(CitizenBaseAPI):
def _get_presidential_elections(self, country_id: int, timestamp: int) -> Response:
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):
def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response:

View File

@ -393,7 +393,7 @@ class BaseCitizen(access_points.CitizenAPI):
sleep(seconds)
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]:
r_json = self._post_main_travel_data().json()
@ -1314,9 +1314,6 @@ class CitizenMilitary(CitizenTravel):
boosters: Dict[int, Dict[int, int]] = {100: {}, 50: {}}
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',
0) + 30 > self.now.timestamp():
r_json = self.__last_war_update_data
@ -1341,7 +1338,10 @@ class CitizenMilitary(CitizenTravel):
all_battles = {}
for battle_data in r_json.get("battles", {}).values():
all_battles[battle_data.get('id')] = classes.Battle(battle_data)
old_all_battles = self.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]:
self.update_war_info()
@ -1516,35 +1516,33 @@ class CitizenMilitary(CitizenTravel):
ret_battles = ret_battles + cs_battles + deployed_battles + other_battles
return ret_battles
def get_cheap_tp_divisions(self) -> Optional[classes.BattleDivision]:
air_divs: List[Tuple[classes.BattleDivision, int]] = []
ground_divs: List[Tuple[classes.BattleDivision, int]] = []
def get_cheap_tp_divisions(self) -> Dict[str, List[Tuple[int, classes.BattleDivision]]]:
air_divs: List[Tuple[int, classes.BattleDivision]] = []
ground_divs: List[Tuple[int, classes.BattleDivision]] = []
for battle in reversed(self.sorted_battles(True, True)):
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:
medal = self.get_battle_round_data(division)[
self.details.citizenship == division.battle.defender.id]
if not medal and division.battle.start:
return division
division_medals = self.get_battle_round_data(division)
medal = division_medals[self.details.citizenship == division.battle.defender.country]
if not medal:
air_divs.append((0, division))
else:
air_divs.append((division, medal.get('1').get('raw_value')))
elif self.config.ground:
air_divs.append((medal.get('1').get('raw_value'), division))
elif not division.is_air and self.config.ground:
if not division.div == self.division and not self.maverick:
continue
division_medals = self.get_battle_round_data(division)
medal = division_medals[self.details.citizenship == division.battle.defender.country]
if not medal and division.battle.start:
return division
if not medal:
ground_divs.append((0, division))
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:
return min(air_divs, key=lambda x: x[1])[0]
elif self.config.ground:
return min(ground_divs, key=lambda x: x[1])[0]
else:
return
air_divs.sort(key=lambda z: (z[0], z[1].battle.start))
ground_divs.sort(key=lambda z: (z[0], z[1].battle.start))
return {'air': air_divs, 'ground': ground_divs}
@property
def has_battle_contribution(self):
@ -1611,7 +1609,8 @@ class CitizenMilitary(CitizenTravel):
self.travel_to_residence()
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.
Will auto activate booster and travel if allowed to do it.
@ -1657,7 +1656,7 @@ class CitizenMilitary(CitizenTravel):
self.write_log("Hits: {:>4} | Damage: {}".format(total_hits, total_damage))
ok_to_fight = False
if total_damage:
self.reporter.report_action("FIGHT", dict(battle=battle, side=side, dmg=total_damage,
self.reporter.report_action("FIGHT", dict(battle=str(battle), side=str(side), dmg=total_damage,
air=battle.has_air, hits=total_hits))
return error_count
@ -1765,7 +1764,8 @@ class CitizenMilitary(CitizenTravel):
if resp.json().get('error'):
self.write_log(resp.json().get('message'))
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
def get_ground_hit_dmg_value(self, rang: int = None, strength: float = None, elite: bool = None, ne: bool = False,
@ -1965,6 +1965,13 @@ class CitizenMilitary(CitizenTravel):
return member.get('panelContents', {}).get('members', [{}])[0].get('dailyOrdersCompleted')
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):
def get_country_parties(self, country: constants.Country = None) -> dict:

View File

@ -1,6 +1,7 @@
import datetime
import hashlib
import threading
import weakref
from decimal import Decimal
from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union
@ -27,9 +28,10 @@ class Holding:
id: int
region: int
companies: List["Company"]
_citizen = weakref.ReferenceType
def __init__(self, _id: int, region: int, citizen):
self.citizen = citizen
self._citizen = weakref.ref(citizen)
self.id: int = _id
self.region: int = region
self.companies: List["Company"] = list()
@ -93,8 +95,13 @@ class Holding:
def as_dict(self):
return dict(name=str(self), id=self.id, region=self.region, companies=self.companies, wam_count=self.wam_count)
@property
def citizen(self):
return self._citizen()
class Company:
_holding: weakref.ReferenceType
holding: Holding
id: int
quality: int
@ -113,7 +120,7 @@ class Company:
base_production: Decimal, wam_enabled: bool, can_wam: bool, cannot_wam_reason: str, industry: int,
already_worked: bool, preset_works: int
):
self.holding: Holding = holding
self._holding = weakref.ref(holding)
self.id: int = _id
self.industry: int = industry
self.quality: int = self._get_real_quality(quality)
@ -211,6 +218,10 @@ class Company:
# noinspection PyProtectedMember
return self.holding.citizen._post_economy_upgrade_company(self.id, level, self.holding.citizen.details.pin)
@property
def holding(self):
return self._holding()
class MyCompanies:
work_units: int = 0
@ -218,13 +229,13 @@ class MyCompanies:
ff_lockdown: int = 0
holdings: Dict[int, Holding]
companies: List[Company]
companies: weakref.WeakSet
_citizen: weakref.ReferenceType
def __init__(self, citizen):
from erepublik import Citizen
self.citizen: Citizen = citizen
self._citizen = weakref.ref(citizen)
self.holdings: Dict[int, Holding] = dict()
self.companies: List[Company] = list()
self.companies: weakref.WeakSet = weakref.WeakSet()
self.next_ot_time = utils.now()
def prepare_holdings(self, holdings: Dict[str, Dict[str, Any]]):
@ -233,8 +244,9 @@ class MyCompanies:
"""
for holding in holdings.values():
if holding.get('id') not in self.holdings:
self.holdings.update(
{int(holding.get('id')): Holding(holding['id'], holding['region_id'], self.citizen)})
self.holdings.update({
int(holding.get('id')): Holding(holding['id'], holding['region_id'], self.citizen)
})
if not self.holdings.get(0):
self.holdings.update({0: Holding(0, 0, self.citizen)}) # unassigned
@ -258,7 +270,7 @@ class MyCompanies:
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')
)
self.companies.append(company)
self.companies.add(company)
holding.add_company(company)
def get_employable_factories(self) -> Dict[int, int]:
@ -282,6 +294,8 @@ class MyCompanies:
def __clear_data(self):
for holding in self.holdings.values():
for company in holding.companies:
del company
holding.companies.clear()
self.companies.clear()
@ -290,6 +304,10 @@ class MyCompanies:
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))
@property
def citizen(self):
return self._citizen()
class Config:
email = ""
@ -528,7 +546,7 @@ class Reporter:
queue=self.__to_update)
def __init__(self, citizen):
self.citizen = citizen
self._citizen = weakref.ref(citizen)
self._req = Session()
self.url = "https://api.erep.lv"
self._req.headers.update({"user-agent": "Bot reporter v2"})
@ -541,6 +559,10 @@ class Reporter:
self.register_account()
self.allowed = True
@property
def citizen(self):
return self._citizen()
def __update_key(self):
self.key = hashlib.md5(bytes(f"{self.name}:{self.email}", encoding="UTF-8")).hexdigest()
@ -548,10 +570,10 @@ class Reporter:
if self.__to_update:
for unreported_data in self.__to_update:
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.__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)
return r
@ -645,12 +667,13 @@ class BattleSide:
deployed: List[constants.Country]
allies: List[constants.Country]
battle: "Battle"
_battle: weakref.ReferenceType
country: constants.Country
defender: bool
def __init__(self, battle: "Battle", country: constants.Country, points: int, allies: List[constants.Country],
deployed: List[constants.Country], defender: bool):
self.battle = battle
self._battle = weakref.ref(battle)
self.country = country
self.points = points
self.allies = allies
@ -677,6 +700,10 @@ class BattleSide:
return dict(points=self.points, country=self.country, defender=self.defender, allies=self.allies,
deployed=self.deployed, battle=repr(self.battle))
@property
def battle(self):
return self._battle()
class BattleDivision:
id: int
@ -689,6 +716,7 @@ class BattleDivision:
terrain: int
div: int
battle: "Battle"
_battle: weakref.ReferenceType
@property
def as_dict(self):
@ -715,7 +743,7 @@ class BattleDivision:
:type wall_for: int
:type wall_dom: float
"""
self.battle = battle
self._battle = weakref.ref(battle)
self.id = div_id
self.end = end
self.epic = epic
@ -738,6 +766,10 @@ class BattleDivision:
def __repr__(self):
return f"<BattleDivision #{self.id} (battle #{self.battle.id})>"
@property
def battle(self):
return self._battle()
class Battle:
id: int

View File

@ -49,16 +49,16 @@ class Country:
class Industries:
__by_name = {'food': 1, 'weapons': 2, 'house': 4, 'aircraft': 23,
'foodraw': 7, 'weaponraw': 12, 'weaponsraw': 12, 'houseraw': 18, 'aircraftraw': 24,
__by_name = {'food': 1, 'weapon': 2, 'house': 4, 'aircraft': 23,
'foodraw': 7, 'weaponraw': 12, 'houseraw': 18, 'aircraftraw': 24,
'frm': 7, 'wrm': 12, 'hrm': 18, 'arm': 24,
'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,
'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}
__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",
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",
24: "aircraftRaw", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5"}

View File

@ -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'])
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")))
if isinstance(player, Citizen):
files.append(('file', ("instance.json", player.to_json(indent=True), "application/json")))
@ -368,3 +368,11 @@ def get_air_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_pat
rang = r['military']['militaryData']['aircraft']['rankNumber']
elite = r['citizenAttributes']['level'] > 100
return calculate_hit(0, rang, true_patriot, elite, natural_enemy, booster, weapon_power)
def _clear_up_battle_memory(battle):
from . import classes
battle: classes.Battle
del battle.invader._battle, battle.defender._battle
for div_id, division in battle.div.items():
del division._battle

View File

@ -1,17 +1,18 @@
bump2version==1.0.0
coverage==5.2
coverage==5.2.1
edx-sphinx-theme==1.5.0
flake8==3.8.3
ipython==7.16.1
isort==5.0.9
pip==20.1.1
PyInstaller==3.6
ipython==7.17.0
isort==5.4.2
pip==20.2.2
PyInstaller==4.0
pytz==2020.1
pytest==5.4.3
responses==0.10.15
setuptools==49.2.0
Sphinx==3.1.2
tox==3.16.1
pytest==6.0.1
responses==0.10.16
setuptools==49.6.0
Sphinx==3.2.1
requests==2.24.0
tox==3.19.0
twine==3.2.0
watchdog==0.10.3
wheel==0.34.2
wheel==0.35.1

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.21.1
current_version = 0.21.3
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?

View File

@ -43,6 +43,6 @@ setup(
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/',
version='0.21.1',
version='0.21.3',
zip_safe=False,
)