Compare commits

...

14 Commits

7 changed files with 282 additions and 148 deletions

View File

@ -6,8 +6,8 @@ eRepublik script
.. image:: https://img.shields.io/pypi/v/erepublik.svg .. image:: https://img.shields.io/pypi/v/erepublik.svg
:target: https://pypi.python.org/pypi/erepublik :target: https://pypi.python.org/pypi/erepublik
.. image:: https://readthedocs.org/projects/erepublik/badge/?version=latest .. image:: https://readthedocs.org/projects/erepublik_script/badge/?version=latest
:target: https://erepublik.readthedocs.io/en/latest/?badge=latest :target: https://erepublik_script.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status :alt: Documentation Status

View File

@ -4,7 +4,7 @@
__author__ = """Eriks Karls""" __author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv' __email__ = 'eriks@72.lv'
__version__ = '__version__ = '0.15.0'' __version__ = '0.15.1'
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 datetime import datetime
import itertools
import re import re
import sys import sys
import threading import threading
import time import time
from json import loads, dumps from json import loads, dumps
from typing import Dict, List, Tuple, Any, Union from typing import Dict, List, Tuple, Any, Union, Mapping
import requests import requests
from requests import Response, RequestException from requests import Response, RequestException
@ -20,9 +21,9 @@ class Citizen(classes.CitizenAPI):
all_battles: Dict[int, classes.Battle] = dict() all_battles: Dict[int, classes.Battle] = dict()
countries: Dict[int, Dict[str, Union[str, List[int]]]] = dict() countries: Dict[int, Dict[str, Union[str, List[int]]]] = dict()
__last_war_update_data = {} __last_war_update_data = {}
__last_full_update: datetime.datetime __last_full_update: datetime.datetime = utils.now().min
active_fs = False active_fs: bool = False
food = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0} food = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
inventory = {"used": 0, "total": 0} inventory = {"used": 0, "total": 0}
@ -311,7 +312,10 @@ class Citizen(classes.CitizenAPI):
self.details.gold, self.details.cc self.details.gold, self.details.cc
)) ))
if send_mail: if send_mail:
active_promos = ["{} active until {}".format(k, v.strftime("%F %T")) for k, v in self.promos.items()] active_promos = []
for kind, time_until in self.promos.items():
active_promos.append(f"{kind} active until {time_until}")
utils.report_promo(kind, time_until)
utils.send_email(self.name, active_promos, player=self, promo=True) utils.send_email(self.name, active_promos, player=self, promo=True)
new_date = re.search(r"var new_date = '(\d*)';", html) new_date = re.search(r"var new_date = '(\d*)';", html)
@ -356,16 +360,17 @@ class Citizen(classes.CitizenAPI):
self.politics.is_party_president = bool(party.get('is_party_president')) self.politics.is_party_president = bool(party.get('is_party_president'))
self.politics.party_slug = "{}-{}".format(party.get("stripped_title"), party.get('party_id')) self.politics.party_slug = "{}-{}".format(party.get("stripped_title"), party.get('party_id'))
def update_money(self, page: int = 0, currency: int = 62) -> Response: def update_money(self, page: int = 0, currency: int = 62) -> Dict[str, Any]:
""" """
Gets monetary market offers to get exact amount of CC and Gold available Gets monetary market offers to get exact amount of CC and Gold available
""" """
if currency not in [1, 62]: if currency not in [1, 62]:
currency = 62 currency = 62
resp = self._post_economy_exchange_retrieve(False, page, currency) resp = self._post_economy_exchange_retrieve(False, page, currency)
self.details.cc = float(resp.json().get("ecash").get("value")) resp_data = resp.json()
self.details.gold = float(resp.json().get("gold").get("value")) self.details.cc = float(resp_data.get("ecash").get("value"))
return resp self.details.gold = float(resp_data.get("gold").get("value"))
return resp_data
def update_job_info(self): def update_job_info(self):
ot = self._get_job_data().json().get("overTime", {}) ot = self._get_job_data().json().get("overTime", {})
@ -433,7 +438,7 @@ class Citizen(classes.CitizenAPI):
if pps: if pps:
self.details.next_pp.append(int(pps.group(1))) self.details.next_pp.append(int(pps.group(1)))
def update_war_info(self) -> Dict[Any, Any]: def update_war_info(self):
if not self.details.current_country: if not self.details.current_country:
self.update_citizen_info() self.update_citizen_info()
@ -451,7 +456,6 @@ class Citizen(classes.CitizenAPI):
if resp_json.get("battles"): if resp_json.get("battles"):
for battle_id, battle_data in resp_json.get("battles", {}).items(): for battle_id, battle_data in resp_json.get("battles", {}).items():
self.all_battles.update({int(battle_id): classes.Battle(battle_data)}) self.all_battles.update({int(battle_id): classes.Battle(battle_data)})
return self.__last_war_update_data
def eat(self): def eat(self):
""" """
@ -538,8 +542,15 @@ class Citizen(classes.CitizenAPI):
def_allies = battle.defender.deployed + [battle.defender.id] def_allies = battle.defender.deployed + [battle.defender.id]
all_allies = inv_allies + def_allies all_allies = inv_allies + def_allies
if self.details.current_country not in all_allies: if self.details.current_country not in all_allies:
self._travel(battle.defender.id, self.get_country_travel_region(battle.defender.id)) if self.details.current_country in battle.invader.allies:
side = battle.defender.id allies = battle.invader.deployed
side = battle.invader.id
else:
allies = battle.defender.deployed
side = battle.defender.id
self.travel_to_battle(battle.id, allies)
else: else:
if self.details.current_country in inv_allies: if self.details.current_country in inv_allies:
side = battle.invader.id side = battle.invader.id
@ -567,7 +578,6 @@ class Citizen(classes.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 = False) -> List[int]:
r = self.update_war_info()
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] = []
@ -614,17 +624,19 @@ class Citizen(classes.CitizenAPI):
other_battles_air.append(battle.id) other_battles_air.append(battle.id)
ret_battles = [] ret_battles = []
if r.get("citizen_contribution"): if self.__last_war_update_data.get("citizen_contribution"):
battle_id = r.get("citizen_contribution")[0].get("battle_id", 0) battle_id = self.__last_war_update_data.get("citizen_contribution")[0].get("battle_id", 0)
ret_battles.append(battle_id) ret_battles.append(battle_id)
ret_battles += cs_battles_air + cs_battles_ground + deployed_battles_air + deployed_battles_ground + \ ret_battles += (cs_battles_air + cs_battles_ground +
ally_battles_air + ally_battles_ground + other_battles_air + other_battles_ground deployed_battles_air + deployed_battles_ground +
ally_battles_air + ally_battles_ground +
other_battles_air + other_battles_ground)
return ret_battles return ret_battles
@property @property
def has_battle_contribution(self): def has_battle_contribution(self):
return bool(self.update_war_info().get("citizen_contribution", [])) return bool(self.__last_war_update_data.get("citizen_contribution", []))
def find_battle_and_fight(self): def find_battle_and_fight(self):
if self.should_fight(False): if self.should_fight(False):
@ -672,14 +684,16 @@ class Citizen(classes.CitizenAPI):
if travel_needed: if travel_needed:
if battle.is_rw: if battle.is_rw:
self._travel(battle.defender.id, self.get_country_travel_region(battle.defender.id)) country_ids_to_travel = [battle.defender.id]
elif self.details.current_country not in battle.invader.allies: elif self.details.current_country in battle.invader.allies:
self.travel(battle_id=battle.id) country_ids_to_travel = battle.invader.deployed + [battle.invader.id]
side_id = battle.invader.id side_id = battle.invader.id
else: else:
self._travel(battle.defender.id, self.get_country_travel_region(battle.defender.id)) country_ids_to_travel = battle.defender.deployed + [battle.defender.id]
side_id = battle.defender.id side_id = battle.defender.id
if not self.travel_to_battle(battle_id, country_ids_to_travel):
break
self.fight(battle_id, side_id, battle.is_air) self.fight(battle_id, side_id, battle.is_air)
self.travel_to_residence() self.travel_to_residence()
self.collect_weekly_reward() self.collect_weekly_reward()
@ -745,6 +759,10 @@ class Citizen(classes.CitizenAPI):
return hits, err, damage return hits, err, damage
def deploy_bomb(self, battle_id: int, bomb_id: int):
r = self._post_military_deploy_bomb(battle_id, bomb_id).json()
return not r.get('error')
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
self.update_job_info() self.update_job_info()
@ -900,7 +918,8 @@ class Citizen(classes.CitizenAPI):
if wam_list: if wam_list:
wam_holding = self.my_companies.holdings.get(wam_holding_id) wam_holding = self.my_companies.holdings.get(wam_holding_id)
if not self.details.current_region == wam_holding['region_id']: if not self.details.current_region == wam_holding['region_id']:
self.travel(holding_id=wam_holding_id, region_id=wam_holding['region_id']) if not self.travel_to_region(wam_holding['region_id']):
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) self.reporter.report_action("WORK_WAM_EMPLOYEES", response)
if response.get("status"): if response.get("status"):
@ -927,7 +946,7 @@ class Citizen(classes.CitizenAPI):
amount = amount_remaining amount = amount_remaining
best_offer = self.get_market_offers(self.details.citizenship, industry, 1) best_offer = self.get_market_offers(self.details.citizenship, industry, 1)
amount = best_offer['amount'] if amount >= best_offer['amount'] else amount amount = best_offer['amount'] if amount >= best_offer['amount'] else amount
rj = self.buy_from_market(amount=best_offer['amount'], offer=best_offer['offer_id']).json() rj = self.buy_from_market(amount=best_offer['amount'], offer=best_offer['offer_id'])
if not rj.get('error'): if not rj.get('error'):
amount_remaining -= amount amount_remaining -= amount
else: else:
@ -963,15 +982,8 @@ class Citizen(classes.CitizenAPI):
self.post_market_offer(industry=self.available_industries[kind], amount=int(amount), self.post_market_offer(industry=self.available_industries[kind], amount=int(amount),
quality=int(quality), price=price) quality=int(quality), price=price)
def travel_to_residence(self):
self.update_citizen_info()
res_r = self.details.residence_region
if self.details.residence_country and res_r and not res_r == self.details.current_region:
self._travel(self.details.residence_country, self.details.residence_region)
def get_country_travel_region(self, country_id: int) -> int: def get_country_travel_region(self, country_id: int) -> int:
r = self.get_travel_regions(country_id=country_id).json() regions = self.get_travel_regions(country_id=country_id)
regions = r.get("regions")
regs = [] regs = []
if regions: if regions:
for region in regions.values(): for region in regions.values():
@ -982,50 +994,112 @@ class Citizen(classes.CitizenAPI):
else: else:
return 0 return 0
def travel(self, holding_id=0, battle_id=0, region_id=0): def _update_citizen_location(self, country_id: int, region_id: int):
r = self.get_travel_regions(holding_id, battle_id, region_id) self.details.current_region = region_id
regions = r.json()["regions"] self.details.current_country = country_id
closest_region = 99999
country_id = int(r.json()["preselectCountryId"]) def travel_to_residence(self) -> bool:
region_id = int(r.json()["preselectRegionId"]) self.update_citizen_info()
if not any((region_id, country_id)): res_r = self.details.residence_region
for rid, info in regions.items(): if self.details.residence_country and res_r and not res_r == self.details.current_region:
if info.get("distanceInKm", 99999) < closest_region: r = self._travel(self.details.residence_country, self.details.residence_region)
closest_region = info.get("distanceInKm") if r.json().get('message', '') == 'success':
country_id = info.get("countryId") self._update_citizen_location(self.details.residence_country, self.details.current_region)
region_id = rid return True
self._travel(country_id, region_id) return False
return True
def travel_to_region(self, region_id: int) -> bool:
data = self._post_travel_data(region_id=region_id).json()
if data.get('alreadyInRegion'):
return True
else:
r = self._travel(data.get('preselectCountryId'), region_id).json()
if r.get('message', '') == 'success':
self._update_citizen_location(data.get('preselectCountryId'), region_id)
return True
return False
def travel_to_country(self, country_id: int) -> bool:
data = self._post_travel_data(countryId=country_id, check="getCountryRegions").json()
regs = []
if data.get('regions'):
for region in data.get('regions').values():
if region['countryId'] == country_id: # Is not occupied by other country
regs.append((region['id'], region['distanceInKm']))
if regs:
region_id = min(regs, key=lambda _: int(_[1]))[0]
r = self._travel(country_id, region_id).json()
if r.get('message', '') == 'success':
self._update_citizen_location(country_id, region_id)
return True
return False
def travel_to_holding(self, holding_id: int) -> bool:
data = self._post_travel_data(holdingId=holding_id).json()
if data.get('alreadyInRegion'):
return True
else:
r = self._travel(data.get('preselectCountryId'), data.get('preselectRegionId')).json()
if r.get('message', '') == 'success':
self._update_citizen_location(data.get('preselectCountryId'), data.get('preselectRegionId'))
return True
return False
def travel_to_battle(self, battle_id: int, *allowed_countries: List[int]) -> bool:
data = self.get_travel_regions(battle_id=battle_id)
regs = []
if data:
for region in data.values():
if region['countryId'] in allowed_countries: # Is not occupied by other country
regs.append((region['distanceInKm'], region['id'], region['countryId']))
if regs:
reg = min(regs, key=lambda _: int(_[0]))
region_id = reg[1]
country_id = reg[2]
r = self._travel(country_id, region_id).json()
if r.get('message', '') == 'success':
self._update_citizen_location(country_id, region_id)
return True
return False
def _travel(self, country_id: int, region_id: int = 0) -> Response: def _travel(self, country_id: int, region_id: int = 0) -> Response:
data = { data = {
"toCountryId": country_id, "toCountryId": country_id,
"inRegionId": region_id, "inRegionId": region_id,
"battleId": 0,
} }
return self._post_travel("moveAction", **data) return self._post_travel("moveAction", **data)
def get_travel_regions(self, holding_id: int = 0, battle_id: int = 0, region_id: int = 0, def get_travel_regions(self, holding_id: int = 0, battle_id: int = 0, country_id: int = 0
country_id: int = 0) -> Response: ) -> Union[List[Any], Dict[str, Dict[str, Any]]]:
data = { d = self._post_travel_data(holdingId=holding_id, battleId=battle_id, countryId=country_id).json()
"holdingId": holding_id, return d.get('regions', [])
"battleId": battle_id,
"regionId": region_id, def get_travel_countries(self) -> List[int]:
} response_json = self._post_travel_data().json()
data.update(countryId=country_id) return_list = []
return self._post_travel_data(**data) for country_data in response_json['countries'].values():
if country_data['currentRegions']:
return_list.append(country_data['id'])
return return_list
def parse_notifications(self, page: int = 1) -> list: def parse_notifications(self, page: int = 1) -> list:
response = self.get_message_alerts(page) community = self._get_notifications_ajax_community(page).json()
notifications = re.findall(r"<p class=\"smallpadded\">(.*?)</p>", response.text, re.M | re.I | re.S) system = self._get_notifications_ajax_system(page).json()
return notifications return community['alertsList'] + system['alertsList']
def delete_notifications(self): def delete_notifications(self):
def notification_ids(html): response = self._get_notifications_ajax_community().json()
return re.findall(r"id=\"delete_alert_(\d+)\"", html) while response['totalAlerts']:
self._post_messages_alert([_['id'] for _ in response['alertList']])
response = self._get_notifications_ajax_community().json()
response = self.get_message_alerts() response = self._get_notifications_ajax_system().json()
while notification_ids(response.text): while response['totalAlerts']:
response = self._post_messages_alert(notification_ids(response.text)) self._post_messages_alert([_['id'] for _ in response['alertList']])
response = self._get_notifications_ajax_system().json()
def collect_weekly_reward(self): def collect_weekly_reward(self):
self.update_weekly_challenge() self.update_weekly_challenge()
@ -1044,12 +1118,18 @@ class Citizen(classes.CitizenAPI):
self._post_delete_message([msg_id]) self._post_delete_message([msg_id])
def get_market_offers(self, country_id: int = None, product: str = None, quality: int = None) -> dict: def get_market_offers(self, country_id: int = None, product: str = None, quality: int = None) -> dict:
ret = dict()
raw_short_names = dict(frm="foodRaw", wrm="weaponRaw", hrm="houseRaw", arm="airplaneRaw") raw_short_names = dict(frm="foodRaw", wrm="weaponRaw", hrm="houseRaw", arm="airplaneRaw")
q1_industries = ["aircraft"] + list(raw_short_names.values()) q1_industries = ["aircraft"] + list(raw_short_names.values())
if product in raw_short_names: if product:
quality = 1 if product not in self.available_industries and product not in raw_short_names:
product = raw_short_names.get(product) self.write_log("Industry '{}' not implemented".format(product))
raise classes.ErepublikException("Industry '{}' not implemented".format(product))
elif product in raw_short_names:
quality = 1
product = raw_short_names.get(product)
product = [product]
elif quality:
raise classes.ErepublikException("Quality without product not allowed")
item_data = dict(price=999999., country=0, amount=0, offer_id=0, citizen_id=0) item_data = dict(price=999999., country=0, amount=0, offer_id=0, citizen_id=0)
@ -1062,48 +1142,43 @@ class Citizen(classes.CitizenAPI):
"foodRaw": dict(q1=item_data.copy()), "weaponRaw": dict(q1=item_data.copy()), "foodRaw": dict(q1=item_data.copy()), "weaponRaw": dict(q1=item_data.copy()),
"houseRaw": dict(q1=item_data.copy()), "airplaneRaw": dict(q1=item_data.copy())} "houseRaw": dict(q1=item_data.copy()), "airplaneRaw": dict(q1=item_data.copy())}
countries = [country_id] if country_id else self.countries if country_id:
if product not in self.available_industries: countries = [country_id]
self.write_log("Industry '{}' not implemented".format(product)) else:
return ret good_countries = self.get_travel_countries()
countries = {cid for cid in self.countries.keys() if cid in good_countries}
start_dt = self.now start_dt = self.now
for country in countries: iterable = [countries, product or items, [quality] if quality else range(1, 8)]
if not country_id and not self.get_country_travel_region(country): for country, industry, q in itertools.product(*iterable):
if (q > 1 and industry in q1_industries) or (q > 5 and industry == "house"):
continue continue
for industry in [product] or items:
for q in [quality] if quality else range(1, 8):
if (q > 1 and industry in q1_industries) or (q > 5 and industry == "house"):
break
str_q = "q%i" % q str_q = "q%i" % q
data = {'country': country, 'industry': self.available_industries[industry], 'quality': q} r = self._post_economy_marketplace(country, self.available_industries[industry], q).json()
r = self._post_economy_marketplace(**data) obj = items[industry][str_q]
rjson = r.json() if not r.get("error", False):
obj = items[industry][str_q] for offer in r["offers"]:
if not rjson.get("error", False): if obj["price"] > float(offer["priceWithTaxes"]):
for offer in rjson["offers"]: obj["price"] = float(offer["priceWithTaxes"])
if obj["price"] > float(offer["priceWithTaxes"]): obj["country"] = int(offer["country_id"])
obj["price"] = float(offer["priceWithTaxes"]) obj["amount"] = int(offer["amount"])
obj["country"] = int(offer["country_id"]) obj["offer_id"] = int(offer["id"])
obj["amount"] = int(offer["amount"]) obj["citizen_id"] = int(offer["citizen_id"])
obj["offer_id"] = int(offer["id"]) elif obj["price"] == float(offer["priceWithTaxes"]) and obj["amount"] < int(offer["amount"]):
obj["citizen_id"] = int(offer["citizen_id"]) obj["country"] = int(offer["country_id"])
elif obj["price"] == float(offer["priceWithTaxes"]) \ obj["amount"] = int(offer["amount"])
and obj["amount"] < int(offer["amount"]): obj["offer_id"] = int(offer["id"])
obj["country"] = int(offer["country_id"])
obj["amount"] = int(offer["amount"])
obj["offer_id"] = int(offer["id"])
self.write_log("Scraped market in {}!".format(self.now - start_dt)) self.write_log("Scraped market in {}!".format(self.now - start_dt))
if quality: if quality:
ret = items[product]["q%i" % quality] ret = items[product[0]]["q%i" % quality]
elif product: elif product:
if product in raw_short_names.values(): if product[0] in raw_short_names.values():
ret = items[product]["q1"] ret = items[product[0]]["q1"]
else: else:
ret = items[product] ret = items[product[0]]
else: else:
ret = items ret = items
return ret return ret
@ -1134,7 +1209,7 @@ class Citizen(classes.CitizenAPI):
def get_monetary_offers(self, currency: int = 62) -> List[Dict[str, Union[int, float]]]: def get_monetary_offers(self, currency: int = 62) -> List[Dict[str, Union[int, float]]]:
if currency not in [1, 62]: if currency not in [1, 62]:
currency = 62 currency = 62
resp = self.update_money(0, currency).json() resp = self._post_economy_exchange_retrieve(False, 0, currency).json()
ret = [] ret = []
offers = re.findall(r"id='purchase_(\d+)' data-i18n='Buy for' data-currency='GOLD' " offers = re.findall(r"id='purchase_(\d+)' data-i18n='Buy for' data-currency='GOLD' "
r"data-price='(\d+\.\d+)' data-max='(\d+\.\d+)' trigger='purchase'", r"data-price='(\d+\.\d+)' data-max='(\d+\.\d+)' trigger='purchase'",
@ -1153,7 +1228,7 @@ class Citizen(classes.CitizenAPI):
value="New amount {o.cc}cc, {o.gold}g".format(o=self.details)) value="New amount {o.cc}cc, {o.gold}g".format(o=self.details))
return not response.json().get("error", False) return not response.json().get("error", False)
def activate_dmg_booster(self, battle_id: int) -> None: def activate_dmg_booster(self, battle_id: int):
if self.config.boosters: if self.config.boosters:
duration = 0 duration = 0
if self.boosters.get("100_damageBoosters_5_600", 0) > 0: if self.boosters.get("100_damageBoosters_5_600", 0) > 0:
@ -1196,7 +1271,7 @@ class Citizen(classes.CitizenAPI):
self.sleep(5) self.sleep(5)
def reject_money_donations(self) -> int: def reject_money_donations(self) -> int:
r = self.get_message_alerts() r = self._get_notifications_ajax_system()
count = 0 count = 0
donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text) donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text)
while donation_ids: while donation_ids:
@ -1204,7 +1279,7 @@ class Citizen(classes.CitizenAPI):
self._get_money_donation_reject(int(don_id)) self._get_money_donation_reject(int(don_id))
count += 1 count += 1
self.sleep(5) self.sleep(5)
r = self.get_message_alerts() r = self._get_notifications_ajax_system()
donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text) donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text)
return count return count
@ -1416,18 +1491,18 @@ class Citizen(classes.CitizenAPI):
self.reporter.report_action("SELL_PRODUCT", ret.json()) self.reporter.report_action("SELL_PRODUCT", ret.json())
return ret return ret
def buy_from_market(self, offer: int, amount: int) -> Response: def buy_from_market(self, offer: int, amount: int) -> dict:
ret = self._post_economy_marketplace_actions(amount, True, offer=offer) ret = self._post_economy_marketplace_actions(amount, True, offer=offer)
json_ret = ret.json() json_ret = ret.json()
if json_ret.get('error'): if json_ret.get('error'):
return ret return json_ret
else: else:
self.details.cc = ret.json()['currency'] self.details.cc = ret.json()['currency']
self.details.gold = ret.json()['gold'] self.details.gold = ret.json()['gold']
r_json = ret.json() r_json = ret.json()
r_json.pop("offerUpdate", None) r_json.pop("offerUpdate", None)
self.reporter.report_action("BUY_PRODUCT", ret.json()) self.reporter.report_action("BUY_PRODUCT", ret.json())
return ret return json_ret
def get_raw_surplus(self) -> (float, float): def get_raw_surplus(self) -> (float, float):
frm = 0.00 frm = 0.00
@ -1560,8 +1635,8 @@ class Citizen(classes.CitizenAPI):
ret = {} ret = {}
r = self._get_military_unit_data(mu_id) r = self._get_military_unit_data(mu_id)
for page in range(1, int(r.json()["panelContents"]["pages"]) + 1): for page in range(int(r.json()["panelContents"]["pages"])):
r = self._get_military_unit_data(mu_id, page) r = self._get_military_unit_data(mu_id, currentPage=page + 1)
for user in r.json()["panelContents"]["members"]: for user in r.json()["panelContents"]["members"]:
if not user["isDead"]: if not user["isDead"]:
ret.update({user["citizenId"]: user["name"]}) ret.update({user["citizenId"]: user["name"]})
@ -1594,18 +1669,45 @@ class Citizen(classes.CitizenAPI):
for resident in resp["widgets"]["residents"]["residents"]: for resident in resp["widgets"]["residents"]["residents"]:
self.add_friend(resident["citizenId"]) self.add_friend(resident["citizenId"])
def schedule_attack(self, war_id: int, region_id: int, at_time: datetime) -> None: def schedule_attack(self, war_id: int, region_id: int, region_name: str, at_time: datetime) -> None:
if at_time: if at_time:
self.sleep(utils.get_sleep_seconds(at_time)) self.sleep(utils.get_sleep_seconds(at_time))
self.get_csrf_token() self.get_csrf_token()
self._launch_battle(war_id, region_id) self.launch_attack(war_id, region_id, region_name)
def get_active_wars_with_regions(self): def get_active_wars(self, country_id: int = None) -> List[int]:
self._get_country_military(self.countries.get(self.details.citizen_id)["name"]) r = self._get_country_military(utils.COUNTRY_LINK.get(country_id or self.details.citizenship))
raise NotImplementedError all_war_ids = re.findall(r'//www\.erepublik\.com/en/wars/show/(\d+)"', r.text)
return [int(wid) for wid in all_war_ids]
def _launch_battle(self, war_id: int, region_id: int) -> Response: def get_war_status(self, war_id: int) -> Dict[str, Union[bool, Dict[int, str]]]:
return self._post_wars_attack_region(war_id, region_id) r = self._get_wars_show(war_id)
html = r.text
ret = {}
reg_re = re.compile(fr'data-war-id="{war_id}" data-region-id="(\d+)" data-region-name="([- \w]+)"')
if reg_re.findall(html):
ret.update(regions={}, can_attack=True)
for reg in reg_re.findall(html):
ret["regions"].update({str(reg[0]): reg[1]})
elif re.search(r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" '
r'class="join" title="Join"><span>Join</span></a>', html):
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)
ret.update(can_attack=False, battle_id=battle_id)
else:
ret.update(can_attack=False)
return ret
def get_last_battle_of_war_end_time(self, war_id: int) -> datetime:
r = self._get_wars_show(war_id)
html = r.text
last_battle_id = int(re.search(r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)">', html).group(1))
console = self._post_military_battle_console(last_battle_id, 'warList', 1).json()
battle = console.get('list')[0]
return utils.localize_dt(datetime.datetime.strptime(battle.get('result').get('end'), "%Y-%m-%d %H:%M:%S"))
def launch_attack(self, war_id: int, region_id: int, region_name: str):
self._post_wars_attack_region(war_id, region_id, region_name)
def state_update_repeater(self): def state_update_repeater(self):
try: try:
@ -1691,8 +1793,8 @@ class Citizen(classes.CitizenAPI):
buy = self.buy_from_market(global_cheapest['offer_id'], 1) buy = self.buy_from_market(global_cheapest['offer_id'], 1)
else: else:
buy = self.buy_from_market(cheapest_offer['offer_id'], 1) buy = self.buy_from_market(cheapest_offer['offer_id'], 1)
if buy.json()["error"]: if buy["error"]:
msg = "Unable to buy q{} house! \n{}".format(q, buy.json()['message']) msg = "Unable to buy q{} house! \n{}".format(q, buy['message'])
self.write_log(msg) self.write_log(msg)
else: else:
ok_to_activate = True ok_to_activate = True
@ -1766,12 +1868,10 @@ class Citizen(classes.CitizenAPI):
def write_on_country_wall(self, message: str) -> bool: def write_on_country_wall(self, message: str) -> bool:
self._get_main() self._get_main()
post_to_wall_as = re.findall(r"""id="post_to_country_as".*?<option value="(.*?)">.*?</option>.*</select>""", post_to_wall_as = re.findall(r"""id="post_to_country_as".*?<option value="(\d?)">.*?</option>.*</select>""",
self.r.text, re.S | re.M) self.r.text, re.S | re.M)
if post_to_wall_as: r = self._post_country_post_create(message, max(post_to_wall_as, key=int) if post_to_wall_as else 0)
self._post_country_post_create(message, max(post_to_wall_as)) return r.json()
return True
return False
def report_error(self, msg: str = ""): def report_error(self, msg: str = ""):
utils.process_error(msg, self.name, sys.exc_info(), self, self.commit_id, False) utils.process_error(msg, self.name, sys.exc_info(), self, self.commit_id, False)

View File

@ -5,7 +5,7 @@ import random
import time import time
from collections import deque from collections import deque
from json import JSONDecodeError, loads, JSONEncoder from json import JSONDecodeError, loads, JSONEncoder
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union, Mapping, Iterable
from requests import Response, Session from requests import Response, Session
@ -41,7 +41,7 @@ class MyCompanies:
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():
tmp: Dict[str, Union[List[Any], Any]] = {} tmp: Dict[str, Union[Iterable[Any], Any]] = {}
for key in template: for key in template:
if key == 'companies': if key == 'companies':
tmp.update({key: []}) tmp.update({key: []})
@ -470,7 +470,7 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_citizen_daily_assistant(self) -> Response: def _get_citizen_daily_assistant(self) -> Response:
return self.get("{}/main/citizenDailyAssistant".format(self.url)) return self.get("{}/main/citizenDailyAssistant".format(self.url))
def _get_city_data_residents(self, city: int, page: int = 1, params: Dict[str, Any] = None) -> Response: def _get_city_data_residents(self, city: int, page: int = 1, params: Mapping[str, Any] = None) -> Response:
if params is None: if params is None:
params = {} params = {}
return self.get("{}/main/city-data/{}/residents".format(self.url, city), params={"currentPage": page, **params}) return self.get("{}/main/city-data/{}/residents".format(self.url, city), params={"currentPage": page, **params})
@ -549,6 +549,9 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_weekly_challenge_data(self) -> Response: def _get_weekly_challenge_data(self) -> Response:
return self.get("{}/main/weekly-challenge-data".format(self.url)) return self.get("{}/main/weekly-challenge-data".format(self.url))
def _get_wars_show(self, war_id: int) -> Response:
return self.get("{}/wars/show/{}".format(self.url, war_id))
def _post_activate_battle_effect(self, battle: int, kind: str, citizen_id: int) -> Response: def _post_activate_battle_effect(self, battle: int, kind: str, citizen_id: int) -> Response:
data = dict(battleId=battle, citizenId=citizen_id, type=kind, _token=self.token) data = dict(battleId=battle, citizenId=citizen_id, type=kind, _token=self.token)
return self.post("{}/main/fight-activateBattleEffect".format(self.url), data=data) return self.post("{}/main/fight-activateBattleEffect".format(self.url), data=data)
@ -737,7 +740,7 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
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)
def _post_messages_alert(self, notification_ids: list) -> Response: def _post_messages_alert(self, notification_ids: List[int]) -> Response:
data = {"_token": self.token, "delete_alerts[]": notification_ids, "deleteAllAlerts": "1", "delete": "Delete"} data = {"_token": self.token, "delete_alerts[]": notification_ids, "deleteAllAlerts": "1", "delete": "Delete"}
return self.post("{}/main/messages-alerts/1".format(self.url), data=data) return self.post("{}/main/messages-alerts/1".format(self.url), data=data)
@ -747,10 +750,18 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
citizen_subject=subject, _token=self.token, citizen_message=body) citizen_subject=subject, _token=self.token, citizen_message=body)
return self.post("{}/main/messages-compose/{}}".format(self.url, url_pk), data=data) return self.post("{}/main/messages-compose/{}}".format(self.url, url_pk), data=data)
def _post_military_battle_console(self, battle_id: int, round_id: int, division: int) -> Response: def _post_military_battle_console(self, battle_id: int, action: str, page: int = 1, **kwargs) -> Response:
data = dict(battleId=battle_id, zoneId=round_id, action="battleStatistics", round=round_id, division=division, data = dict(battleId=battle_id, action=action, _token=self.token)
type="damage", leftPage=1, rightPage=1, _token=self.token) if action == "battleStatistics":
return self.post("{}/military/battle-console".format(self.url, battle_id), data=data) data.update(round=kwargs["round_id"], zoneId=kwargs["round_id"], leftPage=page, rightPage=page,
division=kwargs["division"], type=kwargs.get("type", 'damage'),)
elif action == "warList":
data.update(page=page)
return self.post("{}/military/battle-console".format(self.url), data=data)
def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response:
data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token)
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) -> Response:
data = dict(sideId=side_id, battleId=battle_id, _token=self.token) data = dict(sideId=side_id, battleId=battle_id, _token=self.token)
@ -771,9 +782,9 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _post_travel_data(self, **kwargs) -> Response: def _post_travel_data(self, **kwargs) -> Response:
return self.post("{}/main/travelData".format(self.url), data=dict(_token=self.token, **kwargs)) return self.post("{}/main/travelData".format(self.url), data=dict(_token=self.token, **kwargs))
def _post_wars_attack_region(self, war: int, region: int) -> Response: def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response:
data = dict(_token=self.token) data = {'_token': self.token, 'warId': war_id, 'regionName': region_name, 'regionNameConfirm': region_name}
return self.post("{}/wars/attack-region/{}/{}".format(self.url, war, region), data=data) return self.post('{}/wars/attack-region/{}/{}'.format(self.url, war_id, region_id), data=data)
def _post_weekly_challenge_reward(self, reward_id: int) -> Response: def _post_weekly_challenge_reward(self, reward_id: int) -> Response:
data = dict(_token=self.token, rewardId=reward_id) data = dict(_token=self.token, rewardId=reward_id)

View File

@ -11,13 +11,12 @@ from collections import deque
from decimal import Decimal from decimal import Decimal
from json import JSONEncoder from json import JSONEncoder
from pathlib import Path from pathlib import Path
from typing import Union from typing import Union, Any, List, NoReturn, Mapping
import pytz import pytz
import requests import requests
from requests import Response from requests import Response
__all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", __all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz",
"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",
@ -85,6 +84,19 @@ COUNTRIES = {1: 'Romania', 9: 'Brazil', 10: 'Italy', 11: 'France', 12: 'Germany'
82: 'Cyprus', 83: 'Belarus', 84: 'New Zealand', 164: 'Saudi Arabia', 165: 'Egypt', 82: 'Cyprus', 83: 'Belarus', 84: 'New Zealand', 164: 'Saudi Arabia', 165: 'Egypt',
166: 'United Arab Emirates', 167: 'Albania', 168: 'Georgia', 169: 'Armenia', 170: 'Nigeria', 171: 'Cuba'} 166: 'United Arab Emirates', 167: 'Albania', 168: 'Georgia', 169: 'Armenia', 170: 'Nigeria', 171: 'Cuba'}
COUNTRY_LINK = {1: 'Romania', 9: 'Brazil', 11: 'France', 12: 'Germany', 13: 'Hungary', 82: 'Cyprus', 168: 'Georgia',
15: 'Spain', 23: 'Canada', 26: 'Mexico', 27: 'Argentina', 28: 'Venezuela', 80: 'Montenegro', 24: 'USA',
29: 'United-Kingdom', 50: 'Australia', 47: 'South-Korea',171: 'Cuba', 79: 'Republic-of-Macedonia-FYROM',
30: 'Switzerland', 31: 'Netherlands', 32: 'Belgium', 33: 'Austria', 34: 'Czech-Republic', 35: 'Poland',
36: 'Slovakia', 37: 'Norway', 38: 'Sweden', 39: 'Finland', 40: 'Ukraine', 41: 'Russia', 42: 'Bulgaria',
43: 'Turkey', 44: 'Greece', 45: 'Japan', 48: 'India', 49: 'Indonesia', 78: 'Colombia', 68: 'Singapore',
51: 'South Africa', 52: 'Republic-of-Moldova', 53: 'Portugal', 54: 'Ireland', 55: 'Denmark', 56: 'Iran',
57: 'Pakistan', 58: 'Israel', 59: 'Thailand', 61: 'Slovenia', 63: 'Croatia', 64: 'Chile', 65: 'Serbia',
66: 'Malaysia', 67: 'Philippines', 70: 'Estonia', 165: 'Egypt', 14: 'China', 77: 'Peru', 10: 'Italy',
71: 'Latvia', 72: 'Lithuania', 73: 'North-Korea', 74: 'Uruguay', 75: 'Paraguay', 76: 'Bolivia',
81: 'Republic-of-China-Taiwan', 166: 'United-Arab-Emirates', 167: 'Albania', 69: 'Bosnia-Herzegovina',
169: 'Armenia', 83: 'Belarus', 84: 'New-Zealand', 164: 'Saudi-Arabia', 170: 'Nigeria', }
class MyJSONEncoder(JSONEncoder): class MyJSONEncoder(JSONEncoder):
def default(self, o): def default(self, o):
@ -107,18 +119,22 @@ class MyJSONEncoder(JSONEncoder):
return super().default(o) return super().default(o)
def now(): def now() -> datetime.datetime:
return datetime.datetime.now(erep_tz).replace(microsecond=0) return datetime.datetime.now(erep_tz).replace(microsecond=0)
def localize_timestamp(timestamp: int): def localize_timestamp(timestamp: int) -> datetime.datetime:
return datetime.datetime.fromtimestamp(timestamp, erep_tz) return datetime.datetime.fromtimestamp(timestamp, erep_tz)
def localize_dt(dt: Union[datetime.date, datetime.datetime]): def localize_dt(dt: Union[datetime.date, datetime.datetime]) -> datetime.datetime:
if isinstance(dt, datetime.date): try:
dt = datetime.datetime.combine(dt, datetime.time(0, 0, 0)) try:
return erep_tz.localize(dt) return erep_tz.localize(dt)
except AttributeError:
return erep_tz.localize(datetime.datetime.combine(dt, datetime.time(0, 0, 0)))
except ValueError:
return dt.astimezone(erep_tz)
def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.datetime: def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.datetime:
@ -237,7 +253,10 @@ def write_request(response: requests.Response, is_error: bool = False):
"mimetype": "application/json" if ext == "json" else "text/html"} "mimetype": "application/json" if ext == "json" else "text/html"}
def send_email(name: str, content: list, player=None, local_vars=dict, promo: bool = False, captcha: bool = False): def send_email(name: str, content: List[Any], player=None, local_vars: Mapping[Any, Any] = None,
promo: bool = False, captcha: bool = False):
if local_vars is None:
local_vars = {}
from erepublik import Citizen from erepublik import Citizen
file_content_template = "<html><head><title>{title}</title></head><body>{body}</body></html>" file_content_template = "<html><head><title>{title}</title></head><body>{body}</body></html>"
@ -319,7 +338,11 @@ def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commi
send_email(name, bugtrace, citizen, local_vars=trace) send_email(name, bugtrace, citizen, local_vars=trace)
def slugify(value, allow_unicode=False): def report_promo(kind: str, time_untill: datetime.datetime) -> NoReturn:
requests.post('https://api.erep.lv/promos/add/', data=dict(kind=kind, time_untill=time_untill))
def slugify(value, allow_unicode=False) -> str:
""" """
Function copied from Django2.2.1 django.utils.text.slugify Function copied from Django2.2.1 django.utils.text.slugify
Convert to ASCII if 'allow_unicode' is False. Convert spaces to hyphens. Convert to ASCII if 'allow_unicode' is False. Convert spaces to hyphens.

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.15.0 current_version = 0.15.1
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_script', url='https://github.com/eeriks/erepublik_script',
version='0.15.0', version='0.15.1',
zip_safe=False, zip_safe=False,
) )