diff --git a/erepublik/citizen.py b/erepublik/citizen.py
index 4587f31..2c4a4ab 100644
--- a/erepublik/citizen.py
+++ b/erepublik/citizen.py
@@ -8,128 +8,35 @@ from typing import Any, Dict, List, Optional, Set, Tuple, Union
from requests import RequestException, Response
+from erepublik import utils
from erepublik.classes import (Battle, BattleDivision, CitizenAPI, Config, Details, Energy, ErepublikException,
MyCompanies, MyJSONEncoder, Politics, Reporter, TelegramBot)
-from erepublik.utils import *
-
-try:
- import simplejson as json
-except ImportError:
- import json
-class Citizen(CitizenAPI):
- division = 0
-
- all_battles: Dict[int, Battle] = None
- countries: Dict[int, Dict[str, Union[str, List[int]]]] = None
- __last_war_update_data = None
- __last_full_update: datetime = now().min
-
- active_fs: bool = False
+class BaseCitizen(CitizenAPI):
+ __last_full_update: datetime = utils.now().min
+ promos: Dict[str, datetime] = None
food: Dict[str, int] = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
- inventory: Dict[str, int] = {"used": 0, "total": 0}
- boosters: Dict[int, Dict[int, int]] = {100: {}, 50: {}}
-
eb_normal = 0
eb_double = 0
eb_small = 0
-
- work_units = 0
- ot_points = 0
-
- tg_contract = None
- promos: Dict[str, datetime] = None
+ division = 0
eday = 0
+ config: Config = None
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!"
- debug = False
- __registered = False
- logged_in = False
-
- def __init__(self, email: str = "", password: str = "", auto_login: bool = True):
- super().__init__()
- self.commit_id = COMMIT_ID
- self.config = Config()
- self.config.email = email
- self.config.password = password
- self.energy = Energy()
- self.details = Details()
- self.politics = Politics()
- self.my_companies = MyCompanies()
- self.set_debug(True)
- self.reporter = Reporter()
- self.stop_threads = Event()
- self.telegram = TelegramBot(stop_event=self.stop_threads)
- if auto_login:
- self.login()
-
- def config_setup(self, **kwargs):
- self.config.reset()
- for key, value in kwargs.items():
- if hasattr(self.config, key):
- setattr(self.config, key, value)
- else:
- self.write_log(f"Unknown config parameter! ({key}={value})")
-
- def login(self):
- self.get_csrf_token()
-
- self.update_citizen_info()
- self.reporter.do_init(self.name, self.config.email, self.details.citizen_id)
- if self.config.telegram:
- self.telegram.do_init(self.config.telegram_chat_id or 620981703,
- self.config.telegram_token or "864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o",
- "" if self.config.telegram_chat_id or self.config.telegram_token else self.name)
- self.telegram.send_message(f"*Started* {now():%F %T}")
-
- self.__last_full_update = good_timedelta(self.now, - timedelta(minutes=5))
-
- def write_log(self, *args, **kwargs):
- if self.config.interactive:
- write_interactive_log(*args, **kwargs)
- else:
- write_silent_log(*args, **kwargs)
-
- def sleep(self, seconds: int):
- if seconds < 0:
- seconds = 0
- if self.config.interactive:
- interactive_sleep(seconds)
- else:
- sleep(seconds)
-
- def __str__(self) -> str:
- return f"Citizen {self.name}"
-
- def __repr__(self):
- return self.__str__()
-
- @property
- def __dict__(self):
- ret = super().__dict__.copy()
- ret.pop('stop_threads', None)
- ret.pop('_Citizen__last_war_update_data', None)
-
- return ret
-
- def set_debug(self, debug: bool):
- self.debug = debug
- self._req.debug = debug
-
- def set_pin(self, pin: int):
- self.details.pin = pin
+ name: str = "Not logged in!"
+ logged_in: bool = False
+ commit_id: str = ""
def get_csrf_token(self):
"""
@@ -144,7 +51,7 @@ class Citizen(CitizenAPI):
return
html = resp.text
- self.check_for_medals(html)
+ self._check_response_for_medals(html)
re_token = re.search(r'var csrfToken = \'(\w{32})\'', html)
re_login_token = re.search(r'', html)
if re_token:
@@ -156,7 +63,7 @@ class Citizen(CitizenAPI):
raise ErepublikException("Something went wrong! Can't find token in page! Exiting!")
try:
self.update_citizen_info(resp.text)
- except (AttributeError, json.JSONDecodeError, ValueError, KeyError):
+ except (AttributeError, utils.json.JSONDecodeError, ValueError, KeyError):
pass
def _login(self):
@@ -183,7 +90,7 @@ class Citizen(CitizenAPI):
if j['error'] and j["message"] == 'Too many requests':
self.write_log("Made too many requests! Sleeping for 30 seconds.")
self.sleep(30)
- except (json.JSONDecodeError, KeyError, TypeError):
+ except (utils.json.JSONDecodeError, KeyError, TypeError):
pass
if response.status_code >= 400:
self.r = response
@@ -213,14 +120,14 @@ class Citizen(CitizenAPI):
try:
self.update_citizen_info(response.text)
- except (AttributeError, json.JSONDecodeError, ValueError, KeyError):
+ except (AttributeError, utils.json.JSONDecodeError, ValueError, KeyError):
pass
if self._errors_in_response(response):
self.get_csrf_token()
self.get(url, **kwargs)
else:
- self.check_for_medals(response.text)
+ self._check_response_for_medals(response.text)
self.r = response
return response
@@ -247,8 +154,8 @@ class Citizen(CitizenAPI):
try:
resp_data = response.json()
if (resp_data.get("error") or not resp_data.get("status")) and resp_data.get("message", "") == "captcha":
- send_email(self.name, [response.text, ], player=self, captcha=True)
- except:
+ utils.send_email(self.name, [response.text, ], player=self, captcha=True)
+ except (AttributeError, utils.json.JSONDecodeError, ValueError, KeyError):
pass
if self._errors_in_response(response):
@@ -259,12 +166,80 @@ class Citizen(CitizenAPI):
json.update({"_token": self.token})
response = self.post(url, data=data, json=json, **kwargs)
else:
- self.check_for_medals(response.text)
+ self._check_response_for_medals(response.text)
self.r = response
return response
- def check_for_medals(self, html: str):
+ def update_citizen_info(self, html: str = None):
+ """
+ Gets main page and updates most information about player
+ """
+ if html is None:
+ self._get_main()
+ return
+ ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1)
+ promos = utils.json.loads(utils.normalize_html_json(ugly_js))
+ 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
+ for promo in promos:
+ promo_name = promo.get("id")
+ expire = utils.localize_timestamp(int(promo.get("expiresAt")))
+ if promo_name not in self.promos:
+ send_mail = True
+ self.promos.update({promo_name: expire})
+ if send_mail:
+ 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)
+
+ new_date = re.search(r"var new_date = '(\d*)';", html)
+ if new_date:
+ self.energy.set_reference_time(
+ utils.good_timedelta(self.now, timedelta(seconds=int(new_date.group(1))))
+ )
+
+ ugly_js = re.search(r"var erepublik = ({.*}),\s+", html).group(1)
+ citizen_js = utils.json.loads(ugly_js)
+ citizen = citizen_js.get("citizen", {})
+
+ self.eday = citizen_js.get("settings").get("eDay")
+ self.division = int(citizen.get("division", 0))
+
+ self.energy.interval = citizen.get("energyPerInterval", 0)
+ self.energy.limit = citizen.get("energyToRecover", 0)
+ self.energy.recovered = citizen.get("energy", 0)
+ self.energy.recoverable = citizen.get("energyFromFoodRemaining", 0)
+
+ self.details.current_region = citizen.get("regionLocationId", 0)
+ self.details.current_country = citizen.get("countryLocationId", 0) # country where citizen is located
+ self.details.residence_region = citizen.get("residence", {}).get("regionId", 0)
+ self.details.residence_country = citizen.get("residence", {}).get("countryId", 0)
+ self.details.citizen_id = citizen.get("citizenId", 0)
+ self.details.citizenship = int(citizen.get("country", 0))
+ self.details.xp = citizen.get("currentExperiencePoints", 0)
+ self.details.daily_task_done = citizen.get("dailyTasksDone", False)
+ self.details.daily_task_reward = citizen.get("hasReward", False)
+ if citizen.get("dailyOrderDone", False) and not citizen.get("hasDailyOrderReward", False):
+ self._post_military_group_missions()
+
+ self.details.next_pp.sort()
+ for skill in citizen.get("mySkills", {}).values():
+ self.details.mayhem_skills.update({int(skill["terrain_id"]): int(skill["skill_points"])})
+
+ if citizen.get('party', []):
+ party = citizen.get('party')
+ self.politics.is_party_member = True
+ self.politics.party_id = party.get('party_id')
+ self.politics.is_party_president = bool(party.get('is_party_president'))
+ self.politics.party_slug = f"{party.get('stripped_title')}-{party.get('party_id')}"
+
+ def _check_response_for_medals(self, html: str):
new_medals = re.findall(r'(
)',
html, re.M | re.S | re.I)
data: Dict[Tuple[str, Union[float, str]], Dict[str, Union[int, str, float]]] = {}
@@ -293,12 +268,12 @@ class Citizen(CitizenAPI):
except AttributeError:
continue
if data:
-
msgs = ["{count} x {kind}, totaling {} {currency}\n"
"{about}".format(d["count"] * d["reward"], **d) for d in data.values()]
msgs = "\n".join(msgs)
- self.telegram.report_medal(msgs)
+ if self.config.telegram:
+ self.telegram.report_medal(msgs)
self.write_log(f"Found awards:\n{msgs}")
for info in data.values():
self.reporter.report_action("NEW_MEDAL", info)
@@ -308,171 +283,413 @@ class Citizen(CitizenAPI):
level = levelup.group(1)
msg = f"Level up! Current level {level}"
self.write_log(msg)
- self.telegram.report_medal(f"Level *{level}*")
+ if self.config.telegram:
+ self.telegram.report_medal(f"Level *{level}*")
self.reporter.report_action("LEVEL_UP", value=level)
- def check_for_notification_medals(self):
- notifications = self._get_main_citizen_daily_assistant().json()
- data: Dict[Tuple[str, Union[float, str]], Dict[str, Union[int, str, float]]] = {}
- for medal in notifications.get('notifications', []):
- if medal.get('details', {}).get('type') == "citizenAchievement":
- params = medal.get('details', {}).get('achievement')
- about = medal.get('details').get('description')
- title = medal.get('title')
- # 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)})
-
- if params.get('ccValue'):
- reward = params.get('ccValue')
- currency = "Currency"
- elif params.get('goldValue'):
- reward = params.get('goldValue')
- currency = "Gold"
- else:
- reward = params.get('energyValue')
- currency = "Energy"
-
- if (title, reward) not in data:
- data[(title, reward)] = {'about': about, 'kind': title, 'reward': reward, "count": 1,
- "currency": currency, "params": params}
- else:
- data[(title, reward)]['count'] += 1
- self._post_main_global_alerts_close(medal.get('id'))
- if data:
- msgs = ["{count} x {kind},"
- " totaling {} {currency}".format(d["count"] * d["reward"], **d) for d in data.values()]
-
- msgs = "\n".join(msgs)
- self.telegram.report_medal(msgs)
- self.write_log(f"Found awards:\n{msgs}")
- for info in data.values():
- self.reporter.report_action("NEW_MEDAL", info)
-
- def update_all(self, force_update=False):
- # Do full update max every 5 min
- if good_timedelta(self.__last_full_update, timedelta(minutes=5)) > self.now and not force_update:
- return
+ def write_log(self, *args, **kwargs):
+ if self.config.interactive:
+ utils.write_interactive_log(*args, **kwargs)
else:
- self.__last_full_update = self.now
+ utils.write_silent_log(*args, **kwargs)
+
+ def report_error(self, msg: str = "", is_warning: bool = False):
+ if is_warning:
+ utils.process_warning(msg, self.name, sys.exc_info(), self, self.commit_id)
+ else:
+ utils.process_error(msg, self.name, sys.exc_info(), self, self.commit_id, None)
+
+ def sleep(self, seconds: int):
+ if seconds < 0:
+ seconds = 0
+ if self.config.interactive:
+ utils.interactive_sleep(seconds)
+ else:
+ sleep(seconds)
+
+ def __str__(self) -> str:
+ return f"Citizen {self.name}"
+
+ def __repr__(self):
+ return self.__str__()
+
+ @property
+ def __dict__(self):
+ ret = super().__dict__.copy()
+ ret.pop('stop_threads', None)
+ ret.pop('_Citizen__last_war_update_data', None)
+
+ return ret
+
+ def _travel(self, country_id: int, region_id: int = 0) -> Response:
+ data = {
+ "toCountryId": country_id,
+ "inRegionId": region_id,
+ }
+ return self._post_main_travel("moveAction", **data)
+
+ @property
+ def health_info(self):
+ ret = f"{self.energy.recovered}/{self.energy.limit} + {self.energy.recoverable}, " \
+ f"{self.energy.interval}hp/6m. {self.details.xp_till_level_up}xp until level up"
+ return ret
+
+ @property
+ def now(self) -> datetime:
+ """
+ Returns aware datetime object localized to US/Pacific (eRepublik time)
+ :return: datetime
+ """
+ return utils.now()
+
+ def to_json(self, indent: bool = False) -> str:
+ return utils.json.dumps(self.__dict__, cls=MyJSONEncoder, indent=4 if indent else None, sort_keys=True)
+
+ @property
+ def next_reachable_energy(self) -> int:
+ # Return pps for furthest __reachable__ +1 energy else 0
+ max_pp = 0
+ for pp_milestone in self.details.next_pp:
+ pp_milestone = int(pp_milestone)
+ if self.details.pp + self.energy.food_fights > pp_milestone: # if reachable set max pp
+ max_pp = pp_milestone
+ else: # rest are only bigger no need
+ break
+ return max_pp - self.details.pp if max_pp else 0
+
+ @property
+ def next_wc_start(self) -> datetime:
+ days = 1 - self.now.weekday() if 1 - self.now.weekday() > 0 else 1 - self.now.weekday() + 7
+ return utils.good_timedelta(self.now.replace(hour=0, minute=0, second=0, microsecond=0), timedelta(days=days))
+
+ @property
+ def time_till_week_change(self) -> timedelta:
+ return self.next_wc_start - self.now
+
+ @property
+ def time_till_full_ff(self) -> timedelta:
+ energy = self.energy.recoverable + self.energy.recovered
+ if energy >= self.energy.limit * 2:
+ return timedelta(0)
+ minutes_needed = round((self.energy.limit * 2 - energy) / self.energy.interval) * 6
+ return (self.energy.reference_time - self.now) + timedelta(minutes=minutes_needed)
+
+ @property
+ def max_time_till_full_ff(self) -> timedelta:
+ """
+ Max required time for 0 to full energy (0/0 -> limit/limit) (last interval rounded up)
+ :return:
+ """
+ return timedelta(minutes=round((self.energy.limit * 2 / self.energy.interval) + 0.49) * 6)
+
+ @property
+ def is_levelup_close(self) -> bool:
+ """
+ If Energy limit * 2 >= xp till levelup * 10
+ :return: bool
+ """
+ return self.energy.limit * 2 >= self.details.xp_till_level_up * 10
+
+ @property
+ def is_levelup_reachable(self) -> bool:
+ """
+ If Energy limit >= xp till levelup * 10
+ :return: bool
+ """
+ return self.energy.limit >= self.details.xp_till_level_up * 10
+
+ @property
+ def should_do_levelup(self) -> bool:
+ """
+ If Energy limit >= xp till levelup * 10
+ :return: bool
+ """
+ return (self.energy.recovered >= self.details.xp_till_level_up * 10 and # can reach next level
+ self.energy.recoverable + 2 * self.energy.interval >= self.energy.limit) # can do max amount of dmg
+
+ @property
+ def available_industries(self) -> Dict[str, int]:
+ """
+ Returns currently available industries as dict(name: id)
+ :return: dict
+ """
+ return {"food": 1, "weapon": 2, "house": 4, "aircraft": 23,
+ "foodRaw": 7, "weaponRaw": 12, "houseRaw": 17, "airplaneRaw": 24}
+
+ @property
+ def factories(self) -> Dict[int, str]:
+ """Returns factory industries as dict(id: name)
+ :return: Factory id:name dict
+ ":rtype: Dict[int, str]
+ """
+ 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_main_party_members(self, party_id: int) -> Dict[int, str]:
+ ret = {}
+ r = super()._get_main_party_members(party_id)
+ for id_, name in re.findall(r'', r.text):
+ ret.update({id_: name})
+ return ret
+
+ def _eat(self, colour: str = "blue") -> Response:
+ response = self._post_eat(colour)
+ r_json = response.json()
+ next_recovery = r_json.get("food_remaining_reset").split(":")
+ self.energy.set_reference_time(
+ utils.good_timedelta(self.now, timedelta(seconds=int(next_recovery[1]) * 60 + int(next_recovery[2])))
+ )
+ self.energy.recovered = r_json.get("health")
+ self.energy.recoverable = r_json.get("food_remaining")
+ return response
+
+
+class CitizenTravel(BaseCitizen):
+ def _update_citizen_location(self, country_id: int, region_id: int):
+ self.details.current_region = region_id
+ self.details.current_country = country_id
+
+ def get_country_travel_region(self, country_id: int) -> int:
+ regions = self.get_travel_regions(country_id=country_id)
+ regs = []
+ if regions:
+ for region in regions.values():
+ if region['countryId'] == country_id: # Is not occupied by other country
+ regs.append((region['id'], region['distanceInKm']))
+ if regs:
+ return min(regs, key=lambda _: int(_[1]))[0]
+ else:
+ return 0
+
+ def travel_to_residence(self) -> bool:
self.update_citizen_info()
- self.update_war_info()
- self.update_inventory()
- self.update_companies()
- self.update_money()
- self.update_weekly_challenge()
- self.send_state_update()
- self.check_for_notification_medals()
+ res_r = self.details.residence_region
+ if self.details.residence_country and res_r and not res_r == self.details.current_region:
+ r = self._travel(self.details.residence_country, self.details.residence_region)
+ if r.json().get('message', '') == 'success':
+ self._update_citizen_location(self.details.residence_country, self.details.current_region)
+ return True
+ return False
+ return True
- def update_citizen_info(self, html: str = None):
- """
- Gets main page and updates most information about player
- """
- if html is None:
- self.check_for_notification_medals()
- self._get_main()
- return
- ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1)
- promos = json.loads(normalize_html_json(ugly_js))
- if self.promos is None:
- self.promos = {}
+ def travel_to_region(self, region_id: int) -> bool:
+ data = self._post_main_travel_data(region_id=region_id).json()
+ if data.get('alreadyInRegion'):
+ return True
else:
- self.promos = {k: v for k, v in self.promos.items() if v > self.now}
- send_mail = False
- for promo in promos:
- promo_name = promo.get("id")
- expire = localize_timestamp(int(promo.get("expiresAt")))
- if promo_name not in self.promos:
- send_mail = True
- self.promos.update({promo_name: expire})
- if promo_name == "trainingContract":
- if not self.tg_contract:
+ 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_main_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_main_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 get_travel_regions(self, holding_id: int = 0, battle_id: int = 0, country_id: int = 0
+ ) -> Union[List[Any], Dict[str, Dict[str, Any]]]:
+ d = self._post_main_travel_data(holdingId=holding_id, battleId=battle_id, countryId=country_id).json()
+ return d.get('regions', [])
+
+ def get_travel_countries(self) -> Set[int]:
+ response_json = self._post_main_travel_data().json()
+ return_list = {*[]}
+ for country_data in response_json['countries'].values():
+ if country_data['currentRegions']:
+ return_list.add(country_data['id'])
+ return return_list
+
+
+class CitizenTasks(BaseCitizen):
+ tg_contract: dict = {}
+ ot_points: int = 0
+ next_ot_time: datetime = None
+
+ def _eat(self, colour: str = "blue") -> Response:
+ resp = super()._eat(colour)
+ for q, amount in resp.json().get("units_consumed").items():
+ if f"q{q}" in self.food:
+ self.food[f"q{q}"] -= amount
+ elif q == "10":
+ self.eb_normal -= amount
+ elif q == "11":
+ self.eb_double -= amount
+ elif q == "12":
+ self.eb_small -= amount
+ return resp
+
+ def work(self):
+ if self.energy.food_fights >= 1:
+ response = self._post_economy_work("work")
+ js = response.json()
+ good_msg = ["already_worked", "captcha"]
+ if not js.get("status") and not js.get("message") in good_msg:
+ if js.get('message') == 'employee':
+ self.find_new_job()
+ self.update_citizen_info()
+ self.work()
+ else:
+ self.reporter.report_action("WORK", json_val=js)
+ else:
+ self._eat("blue")
+ if self.energy.food_fights < 1:
+ seconds = (self.energy.reference_time - self.now).total_seconds()
+ self.write_log("I don't have energy to work. Will sleep for {}s".format(seconds))
+ self.sleep(seconds)
+ self._eat("blue")
+ self.work()
+
+ def train(self):
+ r = self._get_main_training_grounds_json()
+ tg_json = r.json()
+ self.details.gold = tg_json["page_details"]["gold"]
+ self.tg_contract.update(free_train=tg_json["hasFreeTrain"])
+ if tg_json["contracts"]:
+ self.tg_contract.update(**tg_json["contracts"][0])
+
+ tgs = []
+ for data in sorted(tg_json["grounds"], key=lambda k: k["cost"]):
+ if data["default"] and not data["trained"]:
+ tgs.append(data["id"])
+ if tgs:
+ if self.energy.food_fights >= len(tgs):
+ response = self._post_economy_train(tgs)
+ if not response.json().get("status"):
+ self.update_citizen_info()
self.train()
- if not self.tg_contract["free_train"] and self.tg_contract.get("active", False):
- if self.details.gold >= 54:
- self.buy_tg_contract()
- else:
- self.write_log(f"Training ground contract active but "
- f"don't have enough gold ({self.details.gold}g {self.details.cc}cc)")
- if send_mail:
- active_promos = []
- for kind, time_until in self.promos.items():
- active_promos.append(f"{kind} active until {time_until}")
- report_promo(kind, time_until)
- send_email(self.name, active_promos, player=self, promo=True)
+ else:
+ self.reporter.report_action("TRAIN", response.json())
+ else:
+ self._eat("blue")
+ if self.energy.food_fights < len(tgs):
+ large = max(self.energy.reference_time, self.now)
+ small = min(self.energy.reference_time, self.now)
+ self.write_log("I don't have energy to train. Will sleep for {} seconds".format(
+ (large - small).seconds))
+ self.sleep(int((large - small).total_seconds()))
+ self._eat("blue")
+ self.train()
- new_date = re.search(r"var new_date = '(\d*)';", html)
- if new_date:
- self.energy.set_reference_time(
- good_timedelta(self.now, timedelta(seconds=int(new_date.group(1))))
- )
+ def work_ot(self):
+ # I"m not checking for 1h cooldown. Beware of nightshift work, if calling more than once every 60min
+ self.update_job_info()
+ if self.ot_points >= 24 and self.energy.food_fights > 1:
+ r = self._post_economy_work_overtime()
+ if not r.json().get("status") and r.json().get("message") == "money":
+ self.resign_from_employer()
+ self.find_new_job()
+ else:
+ if r.json().get('message') == 'employee':
+ self.find_new_job()
+ self.reporter.report_action("WORK_OT", r.json())
+ elif self.energy.food_fights < 1 and self.ot_points >= 24:
+ self._eat("blue")
+ if self.energy.food_fights < 1:
+ large = max(self.energy.reference_time, self.now)
+ small = min(self.energy.reference_time, self.now)
+ self.write_log("I don't have energy to work OT. Will sleep for {}s".format((large - small).seconds))
+ self.sleep(int((large - small).total_seconds()))
+ self._eat("blue")
+ self.work_ot()
- ugly_js = re.search(r"var erepublik = ({.*}),\s+", html).group(1)
- citizen_js = json.loads(ugly_js)
- citizen = citizen_js.get("citizen", {})
+ def resign_from_employer(self) -> bool:
+ self.update_job_info()
+ if self.r.json().get("isEmployee"):
+ self.reporter.report_action("RESIGN", self.r.json())
+ self._post_economy_resign()
+ return True
+ return False
- self.eday = citizen_js.get("settings").get("eDay")
- self.division = int(citizen.get("division", 0))
+ def buy_tg_contract(self) -> Response:
+ ret = self._post_main_buy_gold_items('gold', "TrainingContract2", 1)
+ self.reporter.report_action("BUY_TG_CONTRACT", ret.json())
+ return ret
- self.energy.interval = citizen.get("energyPerInterval", 0)
- self.energy.limit = citizen.get("energyToRecover", 0)
- self.energy.recovered = citizen.get("energy", 0)
- self.energy.recoverable = citizen.get("energyFromFoodRemaining", 0)
- if self.energy.is_energy_full:
- self.telegram.report_full_energy(self.energy.available, self.energy.limit, self.energy.interval)
+ def find_new_job(self) -> Response:
+ r = self._get_economy_job_market_json(self.details.current_country)
+ jobs = r.json().get("jobs")
+ data = dict(citizen=0, salary=10)
+ for posting in jobs:
+ salary = posting.get("salary")
+ limit = posting.get("salaryLimit", 0)
+ userid = posting.get("citizen").get("id")
- self.details.current_region = citizen.get("regionLocationId", 0)
- self.details.current_country = citizen.get("countryLocationId", 0) # country where citizen is located
- self.details.residence_region = citizen.get("residence", {}).get("regionId", 0)
- self.details.residence_country = citizen.get("residence", {}).get("countryId", 0)
- self.details.citizen_id = citizen.get("citizenId", 0)
- self.details.citizenship = int(citizen.get("country", 0))
- self.details.xp = citizen.get("currentExperiencePoints", 0)
- self.details.daily_task_done = citizen.get("dailyTasksDone", False)
- self.details.daily_task_reward = citizen.get("hasReward", False)
- if citizen.get("dailyOrderDone", False) and not citizen.get("hasDailyOrderReward", False):
- self._post_military_group_missions()
+ if (not limit or salary * 3 < limit) and salary > data["salary"]:
+ data.update({"citizen": userid, "salary": salary})
+ self.reporter.report_action("APPLYING_FOR_JOB", jobs, str(data['citizen']))
+ return self._post_economy_job_market_apply(**data)
- self.details.next_pp.sort()
- for skill in citizen.get("mySkills", {}).values():
- self.details.mayhem_skills.update({int(skill["terrain_id"]): int(skill["skill_points"])})
-
- if citizen.get('party', []):
- party = citizen.get('party')
- self.politics.is_party_member = True
- self.politics.party_id = party.get('party_id')
- self.politics.is_party_president = bool(party.get('is_party_president'))
- self.politics.party_slug = f"{party.get('stripped_title')}-{party.get('party_id')}"
-
- 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
- """
- if currency not in [1, 62]:
- currency = 62
- resp = self._post_economy_exchange_retrieve(False, page, currency)
- resp_data = resp.json()
- self.details.cc = float(resp_data.get("ecash").get("value"))
- self.details.gold = float(resp_data.get("gold").get("value"))
- return resp_data
+ def apply_to_employer(self, employer_id: int, salary: float) -> bool:
+ data = dict(citizen=0, salary=10)
+ self.reporter.report_action("APPLYING_FOR_JOB", data, str(data['citizen']))
+ r = self._post_economy_job_market_apply(**data)
+ return bool(r.json().get('status'))
def update_job_info(self):
ot = self._get_main_job_data().json().get("overTime", {})
if ot:
- self.my_companies.next_ot_time = localize_timestamp(int(ot.get("nextOverTime", 0)))
+ self.next_ot_time = utils.localize_timestamp(int(ot.get("nextOverTime", 0)))
self.ot_points = ot.get("points", 0)
- def update_companies(self):
- html = self._get_economy_my_companies().text
- page_details = json.loads(re.search(r"var pageDetails\s+= ({.*});", html).group(1))
- self.my_companies.work_units = int(page_details.get("total_works", 0))
- have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html)
- have_companies = re.search(r"var companies\s+= ({.*}});", html)
- if have_holdings and have_companies:
- self.my_companies.prepare_companies(json.loads(have_companies.group(1)))
- self.my_companies.prepare_holdings(json.loads(have_holdings.group(1)))
- self.my_companies.update_holding_companies()
+class CitizenPolitics(BaseCitizen):
+ def get_country_parties(self, country_id: int = None) -> dict:
+ if country_id is None:
+ country_id = self.details.citizenship
+ r = self._get_main_rankings_parties(country_id)
+ ret = {}
+ for name, id_ in re.findall(r'', r.text):
+ ret.update({int(id_): name})
+ return ret
+
+ def candidate_for_congress(self, presentation: str = "") -> Response:
+ return self._post_candidate_for_congress(presentation)
+
+ def candidate_for_party_presidency(self) -> Response:
+ return self._get_candidate_party(self.politics.party_slug)
+
+
+class CitizenEconomy(CitizenTravel):
+ food: Dict[str, int] = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
+ inventory: Dict[str, int] = {"used": 0, "total": 0}
+ boosters: Dict[int, Dict[int, int]] = {100: {}, 50: {}}
+
+ work_units = 0
+ ot_points = 0
+
+ my_companies: MyCompanies = None
+
+ def __init__(self):
+ super().__init__()
+ self.my_companies = MyCompanies()
def update_inventory(self) -> Dict[str, Any]:
"""
@@ -608,30 +825,581 @@ class Citizen(CitizenAPI):
"total": j.get("inventoryStatus").get("totalStorage")})
inventory = dict(items=dict(active=active_items, final=final_items,
raw=raw_materials, offers=offers), status=self.inventory)
- self.food["total"] = sum([self.food[q] * FOOD_ENERGY[q] for q in FOOD_ENERGY])
+ self.food["total"] = sum([self.food[q] * utils.FOOD_ENERGY[q] for q in utils.FOOD_ENERGY])
return inventory
- def update_weekly_challenge(self):
- data = self._get_main_weekly_challenge_data().json()
- self.details.pp = data.get("player", {}).get("prestigePoints", 0)
- self.details.next_pp.clear()
- for reward in data.get("rewards", {}).get("normal", {}):
- status = reward.get("status", "")
- if status == "rewarded":
+ def work_employees(self) -> bool:
+ self.update_companies()
+ ret = True
+ work_units_needed = 0
+ employee_companies = self.my_companies.get_employable_factories()
+ for c_id, preset_count in employee_companies.items():
+ work_units_needed += preset_count
+
+ if work_units_needed:
+ if work_units_needed <= self.my_companies.work_units:
+ self._do_wam_and_employee_work(employee_companies=employee_companies)
+ self.update_companies()
+ if self.my_companies.get_employable_factories():
+ ret = False
+ else:
+ ret = True
+
+ return ret
+
+ def work_wam(self) -> bool:
+ self.update_citizen_info()
+ self.update_companies()
+ # Prevent messing up levelup with wam
+ if not (self.is_levelup_close and self.config.fight) or self.config.force_wam:
+ # Check for current region
+ regions = {}
+ for holding_id, holding in self.my_companies.holdings.items():
+ if self.my_companies.get_holding_wam_companies(holding_id):
+ regions.update({holding["region_id"]: holding_id})
+
+ if self.details.current_region in regions:
+ self._do_wam_and_employee_work(regions.pop(self.details.current_region, None))
+
+ for holding_id in regions.values():
+ self._do_wam_and_employee_work(holding_id)
+
+ self.travel_to_residence()
+ else:
+ self.write_log("Did not wam because I would mess up levelup!")
+
+ self.update_companies()
+ return not self.my_companies.get_total_wam_count()
+
+ def _do_wam_and_employee_work(self, wam_holding_id: int = 0, employee_companies: dict = None) -> bool:
+ self.update_citizen_info()
+ if employee_companies is None:
+ employee_companies = {}
+ data = {"action_type": "production"}
+ extra = {}
+ wam_list = []
+ if wam_holding_id:
+ raw_count = self.my_companies.get_holding_wam_count(wam_holding_id, raw_factory=True)
+ fab_count = self.my_companies.get_holding_wam_count(wam_holding_id, raw_factory=False)
+ if raw_count + fab_count <= self.energy.food_fights:
+ raw_factories = None
+ elif not raw_count and fab_count <= self.energy.food_fights:
+ raw_factories = False
+ else:
+ raw_factories = True
+
+ free_inventory = self.inventory["total"] - self.inventory["used"]
+ wam_list = self.my_companies.get_holding_wam_companies(wam_holding_id,
+ raw_factory=raw_factories)[:self.energy.food_fights]
+ has_space = False
+ while not has_space and wam_list:
+ extra_needed = self.my_companies.get_needed_inventory_usage(companies=wam_list)
+ has_space = extra_needed < free_inventory
+ if not has_space:
+ inv_w = len(str(self.inventory["total"]))
+ self.write_log(
+ "Inv: {:{inv_w}}/{:{inv_w}} ({:4.2f}), Energy: {}/{} + {} (+{}hp/6min) WAM count {:3}".format(
+ self.inventory["used"], self.inventory["total"], extra_needed,
+ self.energy.recovered, self.energy.limit, self.energy.recoverable, self.energy.interval,
+ len(wam_list), inv_w=inv_w
+ ))
+ wam_list.pop(-1)
+
+ if wam_list or employee_companies:
+ data.update(extra)
+ if wam_list:
+ wam_holding = self.my_companies.holdings.get(wam_holding_id)
+ if not self.details.current_region == 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()
+ if response.get("status"):
+ self.reporter.report_action("WORK_WAM_EMPLOYEES", response)
+ if self.config.auto_sell:
+ for kind, data in response.get("result", {}).get("production", {}).items():
+ if kind in self.config.auto_sell and data:
+ if kind in ["food", "weapon", "house", "airplane"]:
+ for quality, amount in data.items():
+ self.sell_produced_product(kind, quality)
+
+ elif kind.endswith("Raw"):
+ self.sell_produced_product(kind, 1)
+
+ else:
+ raise ErepublikException("Unknown kind produced '{kind}'".format(kind=kind))
+ elif self.config.auto_buy_raw and re.search(r"not_enough_[^_]*_raw", response.get("message")):
+ raw_kind = re.search(r"not_enough_(\w+)_raw", response.get("message"))
+ if raw_kind:
+ raw_kind = raw_kind.group(1)
+ result = response.get("result", {})
+ amount_remaining = round(result.get("consume") + 0.49) - round(result.get("stock") - 0.49)
+ industry = "{}Raw".format(raw_kind)
+ while amount_remaining > 0:
+ amount = amount_remaining
+ best_offer = self.get_market_offers(self.details.citizenship, industry, 1)
+ 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'])
+ if not rj.get('error'):
+ amount_remaining -= amount
+ else:
+ self.write_log(rj.get('message', ""))
+ break
+ else:
+ return self._do_wam_and_employee_work(wam_holding_id, employee_companies)
+ elif response.get("message") == "not_enough_health_food":
+ self.buy_food()
+ return self._do_wam_and_employee_work(wam_holding_id, employee_companies)
+ else:
+ 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()
+ if wam_count:
+ self.write_log("Wam ff lockdown is now {}, was {}".format(wam_count, self.my_companies.ff_lockdown))
+ self.my_companies.ff_lockdown = wam_count
+ return bool(wam_count)
+
+ 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
+ """
+ if currency not in [1, 62]:
+ currency = 62
+ resp = self._post_economy_exchange_retrieve(False, page, currency)
+ resp_data = resp.json()
+ self.details.cc = float(resp_data.get("ecash").get("value"))
+ self.details.gold = float(resp_data.get("gold").get("value"))
+ return resp_data
+
+ def update_companies(self):
+ html = self._get_economy_my_companies().text
+ page_details = utils.json.loads(re.search(r"var pageDetails\s+= ({.*});", html).group(1))
+ self.my_companies.work_units = int(page_details.get("total_works", 0))
+
+ have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html)
+ have_companies = re.search(r"var companies\s+= ({.*}});", html)
+ if have_holdings and have_companies:
+ self.my_companies.prepare_companies(utils.json.loads(have_companies.group(1)))
+ self.my_companies.prepare_holdings(utils.json.loads(have_holdings.group(1)))
+ self.my_companies.update_holding_companies()
+
+ def check_house_durability(self) -> Dict[int, datetime]:
+ ret = {}
+ inv = self.update_inventory()
+ for house_quality, active_house in inv['items']['active'].get('house', {}).items():
+ till = utils.good_timedelta(self.now, timedelta(seconds=active_house['time_left']))
+ ret.update({house_quality: till})
+ return ret
+
+ def buy_and_activate_house(self, q: int) -> Dict[int, datetime]:
+ inventory = self.update_inventory()
+ ok_to_activate = False
+ if not inventory['items']['final'].get('house', {}).get(q, {}):
+ offers = []
+ countries = [self.details.citizenship, ]
+ if self.details.current_country != self.details.citizenship:
+ countries.append(self.details.current_country)
+ for country in countries:
+ offers += [self.get_market_offers(country, "house", q)]
+ global_cheapest = self.get_market_offers(product_name="house", quality=q)
+ cheapest_offer = sorted(offers, key=lambda o: o["price"])[0]
+ region = self.get_country_travel_region(global_cheapest['country'])
+ if global_cheapest['price'] + 200 < cheapest_offer['price'] and region:
+ self._travel(global_cheapest['country'], region)
+ buy = self.buy_from_market(global_cheapest['offer_id'], 1)
+ else:
+ buy = self.buy_from_market(cheapest_offer['offer_id'], 1)
+ if buy["error"]:
+ msg = f"Unable to buy q{q} house! \n{buy['message']}"
+ self.write_log(msg)
+ else:
+ ok_to_activate = True
+ else:
+ ok_to_activate = True
+ if ok_to_activate:
+ self.activate_house(q)
+ return self.check_house_durability()
+
+ def renew_houses(self, forced: bool = False) -> Dict[int, datetime]:
+ """
+ Renew all houses which endtime is in next 48h
+ :param forced: if true - renew all houses
+ :return:
+ """
+ house_durability = self.check_house_durability()
+ for q, active_till in house_durability.items():
+ if utils.good_timedelta(active_till, - timedelta(hours=48)) <= self.now or forced:
+ house_durability = self.buy_and_activate_house(q)
+ return house_durability
+
+ def activate_house(self, quality: int) -> datetime:
+ active_until = self.now
+ r = self._post_economy_activate_house(quality)
+ if r.json().get("status") and not r.json().get("error"):
+ house = r.json()["inventoryItems"]["activeEnhancements"]["items"]["4_%i_active" % quality]
+ active_until = utils.good_timedelta(active_until, timedelta(seconds=house["active"]["time_left"]))
+ return active_until
+
+ def get_game_token_offers(self):
+ r = self._post_economy_game_tokens_market('retrieve').json()
+ return {v.get('id'): dict(amount=v.get('amount'), price=v.get('price')) for v in r.get("topOffers")}
+
+ def fetch_organisation_account(self, org_id: int):
+ r = self._get_economy_citizen_accounts(org_id)
+ table = re.search(r'()', r.text, re.I | re.M | re.S)
+ if table:
+ account = re.findall(r'>\s*(\d+.\d+)\s*', table.group(1))
+ if account:
+ return {"gold": account[0], "cc": account[1], 'ok': True}
+
+ return {"gold": 0, "cc": 0, 'ok': False}
+
+ def accept_money_donations(self):
+ for notification in self._get_main_notifications_ajax_system():
+ don_id = re.search(r"erepublik.functions.acceptRejectDonation\(\"accept\", (\d+)\)", notification)
+ if don_id:
+ self._get_main_money_donation_accept(int(don_id.group(1)))
+ self.sleep(5)
+
+ def reject_money_donations(self) -> int:
+ r = self._get_main_notifications_ajax_system()
+ count = 0
+ donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text)
+ while donation_ids:
+ for don_id in donation_ids:
+ self._get_main_money_donation_reject(int(don_id))
+ count += 1
+ self.sleep(5)
+ r = self._get_main_notifications_ajax_system()
+ donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text)
+ return count
+
+ def get_my_market_offers(self) -> List[Dict[str, Union[int, float, str]]]:
+ ret = []
+ for offer in self._get_economy_my_market_offers().json():
+ line = offer.copy()
+ line.pop('icon', None)
+ ret.append(line)
+ return ret
+
+ def post_market_offer(self, industry: int, quality: int, amount: int, price: float) -> Response:
+ if industry not in self.available_industries.values():
+ self.write_log(f"Trying to sell unsupported industry {industry}")
+
+ data = {
+ "country": self.details.citizenship,
+ "industry": industry,
+ "quality": quality,
+ "amount": amount,
+ "price": price,
+ "buy": False,
+ }
+ ret = self._post_economy_marketplace_actions(**data)
+ self.reporter.report_action("SELL_PRODUCT", ret.json())
+ return ret
+
+ def buy_from_market(self, offer: int, amount: int) -> dict:
+ ret = self._post_economy_marketplace_actions(amount, True, offer=offer)
+ json_ret = ret.json()
+ if json_ret.get('error'):
+ return json_ret
+ else:
+ self.details.cc = ret.json()['currency']
+ self.details.gold = ret.json()['gold']
+ r_json = ret.json()
+ r_json.pop("offerUpdate", None)
+ self.reporter.report_action("BUY_PRODUCT", ret.json())
+ return json_ret
+
+ def assign_factory_to_holding(self, factory_id: int, holding_id: int) -> Response:
+ """
+ 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)
+
+ def upgrade_factory(self, factory_id: int, level: int) -> Response:
+ return self._post_economy_upgrade_company(factory_id, level, self.details.pin)
+
+ def create_factory(self, industry_id: int, building_type: int = 1) -> Response:
+ """
+ param industry_ids: FRM={q1:7, q2:8, q3:9, q4:10, q5:11} WRM={q1:12, q2:13, q3:14, q4:15, q5:16}
+ HRM={q1:18, q2:19, q3:20, q4:21, q5:22} ARM={q1:24, q2:25, q3:26, q4:27, q5:28}
+ Factories={Food:1, Weapons:2, House:4, Aircraft:23} <- Building_type 1
+
+ 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)
+
+ 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)
+
+ def get_industry_id(self, industry_name: str) -> int:
+ """Returns industry id
+
+ :type industry_name: str
+ :return: int
+ """
+ return self.available_industries.get(industry_name, 0)
+
+ def get_industry_name(self, industry_id: int) -> str:
+ """Returns industry name from industry ID
+
+ :type industry_id: int
+ :return: industry name
+ :rtype: str
+ """
+ for iname, iid in self.available_industries.items():
+ if iid == industry_id:
+ return iname
+ return ""
+
+ def get_market_offers(self, country_id: int = None, product_name: str = None, quality: int = None) -> dict:
+ raw_short_names = dict(frm="foodRaw", wrm="weaponRaw", hrm="houseRaw", arm="airplaneRaw")
+ q1_industries = ["aircraft"] + list(raw_short_names.values())
+ if product_name:
+ if product_name not in self.available_industries and product_name not in raw_short_names:
+ self.write_log(f"Industry '{product_name}' not implemented")
+ raise ErepublikException(f"Industry '{product_name}' not implemented")
+ elif product_name in raw_short_names:
+ quality = 1
+ product_name = raw_short_names[product_name]
+ product_name = [product_name]
+ elif quality:
+ raise ErepublikException("Quality without product not allowed")
+
+ item_data = dict(price=999999., country=0, amount=0, offer_id=0, citizen_id=0)
+
+ items = {"food": dict(q1=item_data.copy(), q2=item_data.copy(), q3=item_data.copy(), q4=item_data.copy(),
+ q5=item_data.copy(), q6=item_data.copy(), q7=item_data.copy()),
+ "weapon": dict(q1=item_data.copy(), q2=item_data.copy(), q3=item_data.copy(), q4=item_data.copy(),
+ q5=item_data.copy(), q6=item_data.copy(), q7=item_data.copy()),
+ "house": dict(q1=item_data.copy(), q2=item_data.copy(), q3=item_data.copy(), q4=item_data.copy(),
+ q5=item_data.copy()), "aircraft": 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())}
+
+ if country_id:
+ countries = [country_id]
+ else:
+ countries = self.get_travel_countries()
+
+ start_dt = self.now
+ iterable = [countries, product_name or items, [quality] if quality else range(1, 8)]
+ for country, industry, q in product(*iterable):
+ if (q > 1 and industry in q1_industries) or (q > 5 and industry == "house"):
continue
- elif status == "completed":
- self._post_main_weekly_challenge_reward(reward.get("id", 0))
- elif reward.get("icon", "") == "energy_booster":
- pps = re.search(r"Reach (\d+) Prestige Points to unlock the following reward: \+1 Energy",
- reward.get("tooltip", ""))
- if pps:
- self.details.next_pp.append(int(pps.group(1)))
+
+ r = self._post_economy_marketplace(country, self.available_industries[industry], q).json()
+ obj = items[industry][f"q{q}"]
+ if not r.get("error", False):
+ for offer in r["offers"]:
+ if obj["price"] > float(offer["priceWithTaxes"]):
+ obj["price"] = float(offer["priceWithTaxes"])
+ obj["country"] = int(offer["country_id"])
+ obj["amount"] = int(offer["amount"])
+ obj["offer_id"] = int(offer["id"])
+ obj["citizen_id"] = int(offer["citizen_id"])
+ elif obj["price"] == float(offer["priceWithTaxes"]) and obj["amount"] < int(offer["amount"]):
+ obj["country"] = int(offer["country_id"])
+ obj["amount"] = int(offer["amount"])
+ obj["offer_id"] = int(offer["id"])
+ self.write_log(f"Scraped market in {self.now - start_dt}!")
+
+ if quality:
+ ret = items[product_name[0]]["q%i" % quality]
+ elif product_name:
+ if product_name[0] in raw_short_names.values():
+ ret = items[product_name[0]]["q1"]
+ else:
+ ret = items[product_name[0]]
+ else:
+ ret = items
+ return ret
+
+ def buy_food(self):
+ hp_per_quality = {"q1": 2, "q2": 4, "q3": 6, "q4": 8, "q5": 10, "q6": 12, "q7": 20}
+ hp_needed = 48 * self.energy.interval * 10 - self.food["total"]
+ local_offers = self.get_market_offers(country_id=self.details.current_country, product_name="food")
+
+ cheapest_q, cheapest = sorted(local_offers.items(), key=lambda v: v[1]["price"] / hp_per_quality[v[0]])[0]
+
+ if cheapest["amount"] * hp_per_quality[cheapest_q] < hp_needed:
+ amount = cheapest["amount"]
+ else:
+ amount = hp_needed // hp_per_quality[cheapest_q]
+
+ if amount * cheapest["price"] < self.details.cc:
+ data = dict(offer=cheapest["offer_id"], amount=amount, price=cheapest["price"],
+ cost=amount * cheapest["price"], quality=cheapest_q, energy=amount * hp_per_quality[cheapest_q])
+ self.reporter.report_action("BUY_FOOD", json_val=data)
+ self.buy_from_market(cheapest["offer_id"], amount)
+ self.update_inventory()
+ else:
+ s = f"Don't have enough money! Needed: {amount * cheapest['price']}cc, Have: {self.details.cc}cc"
+ self.write_log(s)
+ self.reporter.report_action("BUY_FOOD", value=s)
+
+ def get_monetary_offers(self, currency: int = 62) -> List[Dict[str, Union[int, float]]]:
+ if currency not in [1, 62]:
+ currency = 62
+ resp = self._post_economy_exchange_retrieve(False, 0, currency).json()
+ ret = []
+ 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'",
+ resp["buy_mode"], re.M | re.I | re.S)
+
+ for offer_id, price, amount in offers:
+ ret.append({"offer_id": int(offer_id), "price": float(price), "amount": float(amount)})
+
+ return sorted(ret, key=lambda o: (o["price"], -o["amount"]))
+
+ def buy_monetary_market_offer(self, offer: int, amount: float, currency: int) -> bool:
+ response = self._post_economy_exchange_purchase(amount, currency, offer)
+ self.details.cc = float(response.json().get("ecash").get("value"))
+ self.details.gold = float(response.json().get("gold").get("value"))
+ self.reporter.report_action("BUY_GOLD", json_val=response.json(),
+ value=f"New amount {self.details.cc}cc, {self.details.gold}g")
+ return not response.json().get("error", False)
+
+ def donate_money(self, citizen_id: int = 1620414, amount: float = 0.0, currency: int = 62) -> bool:
+ """ currency: gold = 62, cc = 1 """
+ resp = self._post_economy_donate_money_action(citizen_id, amount, currency)
+ r = re.search('You do not have enough money in your account to make this donation', resp.text)
+ return not bool(r)
+
+ def donate_items(self, citizen_id: int = 1620414, amount: int = 0, industry_id: int = 1, quality: int = 1) -> int:
+ if amount < 1:
+ return 0
+ ind = {v: k for k, v in self.available_industries.items()}
+ self.write_log(f"Donate: {amount:4d}q{quality} {ind[industry_id]} to {citizen_id}")
+ response = self._post_economy_donate_items_action(citizen_id, amount, industry_id, quality)
+ if re.search(rf"Successfully transferred {amount} item\(s\) to", response.text):
+ return amount
+ else:
+ if re.search(r"You do not have enough items in your inventory to make this donation", response.text):
+ return 0
+ available = re.search(rf"Cannot transfer the items because the user has only (\d+) free slots in (his|her) "
+ rf"storage.", response.text).group(1)
+ return self.donate_items(citizen_id, int(available), industry_id, quality)
+
+ def sell_produced_product(self, kind: str, quality: int = 1, amount: int = 0):
+ if not amount:
+ inv_resp = self._get_economy_inventory_items().json()
+ category = "rawMaterials" if kind.endswith("Raw") else "finalProducts"
+ item = "{}_{}".format(self.available_industries[kind], quality)
+ amount = inv_resp.get("inventoryItems").get(category).get("items").get(item).get("amount", 0)
+
+ if amount >= 1:
+ lowest_price = self.get_market_offers(country_id=self.details.citizenship,
+ product_name=kind, quality=int(quality))
+
+ if lowest_price["citizen_id"] == self.details.citizen_id:
+ price = lowest_price["price"]
+ else:
+ price = lowest_price["price"] - 0.01
+
+ self.post_market_offer(industry=self.available_industries[kind], amount=int(amount),
+ quality=int(quality), price=price)
+
+ def contribute_cc_to_country(self, amount=0., country_id: int = 71) -> bool:
+ self.update_money()
+ amount = int(amount)
+ if self.details.cc < amount or amount < 20:
+ return False
+ data = dict(country=country_id, action='currency', value=amount)
+ self.reporter.report_action("CONTRIBUTE_CC", data, str(amount))
+ r = self._post_main_country_donate(**data)
+ return r.json().get('status') or not r.json().get('error')
+
+ def contribute_food_to_country(self, amount: int = 0, quality: int = 1, country_id: int = 71) -> bool:
+ self.update_inventory()
+ amount = amount // 1
+ if self.food["q" + str(quality)] < amount or amount < 10:
+ return False
+ data = dict(country=country_id, action='food', value=amount, quality=quality)
+ self.reporter.report_action("CONTRIBUTE_FOOD", data, utils.FOOD_ENERGY[quality] * amount)
+ r = self._post_main_country_donate(**data)
+ return r.json().get('status') or not r.json().get('error')
+
+ def contribute_gold_to_country(self, amount: int, country_id: int = 71) -> bool:
+ self.update_money()
+
+ if self.details.cc < amount:
+ return False
+ data = dict(country=country_id, action='gold', value=amount)
+ self.reporter.report_action("CONTRIBUTE_GOLD", data, str(amount))
+ r = self._post_main_country_donate(**data)
+ return r.json().get('status') or not r.json().get('error')
+
+
+class CitizenMedia(BaseCitizen):
+ def endorse_article(self, article_id: int, amount: int) -> bool:
+ if amount in (5, 50, 100):
+ resp = self._post_main_donate_article(article_id, amount).json()
+ return not bool(resp.get('error'))
+ else:
+ return False
+
+ def vote_article(self, article_id: int) -> bool:
+ resp = self._post_main_vote_article(article_id).json()
+ return not bool(resp.get('error'))
+
+ def get_article_comments(self, article_id: int, page_id: int = 1) -> Response:
+ return self._post_main_article_comments(article_id, page_id)
+
+ def comment_article(self, article_id: int = 2645676, msg: str = None) -> Response:
+ if msg is None:
+ msg = self.eday
+ r = self.get_article_comments(article_id, 2)
+ r = self.get_article_comments(article_id, r.json()["pages"])
+ comments = r.json()["comments"]
+ if not comments[max(comments.keys())]["isMyComment"]:
+ r = self.write_article_comment(msg, article_id)
+ return r
+
+ def write_article_comment(self, message: str, article_id: int, parent_id: int = None) -> Response:
+ return self._post_main_article_comments_create(message, article_id, parent_id)
+
+ def publish_article(self, title: str, content: str, kind: int) -> Response:
+ kinds = {1: "First steps in eRepublik", 2: "Battle orders", 3: "Warfare analysis",
+ 4: "Political debates and analysis", 5: "Financial business",
+ 6: "Social interactions and entertainment"}
+ if kind in kinds:
+ return self._post_main_write_article(title, content, self.details.citizenship, kind)
+ else:
+ raise ErepublikException("Article kind must be one of:\n{}\n'{}' is not supported".format(
+ "\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), kind
+ ))
+
+
+class CitizenMilitary(CitizenTravel, CitizenTasks):
+ all_battles: Dict[int, Battle] = None
+ countries: Dict[int, Dict[str, Union[str, List[int]]]] = None
+ __last_war_update_data = None
+
+ active_fs: bool = False
+ 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():
+ if self.__last_war_update_data and self.__last_war_update_data.get('last_updated',
+ 0) + 30 > self.now.timestamp():
resp_json = self.__last_war_update_data
else:
resp_json = self._get_military_campaigns_json_list().json()
@@ -658,64 +1426,55 @@ class Citizen(CitizenAPI):
for battle_data in resp_json.get("battles", {}).values():
self.all_battles[battle_data.get('id')] = Battle(battle_data)
- def eat(self):
- """
- Try to eat food
- """
- if self.food["total"] > self.energy.interval:
- if self.energy.limit - self.energy.recovered > self.energy.interval or not self.energy.recoverable % 2:
- self._eat("blue")
- else:
- self.write_log("I don't want to eat right now!")
+ def get_battle_for_war(self, war_id: int) -> Optional[Battle]:
+ self.update_war_info()
+ war_info = self.get_war_status(war_id)
+ return self.all_battles.get(war_info.get("battle_id"), None)
+
+ def get_war_status(self, war_id: int) -> Dict[str, Union[bool, Dict[int, str]]]:
+ 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({int(reg[0]): reg[1]})
+ elif re.search(r'Join', html):
+ battle_id = re.search(r'Join', html).group(1)
+ ret.update(can_attack=False, battle_id=int(battle_id))
+ elif re.search(r'This war is no longer active.', html):
+ ret.update(can_attack=False, ended=True)
else:
- self.write_log(f"I'm out of food! But I'll try to buy some!\n{self.food}")
- self.buy_food()
- if self.food["total"] > self.energy.interval:
- self.eat()
- else:
- self.write_log("I failed to buy food")
- self.write_log(self.health_info)
-
- def eat_ebs(self):
- self.write_log("Eating energy bar")
- if self.energy.recoverable:
- self._eat("blue")
- self._eat("orange")
- self.write_log(self.health_info)
-
- def _eat(self, colour: str = "blue") -> Response:
- response = self._post_eat(colour)
- r_json = response.json()
- next_recovery = r_json.get("food_remaining_reset").split(":")
- self.energy.set_reference_time(
- good_timedelta(self.now, timedelta(seconds=int(next_recovery[1]) * 60 + int(next_recovery[2])))
- )
- self.energy.recovered = r_json.get("health")
- self.energy.recoverable = r_json.get("food_remaining")
- for q, amount in r_json.get("units_consumed").items():
- if f"q{q}" in self.food:
- self.food[f"q{q}"] -= amount
- elif q == "10":
- self.eb_normal -= amount
- elif q == "11":
- self.eb_double -= amount
- elif q == "12":
- self.eb_small -= amount
- return response
-
- @property
- def health_info(self):
- ret = f"{self.energy.recovered}/{self.energy.limit} + {self.energy.recoverable}, " \
- f"{self.energy.interval}hp/6m. {self.details.xp_till_level_up}xp until level up"
+ ret.update(can_attack=False)
return ret
- @property
- def now(self) -> datetime:
- """
- Returns aware datetime object localized to US/Pacific (eRepublik time)
- :return: datetime
- """
- return now()
+ 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.get(battle_id)
+ available_weapons = self._get_military_show_weapons(battle_id).json()
+ while not isinstance(available_weapons, list):
+ available_weapons = self._get_military_show_weapons(battle_id).json()
+ weapon_quality = -1
+ weapon_damage = 0
+ if not battle.is_air:
+ for weapon in available_weapons:
+ try:
+ if weapon['weaponQuantity'] > 30 and weapon['damage'] > weapon_damage:
+ weapon_quality = int(weapon['weaponId'])
+ except ValueError:
+ pass
+ return self.change_weapon(battle_id, weapon_quality)
+
+ def change_weapon(self, battle_id: int, weapon_quality: int) -> int:
+ battle = self.all_battles.get(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')
def check_epic_battles(self):
active_fs = False
@@ -872,7 +1631,7 @@ class Citizen(CitizenAPI):
continue
if battle.start > self.now:
- self.sleep(get_sleep_seconds(battle.start))
+ self.sleep(utils.get_sleep_seconds(battle.start))
if travel_needed:
if battle.is_rw:
@@ -889,7 +1648,6 @@ class Citizen(CitizenAPI):
self.set_default_weapon(battle_id)
self.fight(battle_id, side_id)
self.travel_to_residence()
- self.collect_weekly_reward()
break
def fight(self, battle_id: int, side_id: int = None, count: int = None) -> int:
@@ -1023,474 +1781,31 @@ class Citizen(CitizenAPI):
battle = self.all_battles.get(battle_id)
self._post_main_battlefield_change_division(battle_id, battle.div[division_to].battle_zone_id)
- def work_ot(self):
- # I"m not checking for 1h cooldown. Beware of nightshift work, if calling more than once every 60min
- self.update_job_info()
- if self.ot_points >= 24 and self.energy.food_fights > 1:
- r = self._post_economy_work_overtime()
- if not r.json().get("status") and r.json().get("message") == "money":
- self.resign()
- self.find_new_job()
- else:
- if r.json().get('message') == 'employee':
- self.find_new_job()
- self.reporter.report_action("WORK_OT", r.json())
- elif self.energy.food_fights < 1 and self.ot_points >= 24:
- self._eat("blue")
- if self.energy.food_fights < 1:
- large = max(self.energy.reference_time, self.now)
- small = min(self.energy.reference_time, self.now)
- self.write_log("I don't have energy to work OT. Will sleep for {}s".format((large - small).seconds))
- self.sleep(int((large - small).total_seconds()))
- self._eat("blue")
- self.work_ot()
+ def get_ground_hit_dmg_value(self, rang: int = None, strength: float = None, elite: bool = None, ne: bool = False,
+ booster_50: bool = False, booster_100: bool = False, tp: bool = True) -> float:
+ if not rang or strength or elite is None:
+ r = self._get_main_citizen_profile_json(self.details.citizen_id).json()
+ if not rang:
+ rang = r['military']['militaryData']['ground']['rankNumber']
+ if not strength:
+ strength = r['military']['militaryData']['ground']['strength']
+ if elite is None:
+ elite = r['citizenAttributes']['level'] > 100
+ if ne:
+ tp = True
- def work(self):
- if self.energy.food_fights >= 1:
- response = self._post_economy_work("work")
- js = response.json()
- good_msg = ["already_worked", "captcha"]
- if not js.get("status") and not js.get("message") in good_msg:
- if js.get('message') == 'employee':
- self.find_new_job()
- self.update_citizen_info()
- self.work()
- else:
- self.reporter.report_action("WORK", json_val=js)
- else:
- self._eat("blue")
- if self.energy.food_fights < 1:
- seconds = (self.energy.reference_time - self.now).total_seconds()
- self.write_log("I don't have energy to work. Will sleep for {}s".format(seconds))
- self.sleep(seconds)
- self._eat("blue")
- self.work()
+ return utils.calculate_hit(strength, rang, tp, elite, ne, 50 if booster_50 else 100 if booster_100 else 0)
- def train(self):
- r = self._get_main_training_grounds_json()
- tg_json = r.json()
- self.details.gold = tg_json["page_details"]["gold"]
- self.tg_contract = {"free_train": tg_json["hasFreeTrain"]}
- if tg_json["contracts"]:
- self.tg_contract.update(**tg_json["contracts"][0])
+ def get_air_hit_dmg_value(self, rang: int = None, elite: bool = None, ne: bool = False,
+ weapon: bool = False) -> float:
+ if not rang or elite is None:
+ r = self._get_main_citizen_profile_json(self.details.citizen_id).json()
+ if not rang:
+ rang = r['military']['militaryData']['air']['rankNumber']
+ if elite is None:
+ elite = r['citizenAttributes']['level'] > 100
- tgs = []
- for data in sorted(tg_json["grounds"], key=lambda k: k["cost"]):
- if data["default"] and not data["trained"]:
- tgs.append(data["id"])
- if tgs:
- if self.energy.food_fights >= len(tgs):
- response = self._post_economy_train(tgs)
- if not response.json().get("status"):
- self.update_citizen_info()
- self.train()
- else:
- self.reporter.report_action("TRAIN", response.json())
- else:
- self._eat("blue")
- if self.energy.food_fights < len(tgs):
- large = max(self.energy.reference_time, self.now)
- small = min(self.energy.reference_time, self.now)
- self.write_log("I don't have energy to train. Will sleep for {} seconds".format(
- (large - small).seconds))
- self.sleep(int((large - small).total_seconds()))
- self._eat("blue")
- self.train()
-
- def work_employees(self) -> bool:
- self.update_companies()
- ret = True
- work_units_needed = 0
- employee_companies = self.my_companies.get_employable_factories()
- for c_id, preset_count in employee_companies.items():
- work_units_needed += preset_count
-
- if work_units_needed:
- if work_units_needed <= self.my_companies.work_units:
- self._do_wam_and_employee_work(employee_companies=employee_companies)
- self.update_companies()
- if self.my_companies.get_employable_factories():
- ret = False
- else:
- ret = True
-
- return ret
-
- def work_wam(self) -> bool:
- self.update_citizen_info()
- self.update_companies()
- # Prevent messing up levelup with wam
- if not (self.is_levelup_close and self.config.fight) or self.config.force_wam:
- # Check for current region
- regions = {}
- for holding_id, holding in self.my_companies.holdings.items():
- if self.my_companies.get_holding_wam_companies(holding_id):
- regions.update({holding["region_id"]: holding_id})
-
- if self.details.current_region in regions:
- self._do_wam_and_employee_work(regions.pop(self.details.current_region, None))
-
- for holding_id in regions.values():
- self._do_wam_and_employee_work(holding_id)
-
- self.travel_to_residence()
- else:
- self.write_log("Did not wam because I would mess up levelup!")
-
- self.update_companies()
- return not self.my_companies.get_total_wam_count()
-
- def _do_wam_and_employee_work(self, wam_holding_id: int = 0, employee_companies: dict = None) -> bool:
- self.update_citizen_info()
- if employee_companies is None:
- employee_companies = {}
- data = {
- "action_type": "production",
- }
- extra = {}
- wam_list = []
- if wam_holding_id:
- raw_count = self.my_companies.get_holding_wam_count(wam_holding_id, raw_factory=True)
- fab_count = self.my_companies.get_holding_wam_count(wam_holding_id, raw_factory=False)
- if raw_count + fab_count <= self.energy.food_fights:
- raw_factories = None
- elif not raw_count and fab_count <= self.energy.food_fights:
- raw_factories = False
- else:
- raw_factories = True
-
- free_inventory = self.inventory["total"] - self.inventory["used"]
- wam_list = self.my_companies.get_holding_wam_companies(wam_holding_id,
- raw_factory=raw_factories)[:self.energy.food_fights]
- has_space = False
- while not has_space and wam_list:
- extra_needed = self.my_companies.get_needed_inventory_usage(companies=wam_list)
- has_space = extra_needed < free_inventory
- if not has_space:
- inv_w = len(str(self.inventory["total"]))
- self.write_log(
- "Inv: {:{inv_w}}/{:{inv_w}} ({:4.2f}), Energy: {}/{} + {} (+{}hp/6min) WAM count {:3}".format(
- self.inventory["used"], self.inventory["total"], extra_needed,
- self.energy.recovered, self.energy.limit, self.energy.recoverable, self.energy.interval,
- len(wam_list), inv_w=inv_w
- ))
- wam_list.pop(-1)
-
- if wam_list or employee_companies:
- data.update(extra)
- if wam_list:
- wam_holding = self.my_companies.holdings.get(wam_holding_id)
- if not self.details.current_region == 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()
- if response.get("status"):
- self.reporter.report_action("WORK_WAM_EMPLOYEES", response)
- if self.config.auto_sell:
- for kind, data in response.get("result", {}).get("production", {}).items():
- if kind in self.config.auto_sell and data:
- if kind in ["food", "weapon", "house", "airplane"]:
- for quality, amount in data.items():
- self.sell_produced_product(kind, quality)
-
- elif kind.endswith("Raw"):
- self.sell_produced_product(kind, 1)
-
- else:
- raise ErepublikException("Unknown kind produced '{kind}'".format(kind=kind))
- elif self.config.auto_buy_raw and re.search(r"not_enough_[^_]*_raw", response.get("message")):
- raw_kind = re.search(r"not_enough_(\w+)_raw", response.get("message"))
- if raw_kind:
- raw_kind = raw_kind.group(1)
- result = response.get("result", {})
- amount_remaining = round(result.get("consume") + 0.49) - round(result.get("stock") - 0.49)
- industry = "{}Raw".format(raw_kind)
- while amount_remaining > 0:
- amount = amount_remaining
- best_offer = self.get_market_offers(self.details.citizenship, industry, 1)
- 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'])
- if not rj.get('error'):
- amount_remaining -= amount
- else:
- self.write_log(rj.get('message', ""))
- break
- else:
- return self._do_wam_and_employee_work(wam_holding_id, employee_companies)
- elif response.get("message") == "not_enough_health_food":
- self.buy_food()
- return self._do_wam_and_employee_work(wam_holding_id, employee_companies)
- else:
- 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()
- if wam_count:
- self.write_log("Wam ff lockdown is now {}, was {}".format(wam_count, self.my_companies.ff_lockdown))
- self.my_companies.ff_lockdown = wam_count
- return bool(wam_count)
-
- def sell_produced_product(self, kind: str, quality: int = 1, amount: int = 0):
- if not amount:
- inv_resp = self._get_economy_inventory_items().json()
- category = "rawMaterials" if kind.endswith("Raw") else "finalProducts"
- item = "{}_{}".format(self.available_industries[kind], quality)
- amount = inv_resp.get("inventoryItems").get(category).get("items").get(item).get("amount", 0)
-
- if amount >= 1:
- lowest_price = self.get_market_offers(country_id=self.details.citizenship,
- product_name=kind, quality=int(quality))
-
- if lowest_price["citizen_id"] == self.details.citizen_id:
- price = lowest_price["price"]
- else:
- price = lowest_price["price"] - 0.01
-
- self.post_market_offer(industry=self.available_industries[kind], amount=int(amount),
- quality=int(quality), price=price)
-
- def get_country_travel_region(self, country_id: int) -> int:
- regions = self.get_travel_regions(country_id=country_id)
- regs = []
- if regions:
- for region in regions.values():
- if region['countryId'] == country_id: # Is not occupied by other country
- regs.append((region['id'], region['distanceInKm']))
- if regs:
- return min(regs, key=lambda _: int(_[1]))[0]
- else:
- return 0
-
- def _update_citizen_location(self, country_id: int, region_id: int):
- self.details.current_region = region_id
- self.details.current_country = country_id
-
- def travel_to_residence(self) -> bool:
- 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:
- r = self._travel(self.details.residence_country, self.details.residence_region)
- if r.json().get('message', '') == 'success':
- self._update_citizen_location(self.details.residence_country, self.details.current_region)
- return True
- return False
- return True
-
- def travel_to_region(self, region_id: int) -> bool:
- data = self._post_main_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_main_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_main_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:
- data = {
- "toCountryId": country_id,
- "inRegionId": region_id,
- }
- return self._post_main_travel("moveAction", **data)
-
- def get_travel_regions(self, holding_id: int = 0, battle_id: int = 0, country_id: int = 0
- ) -> Union[List[Any], Dict[str, Dict[str, Any]]]:
- d = self._post_main_travel_data(holdingId=holding_id, battleId=battle_id, countryId=country_id).json()
- return d.get('regions', [])
-
- def get_travel_countries(self) -> Set[int]:
- response_json = self._post_main_travel_data().json()
- return_list = {*[]}
- for country_data in response_json['countries'].values():
- if country_data['currentRegions']:
- return_list.add(country_data['id'])
- return return_list
-
- def parse_notifications(self, page: int = 1) -> list:
- community = self._get_main_notifications_ajax_community(page).json()
- system = self._get_main_notifications_ajax_system(page).json()
- return community['alertsList'] + system['alertsList']
-
- def delete_notifications(self):
- response = self._get_main_notifications_ajax_community().json()
- while response['totalAlerts']:
- self._post_main_messages_alert([_['id'] for _ in response['alertList']])
- response = self._get_main_notifications_ajax_community().json()
-
- response = self._get_main_notifications_ajax_system().json()
- while response['totalAlerts']:
- self._post_main_messages_alert([_['id'] for _ in response['alertList']])
- response = self._get_main_notifications_ajax_system().json()
-
- def collect_weekly_reward(self):
- self.update_weekly_challenge()
-
- def collect_daily_task(self) -> None:
- self.update_citizen_info()
- if self.details.daily_task_done and not self.details.daily_task_reward:
- self._post_main_daily_task_reward()
-
- def send_mail_to_owner(self) -> None:
- if not self.details.citizen_id == 1620414:
- self.send_mail("Started", "time {}".format(self.now.strftime("%Y-%m-%d %H-%M-%S")), [1620414, ])
- self.sleep(1)
- msg_id = re.search(r"", self.r.text).group(1)
- self._post_delete_message([msg_id])
-
- def get_market_offers(self, country_id: int = None, product_name: str = None, quality: int = None) -> dict:
- raw_short_names = dict(frm="foodRaw", wrm="weaponRaw", hrm="houseRaw", arm="airplaneRaw")
- q1_industries = ["aircraft"] + list(raw_short_names.values())
- if product_name:
- if product_name not in self.available_industries and product_name not in raw_short_names:
- self.write_log(f"Industry '{product_name}' not implemented")
- raise ErepublikException(f"Industry '{product_name}' not implemented")
- elif product_name in raw_short_names:
- quality = 1
- product_name = raw_short_names[product_name]
- product_name = [product_name]
- elif quality:
- raise ErepublikException("Quality without product not allowed")
-
- item_data = dict(price=999999., country=0, amount=0, offer_id=0, citizen_id=0)
-
- items = {"food": dict(q1=item_data.copy(), q2=item_data.copy(), q3=item_data.copy(), q4=item_data.copy(),
- q5=item_data.copy(), q6=item_data.copy(), q7=item_data.copy()),
- "weapon": dict(q1=item_data.copy(), q2=item_data.copy(), q3=item_data.copy(), q4=item_data.copy(),
- q5=item_data.copy(), q6=item_data.copy(), q7=item_data.copy()),
- "house": dict(q1=item_data.copy(), q2=item_data.copy(), q3=item_data.copy(), q4=item_data.copy(),
- q5=item_data.copy()), "aircraft": 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())}
-
- if country_id:
- countries = [country_id]
- else:
- countries = self.get_travel_countries()
-
- start_dt = self.now
- iterable = [countries, product_name or items, [quality] if quality else range(1, 8)]
- for country, industry, q in product(*iterable):
- if (q > 1 and industry in q1_industries) or (q > 5 and industry == "house"):
- continue
-
- r = self._post_economy_marketplace(country, self.available_industries[industry], q).json()
- obj = items[industry][f"q{q}"]
- if not r.get("error", False):
- for offer in r["offers"]:
- if obj["price"] > float(offer["priceWithTaxes"]):
- obj["price"] = float(offer["priceWithTaxes"])
- obj["country"] = int(offer["country_id"])
- obj["amount"] = int(offer["amount"])
- obj["offer_id"] = int(offer["id"])
- obj["citizen_id"] = int(offer["citizen_id"])
- elif obj["price"] == float(offer["priceWithTaxes"]) and obj["amount"] < int(offer["amount"]):
- obj["country"] = int(offer["country_id"])
- obj["amount"] = int(offer["amount"])
- obj["offer_id"] = int(offer["id"])
- self.write_log(f"Scraped market in {self.now - start_dt}!")
-
- if quality:
- ret = items[product_name[0]]["q%i" % quality]
- elif product_name:
- if product_name[0] in raw_short_names.values():
- ret = items[product_name[0]]["q1"]
- else:
- ret = items[product_name[0]]
- else:
- ret = items
- return ret
-
- def buy_food(self):
- hp_per_quality = {"q1": 2, "q2": 4, "q3": 6, "q4": 8, "q5": 10, "q6": 12, "q7": 20}
- hp_needed = 48 * self.energy.interval * 10 - self.food["total"]
- local_offers = self.get_market_offers(country_id=self.details.current_country, product_name="food")
-
- cheapest_q, cheapest = sorted(local_offers.items(), key=lambda v: v[1]["price"] / hp_per_quality[v[0]])[0]
-
- if cheapest["amount"] * hp_per_quality[cheapest_q] < hp_needed:
- amount = cheapest["amount"]
- else:
- amount = hp_needed // hp_per_quality[cheapest_q]
-
- if amount * cheapest["price"] < self.details.cc:
- data = dict(offer=cheapest["offer_id"], amount=amount, price=cheapest["price"],
- cost=amount * cheapest["price"], quality=cheapest_q, energy=amount * hp_per_quality[cheapest_q])
- self.reporter.report_action("BUY_FOOD", json_val=data)
- self.buy_from_market(cheapest["offer_id"], amount)
- self.update_inventory()
- else:
- s = f"Don't have enough money! Needed: {amount * cheapest['price']}cc, Have: {self.details.cc}cc"
- self.write_log(s)
- self.reporter.report_action("BUY_FOOD", value=s)
-
- def get_monetary_offers(self, currency: int = 62) -> List[Dict[str, Union[int, float]]]:
- if currency not in [1, 62]:
- currency = 62
- resp = self._post_economy_exchange_retrieve(False, 0, currency).json()
- ret = []
- 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'",
- resp["buy_mode"], re.M | re.I | re.S)
-
- for offer_id, price, amount in offers:
- ret.append({"offer_id": int(offer_id), "price": float(price), "amount": float(amount)})
-
- return sorted(ret, key=lambda o: (o["price"], -o["amount"]))
-
- def buy_monetary_market_offer(self, offer: int, amount: float, currency: int) -> bool:
- response = self._post_economy_exchange_purchase(amount, currency, offer)
- self.details.cc = float(response.json().get("ecash").get("value"))
- self.details.gold = float(response.json().get("gold").get("value"))
- self.reporter.report_action("BUY_GOLD", json_val=response.json(),
- value=f"New amount {self.details.cc}cc, {self.details.gold}g")
- return not response.json().get("error", False)
+ return utils.calculate_hit(0, rang, True, elite, ne, 0, 20 if weapon else 0)
def activate_dmg_booster(self):
if self.config.boosters:
@@ -1518,53 +1833,6 @@ class Citizen(CitizenAPI):
def activate_pp_booster(self, battle_id: int) -> Response:
return self._post_military_fight_activate_booster(battle_id, 1, 180, "prestige_points")
- def donate_money(self, citizen_id: int = 1620414, amount: float = 0.0, currency: int = 62) -> bool:
- """ currency: gold = 62, cc = 1 """
- resp = self._post_economy_donate_money_action(citizen_id, amount, currency)
- r = re.search('You do not have enough money in your account to make this donation', resp.text)
- return not bool(r)
-
- def donate_items(self, citizen_id: int = 1620414, amount: int = 0, industry_id: int = 1, quality: int = 1) -> int:
- if amount < 1:
- return 0
- ind = {v: k for k, v in self.available_industries.items()}
- self.write_log(f"Donate: {amount:4d}q{quality} {ind[industry_id]} to {citizen_id}")
- response = self._post_economy_donate_items_action(citizen_id, amount, industry_id, quality)
- if re.search(rf"Successfully transferred {amount} item\(s\) to", response.text):
- return amount
- else:
- if re.search(r"You do not have enough items in your inventory to make this donation", response.text):
- return 0
- available = re.search(rf"Cannot transfer the items because the user has only (\d+) free slots in (his|her) "
- rf"storage.", response.text).group(1)
- return self.donate_items(citizen_id, int(available), industry_id, quality)
-
- def candidate_for_congress(self, presentation: str = "") -> Response:
- return self._post_candidate_for_congress(presentation)
-
- def candidate_for_party_presidency(self) -> Response:
- return self._get_candidate_party(self.politics.party_slug)
-
- def accept_money_donations(self):
- for notification in self.parse_notifications():
- don_id = re.search(r"erepublik.functions.acceptRejectDonation\(\"accept\", (\d+)\)", notification)
- if don_id:
- self._get_main_money_donation_accept(int(don_id.group(1)))
- self.sleep(5)
-
- def reject_money_donations(self) -> int:
- r = self._get_main_notifications_ajax_system()
- count = 0
- donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text)
- while donation_ids:
- for don_id in donation_ids:
- self._get_main_money_donation_reject(int(don_id))
- count += 1
- self.sleep(5)
- r = self._get_main_notifications_ajax_system()
- donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text)
- return count
-
def _rw_choose_side(self, battle_id: int, side_id: int) -> Response:
return self._post_main_battlefield_travel(side_id, battle_id)
@@ -1653,262 +1921,58 @@ class Citizen(CitizenAPI):
return count if count > 0 else 0
- @property
- def next_reachable_energy(self) -> int:
- # Return pps for furthest __reachable__ +1 energy else 0
- max_pp = 0
- for pp_milestone in self.details.next_pp:
- pp_milestone = int(pp_milestone)
- if self.details.pp + self.energy.food_fights > pp_milestone: # if reachable set max pp
- max_pp = pp_milestone
- else: # rest are only bigger no need
- break
- return max_pp - self.details.pp if max_pp else 0
+ def get_battle_round_data(self, battle_id: int, round_id: int, division: int = None) -> dict:
+ battle = self.all_battles.get(battle_id)
+ if not battle:
+ return {}
- @property
- def next_wc_start(self) -> datetime:
- 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), timedelta(days=days))
+ data = dict(zoneId=round_id, round=round_id, division=division, leftPage=1, rightPage=1, type="damage")
- @property
- def time_till_week_change(self) -> timedelta:
- return self.next_wc_start - self.now
+ r = self._post_military_battle_console(battle_id, "battleStatistics", 1, **data)
+ return {battle.invader.id: r.json().get(str(battle.invader.id)).get("fighterData"),
+ battle.defender.id: r.json().get(str(battle.defender.id)).get("fighterData")}
- @property
- def time_till_full_ff(self) -> timedelta:
- energy = self.energy.recoverable + self.energy.recovered
- if energy >= self.energy.limit * 2:
- return timedelta(0)
- minutes_needed = round((self.energy.limit * 2 - energy) / self.energy.interval) * 6
- return (self.energy.reference_time - self.now) + timedelta(minutes=minutes_needed)
+ def schedule_attack(self, war_id: int, region_id: int, region_name: str, at_time: datetime):
+ if at_time:
+ self.sleep(utils.get_sleep_seconds(at_time))
+ self.get_csrf_token()
+ self.launch_attack(war_id, region_id, region_name)
- @property
- def max_time_till_full_ff(self) -> timedelta:
- """
- Max required time for 0 to full energy (0/0 -> limit/limit) (last interval rounded up)
- :return:
- """
- return timedelta(minutes=round((self.energy.limit * 2 / self.energy.interval) + 0.49) * 6)
+ def get_active_wars(self, country_id: int = None) -> List[int]:
+ r = self._get_country_military(utils.COUNTRY_LINK.get(country_id or self.details.citizenship))
+ all_war_ids = re.findall(r'//www\.erepublik\.com/en/wars/show/(\d+)"', r.text)
+ return [int(wid) for wid in all_war_ids]
- @property
- def is_levelup_close(self) -> bool:
- """
- If Energy limit * 2 >= xp till levelup * 10
- :return: bool
- """
- return self.energy.limit * 2 >= self.details.xp_till_level_up * 10
+ 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'', 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.strptime(battle.get('result').get('end'), "%Y-%m-%d %H:%M:%S"))
- @property
- def is_levelup_reachable(self) -> bool:
- """
- If Energy limit >= xp till levelup * 10
- :return: bool
- """
- return self.energy.limit >= self.details.xp_till_level_up * 10
+ def launch_attack(self, war_id: int, region_id: int, region_name: str):
+ self._post_wars_attack_region(war_id, region_id, region_name)
+ self.telegram.send_message(f"Battle for *{region_name}* queued")
- @property
- def should_do_levelup(self) -> bool:
- """
- If Energy limit >= xp till levelup * 10
- :return: bool
- """
- return (self.energy.recovered >= self.details.xp_till_level_up * 10 and # can reach next level
- self.energy.recoverable + 2 * self.energy.interval >= self.energy.limit) # can do max amount of dmg
+ def travel_to_battle(self, battle_id: int, allowed_countries: List[int]) -> bool:
+ data = self.get_travel_regions(battle_id=battle_id)
- def get_article_comments(self, article_id: int = 2645676, page_id: int = 1) -> Response:
- return self._post_main_article_comments(article_id, page_id)
-
- def comment_article(self, article_id: int = 2645676, msg: str = None) -> Response:
- if msg is None:
- msg = self.eday
- r = self.get_article_comments(article_id, 2)
- r = self.get_article_comments(article_id, r.json()["pages"])
- comments = r.json()["comments"]
- if not comments[max(comments.keys())]["isMyComment"]:
- r = self.write_article_comment(msg, article_id)
- return r
-
- def write_article_comment(self, message: str, article_id: int, parent_id: int = None) -> Response:
- return self._post_main_article_comments_create(message, article_id, parent_id)
-
- def publish_article(self, title: str, content: str, kind: int) -> Response:
- kinds = {1: "First steps in eRepublik", 2: "Battle orders", 3: "Warfare analysis",
- 4: "Political debates and analysis", 5: "Financial business",
- 6: "Social interactions and entertainment"}
- if kind in kinds:
- return self._post_main_write_article(title, content, self.details.citizenship, kind)
- else:
- raise ErepublikException("Article kind must be one of:\n{}\n'{}' is not supported".format(
- "\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), kind
- ))
-
- def get_my_market_offers(self) -> List[Dict[str, Union[int, float, str]]]:
- ret = []
- for offer in self._get_economy_my_market_offers().json():
- line = offer.copy()
- line.pop('icon', None)
- ret.append(line)
- return ret
-
- def post_market_offer(self, industry: int, quality: int, amount: int, price: float) -> Response:
- if industry not in self.available_industries.values():
- self.write_log(f"Trying to sell unsupported industry {industry}")
-
- data = {
- "country": self.details.citizenship,
- "industry": industry,
- "quality": quality,
- "amount": amount,
- "price": price,
- "buy": False,
- }
- ret = self._post_economy_marketplace_actions(**data)
- self.reporter.report_action("SELL_PRODUCT", ret.json())
- return ret
-
- def buy_from_market(self, offer: int, amount: int) -> dict:
- ret = self._post_economy_marketplace_actions(amount, True, offer=offer)
- json_ret = ret.json()
- if json_ret.get('error'):
- return json_ret
- else:
- self.details.cc = ret.json()['currency']
- self.details.gold = ret.json()['gold']
- r_json = ret.json()
- r_json.pop("offerUpdate", None)
- self.reporter.report_action("BUY_PRODUCT", ret.json())
- return json_ret
-
- def assign_factory_to_holding(self, factory_id: int, holding_id: int) -> Response:
- """
- 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)
-
- def upgrade_factory(self, factory_id: int, level: int) -> Response:
- return self._post_economy_upgrade_company(factory_id, level, self.details.pin)
-
- def create_factory(self, industry_id: int, building_type: int = 1) -> Response:
- """
- param industry_ids: FRM={q1:7, q2:8, q3:9, q4:10, q5:11} WRM={q1:12, q2:13, q3:14, q4:15, q5:16}
- HRM={q1:18, q2:19, q3:20, q4:21, q5:22} ARM={q1:24, q2:25, q3:26, q4:27, q5:28}
- Factories={Food:1, Weapons:2, House:4, Aircraft:23} <- Building_type 1
-
- 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)
-
- 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)
-
- @property
- def available_industries(self) -> Dict[str, int]:
- """
- Returns currently available industries as dict(name: id)
- :return: dict
- """
- return {"food": 1, "weapon": 2, "house": 4, "aircraft": 23,
- "foodRaw": 7, "weaponRaw": 12, "houseRaw": 17, "airplaneRaw": 24}
-
- @property
- def factories(self) -> Dict[int, str]:
- """Returns factory industries as dict(id: name)
- :return: Factory id:name dict
- ":rtype: Dict[int, str]
- """
- 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:
- """Returns industry id
-
- :type industry_name: str
- :return: int
- """
- return self.available_industries.get(industry_name, 0)
-
- def get_industry_name(self, industry_id: int) -> str:
- """Returns industry name from industry ID
-
- :type industry_id: int
- :return: industry name
- :rtype: str
- """
- for iname, iid in self.available_industries.items():
- if iid == industry_id:
- return iname
- return ""
-
- def buy_tg_contract(self) -> Response:
- ret = self._post_main_buy_gold_items('gold', "TrainingContract2", 1)
- self.reporter.report_action("BUY_TG_CONTRACT", ret.json())
- return ret
-
- def resign(self) -> bool:
- self.update_job_info()
- if self.r.json().get("isEmployee"):
- self.reporter.report_action("RESIGN", self.r.json())
- self._post_economy_resign()
- return True
+ 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 find_new_job(self) -> Response:
- r = self._get_economy_job_market_json(self.details.current_country)
- jobs = r.json().get("jobs")
- data = dict(citizen=0, salary=10)
- for posting in jobs:
- salary = posting.get("salary")
- limit = posting.get("salaryLimit", 0)
- userid = posting.get("citizen").get("id")
-
- if (not limit or salary * 3 < limit) and salary > data["salary"]:
- data.update({"citizen": userid, "salary": salary})
- self.reporter.report_action("APPLYING_FOR_JOB", jobs, str(data['citizen']))
- return self._post_economy_job_market_apply(**data)
-
- def add_friend(self, player_id: int) -> Response:
- resp = self._get_main_citizen_hovercard(player_id)
- rjson = resp.json()
- if not any([rjson["isBanned"], rjson["isDead"], rjson["isFriend"], rjson["isOrg"], rjson["isSelf"]]):
- r = self._post_main_citizen_add_remove_friend(int(player_id), True)
- self.write_log(f"{rjson['name']:<64} (id:{player_id:>11}) added as friend")
- return r
- return resp
-
- def get_country_parties(self, country_id: int = None) -> dict:
- if country_id is None:
- country_id = self.details.citizenship
- r = self._get_main_rankings_parties(country_id)
- ret = {}
- for name, id_ in re.findall(r'', r.text):
- ret.update({int(id_): name})
- return ret
-
- def _get_main_party_members(self, party_id: int) -> Dict[int, str]:
- ret = {}
- r = super()._get_main_party_members(party_id)
- for id_, name in re.findall(r'', r.text):
- ret.update({id_: name})
- return ret
-
def get_country_mus(self, country_id: int) -> Dict[int, str]:
ret = {}
r = self._get_main_leaderboards_damage_rankings(country_id)
@@ -1932,12 +1996,56 @@ class Citizen(CitizenAPI):
ret.update({user["citizenId"]: user["name"]})
return ret
+
+class CitizenAnniversary(BaseCitizen):
+ def collect_anniversary_reward(self) -> Response:
+ return self._post_main_collect_anniversary_reward()
+
+ def get_anniversary_quest_data(self):
+ return self._get_anniversary_quest_data().json()
+
+ def start_unlocking_map_quest_node(self, node_id: int):
+ return self._post_map_rewards_unlock(node_id)
+
+ def collect_map_quest_node(self, node_id: int):
+ return self._post_map_rewards_claim(node_id)
+
+ def speedup_map_quest_node(self, node_id: int):
+ node = self.get_anniversary_quest_data().get('cities', {}).get(str(node_id), {})
+ return self._post_map_rewards_speedup(node_id, node.get("skipCost", 0))
+
+
+class CitizenSocial(BaseCitizen):
+ def send_mail_to_owner(self):
+ if not self.details.citizen_id == 1620414:
+ self.send_mail("Started", "time {}".format(self.now.strftime("%Y-%m-%d %H-%M-%S")), [1620414, ])
+ self.sleep(1)
+ msg_id = re.search(r"", self.r.text).group(1)
+ self._post_delete_message([msg_id])
+
def send_mail(self, subject: str, msg: str, ids: List[int] = None):
if ids is None:
ids = [1620414, ]
for player_id in ids:
self._post_main_messages_compose(subject, msg, [player_id])
+ def write_on_country_wall(self, message: str) -> bool:
+ self._get_main()
+ post_to_wall_as = re.findall(r'id="post_to_country_as".*?.*',
+ self.r.text, re.S | re.M)
+ 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()
+
+ def add_friend(self, player_id: int) -> Response:
+ resp = self._get_main_citizen_hovercard(player_id)
+ rjson = resp.json()
+ if not any([rjson["isBanned"], rjson["isDead"], rjson["isFriend"], rjson["isOrg"], rjson["isSelf"]]):
+ r = self._post_main_citizen_add_remove_friend(int(player_id), True)
+ self.write_log(f"{rjson['name']:<64} (id:{player_id:>11}) added as friend")
+ return r
+ return resp
+
def add_every_player_as_friend(self):
cities = []
cities_dict = {}
@@ -1959,48 +2067,170 @@ class Citizen(CitizenAPI):
for resident in resp["widgets"]["residents"]["residents"]:
self.add_friend(resident["citizenId"])
- def schedule_attack(self, war_id: int, region_id: int, region_name: str, at_time: datetime):
- if at_time:
- self.sleep(get_sleep_seconds(at_time))
+
+class Citizen(CitizenMilitary, CitizenAnniversary, CitizenEconomy, CitizenSocial, CitizenPolitics):
+ debug: bool = False
+
+ def __init__(self, email: str = "", password: str = "", auto_login: bool = True):
+ super().__init__()
+ self.__last_full_update = utils.good_timedelta(self.now, - timedelta(minutes=5))
+ self.commit_id = utils.COMMIT_ID
+ self.config = Config()
+ self.config.email = email
+ self.config.password = password
+ self.energy = Energy()
+ self.details = Details()
+ self.politics = Politics()
+ self.my_companies = MyCompanies()
+ self.set_debug(True)
+ self.reporter = Reporter()
+ self.stop_threads = Event()
+ self.telegram = TelegramBot(stop_event=self.stop_threads)
+ if auto_login:
+ self.login()
+
+ def config_setup(self, **kwargs):
+ self.config.reset()
+ for key, value in kwargs.items():
+ if hasattr(self.config, key):
+ setattr(self.config, key, value)
+ else:
+ self.write_log(f"Unknown config parameter! ({key}={value})")
+
+ def login(self):
self.get_csrf_token()
- self.launch_attack(war_id, region_id, region_name)
- def get_active_wars(self, country_id: int = None) -> List[int]:
- r = self._get_country_military(COUNTRY_LINK.get(country_id or self.details.citizenship))
- all_war_ids = re.findall(r'//www\.erepublik\.com/en/wars/show/(\d+)"', r.text)
- return [int(wid) for wid in all_war_ids]
+ self.update_citizen_info()
+ self.reporter.do_init(self.name, self.config.email, self.details.citizen_id)
+ if self.config.telegram:
+ self.telegram.do_init(self.config.telegram_chat_id or 620981703,
+ self.config.telegram_token or "864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o",
+ "" if self.config.telegram_chat_id or self.config.telegram_token else self.name)
+ self.telegram.send_message(f"*Started* {utils.now():%F %T}")
- def get_war_status(self, war_id: int) -> Dict[str, Union[bool, Dict[int, str]]]:
- 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({int(reg[0]): reg[1]})
- elif re.search(r'Join', html):
- battle_id = re.search(r'Join', html).group(1)
- ret.update(can_attack=False, battle_id=int(battle_id))
- elif re.search(r'This war is no longer active.', html):
- ret.update(can_attack=False, ended=True)
+ self.__last_full_update = utils.good_timedelta(self.now, - timedelta(minutes=5))
+
+ def update_citizen_info(self, html: str = None):
+ """
+ Gets main page and updates most information about player
+ """
+ if html is None:
+ self._get_main()
+ return
+ super().update_citizen_info(html)
+
+ if self.promos.get("trainingContract"):
+ if not self.tg_contract:
+ self.train()
+ if not self.tg_contract["free_train"] and self.tg_contract.get("active", False):
+ if self.details.gold >= 54:
+ self.buy_tg_contract()
+ else:
+ self.write_log(f"Training ground contract active but "
+ f"don't have enough gold ({self.details.gold}g {self.details.cc}cc)")
+ if self.energy.is_energy_full and self.config.telegram:
+ self.telegram.report_full_energy(self.energy.available, self.energy.limit, self.energy.interval)
+
+ def check_for_notification_medals(self):
+ notifications = self._get_main_citizen_daily_assistant().json()
+ data: Dict[Tuple[str, Union[float, str]], Dict[str, Union[int, str, float]]] = {}
+ for medal in notifications.get('notifications', []):
+ if medal.get('details', {}).get('type') == "citizenAchievement":
+ params: dict = medal.get('details', {}).get('achievement')
+ about: str = medal.get('details').get('description')
+ title: str = medal.get('title')
+
+ award_id: int = medal.get('id')
+ if award_id and title:
+ self._post_main_wall_post_automatic(title.lower(), award_id)
+
+ if params.get('ccValue'):
+ reward = params.get('ccValue')
+ currency = "Currency"
+ elif params.get('goldValue'):
+ reward = params.get('goldValue')
+ currency = "Gold"
+ else:
+ reward = params.get('energyValue')
+ currency = "Energy"
+
+ if (title, reward) not in data:
+ data[(title, reward)] = {'about': about, 'kind': title, 'reward': reward, "count": 1,
+ "currency": currency, "params": params}
+ else:
+ data[(title, reward)]['count'] += 1
+ self._post_main_global_alerts_close(medal.get('id'))
+ if data:
+ msgs = ["{count} x {kind},"
+ " totaling {} {currency}".format(d["count"] * d["reward"], **d) for d in data.values()]
+
+ msgs = "\n".join(msgs)
+ self.telegram.report_medal(msgs)
+ self.write_log(f"Found awards:\n{msgs}")
+ for info in data.values():
+ self.reporter.report_action("NEW_MEDAL", info)
+
+ def set_debug(self, debug: bool):
+ self.debug = debug
+ self._req.debug = debug
+
+ def set_pin(self, pin: int):
+ self.details.pin = pin
+
+ def update_all(self, force_update=False):
+ # Do full update max every 5 min
+ if utils.good_timedelta(self.__last_full_update, timedelta(minutes=5)) > self.now and not force_update:
+ return
else:
- ret.update(can_attack=False)
- return ret
+ self.__last_full_update = self.now
+ self.update_citizen_info()
+ self.update_war_info()
+ self.update_inventory()
+ self.update_companies()
+ self.update_money()
+ self.update_weekly_challenge()
+ self.send_state_update()
+ self.check_for_notification_medals()
- 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'', html).group(1))
- console = self._post_military_battle_console(last_battle_id, 'warList', 1).json()
- battle = console.get('list')[0]
- return localize_dt(datetime.strptime(battle.get('result').get('end'), "%Y-%m-%d %H:%M:%S"))
+ def update_weekly_challenge(self):
+ data = self._get_main_weekly_challenge_data().json()
+ self.details.pp = data.get("player", {}).get("prestigePoints", 0)
+ self.details.next_pp.clear()
+ for reward in data.get("rewards", {}).get("normal", {}):
+ status = reward.get("status", "")
+ if status == "rewarded":
+ continue
+ elif status == "completed":
+ self._post_main_weekly_challenge_reward(reward.get("id", 0))
+ elif reward.get("icon", "") == "energy_booster":
+ pps = re.search(r"Reach (\d+) Prestige Points to unlock the following reward: \+1 Energy",
+ reward.get("tooltip", ""))
+ if pps:
+ self.details.next_pp.append(int(pps.group(1)))
- def launch_attack(self, war_id: int, region_id: int, region_name: str):
- self._post_wars_attack_region(war_id, region_id, region_name)
- self.telegram.send_message(f"Battle for *{region_name}* queued")
+ def parse_notifications(self, page: int = 1) -> list:
+ community = self._get_main_notifications_ajax_community(page).json()
+ system = self._get_main_notifications_ajax_system(page).json()
+ return community['alertsList'] + system['alertsList']
+
+ def delete_notifications(self):
+ response = self._get_main_notifications_ajax_community().json()
+ while response['totalAlerts']:
+ self._post_main_messages_alert([_['id'] for _ in response['alertList']])
+ response = self._get_main_notifications_ajax_community().json()
+
+ response = self._get_main_notifications_ajax_system().json()
+ while response['totalAlerts']:
+ self._post_main_messages_alert([_['id'] for _ in response['alertList']])
+ response = self._get_main_notifications_ajax_system().json()
+
+ def collect_weekly_reward(self):
+ self.update_weekly_challenge()
+
+ def collect_daily_task(self):
+ self.update_citizen_info()
+ if self.details.daily_task_done and not self.details.daily_task_reward:
+ self._post_main_daily_task_reward()
def state_update_repeater(self):
try:
@@ -2008,10 +2238,10 @@ class Citizen(CitizenAPI):
if start_time.minute <= 30:
start_time = start_time.replace(minute=30)
else:
- start_time = good_timedelta(start_time.replace(minute=0), timedelta(hours=1))
+ start_time = utils.good_timedelta(start_time.replace(minute=0), timedelta(hours=1))
while not self.stop_threads.is_set():
self.update_citizen_info()
- start_time = good_timedelta(start_time, timedelta(minutes=30))
+ start_time = utils.good_timedelta(start_time, timedelta(minutes=30))
self.send_state_update()
self.send_inventory_update()
sleep_seconds = (start_time - self.now).total_seconds()
@@ -2028,230 +2258,3 @@ class Citizen(CitizenAPI):
def send_inventory_update(self):
to_report = self.update_inventory()
self.reporter.report_action("INVENTORY", json_val=to_report)
-
- def check_house_durability(self) -> Dict[int, datetime]:
- ret = {}
- inv = self.update_inventory()
- for house_quality, active_house in inv['items']['active'].get('house', {}).items():
- till = good_timedelta(self.now, timedelta(seconds=active_house['time_left']))
- ret.update({house_quality: till})
- return ret
-
- def buy_and_activate_house(self, q: int) -> Dict[int, datetime]:
- inventory = self.update_inventory()
- ok_to_activate = False
- if not inventory['items']['final'].get('house', {}).get(q, {}):
- offers = []
- countries = [self.details.citizenship, ]
- if self.details.current_country != self.details.citizenship:
- countries.append(self.details.current_country)
- for country in countries:
- offers += [self.get_market_offers(country, "house", q)]
- global_cheapest = self.get_market_offers(product_name="house", quality=q)
- cheapest_offer = sorted(offers, key=lambda o: o["price"])[0]
- region = self.get_country_travel_region(global_cheapest['country'])
- if global_cheapest['price'] + 200 < cheapest_offer['price'] and region:
- self._travel(global_cheapest['country'], region)
- buy = self.buy_from_market(global_cheapest['offer_id'], 1)
- else:
- buy = self.buy_from_market(cheapest_offer['offer_id'], 1)
- if buy["error"]:
- msg = f"Unable to buy q{q} house! \n{buy['message']}"
- self.write_log(msg)
- else:
- ok_to_activate = True
- else:
- ok_to_activate = True
- if ok_to_activate:
- self.activate_house(q)
- return self.check_house_durability()
-
- def renew_houses(self, forced: bool = False) -> Dict[int, datetime]:
- """
- Renew all houses which endtime is in next 48h
- :param forced: if true - renew all houses
- :return:
- """
- house_durability = self.check_house_durability()
- for q, active_till in house_durability.items():
- if good_timedelta(active_till, - timedelta(hours=48)) <= self.now or forced:
- house_durability = self.buy_and_activate_house(q)
- self.travel_to_residence()
- return house_durability
-
- def activate_house(self, quality: int) -> datetime:
- active_until = self.now
- r = self._post_economy_activate_house(quality)
- if r.json().get("status") and not r.json().get("error"):
- house = r.json()["inventoryItems"]["activeEnhancements"]["items"]["4_%i_active" % quality]
- active_until = good_timedelta(active_until, timedelta(seconds=house["active"]["time_left"]))
- return active_until
-
- def collect_anniversary_reward(self) -> Response:
- return self._post_main_collect_anniversary_reward()
-
- def get_battle_round_data(self, battle_id: int, round_id: int, division: int = None) -> dict:
- battle = self.all_battles.get(battle_id)
- if not battle:
- return {}
-
- data = dict(zoneId=round_id, round=round_id, division=division, leftPage=1, rightPage=1, type="damage")
-
- r = self._post_military_battle_console(battle_id, "battleStatistics", 1, **data)
- return {battle.invader.id: r.json().get(str(battle.invader.id)).get("fighterData"),
- battle.defender.id: r.json().get(str(battle.defender.id)).get("fighterData")}
-
- def contribute_cc_to_country(self, amount=0., country_id: int = 71) -> bool:
- self.update_money()
- amount = int(amount)
- if self.details.cc < amount or amount < 20:
- return False
- data = dict(country=country_id, action='currency', value=amount)
- self.reporter.report_action("CONTRIBUTE_CC", data, str(amount))
- r = self._post_main_country_donate(**data)
- return r.json().get('status') or not r.json().get('error')
-
- def contribute_food_to_country(self, amount: int = 0, quality: int = 1, country_id: int = 71) -> bool:
- self.update_inventory()
- amount = amount // 1
- if self.food["q" + str(quality)] < amount or amount < 10:
- return False
- data = dict(country=country_id, action='food', value=amount, quality=quality)
- self.reporter.report_action("CONTRIBUTE_FOOD", data, FOOD_ENERGY[quality] * amount)
- r = self._post_main_country_donate(**data)
- return r.json().get('status') or not r.json().get('error')
-
- def contribute_gold_to_country(self, amount: int, country_id: int = 71) -> bool:
- self.update_money()
-
- if self.details.cc < amount:
- return False
- data = dict(country=country_id, action='gold', value=amount)
- self.reporter.report_action("CONTRIBUTE_GOLD", data, str(amount))
- r = self._post_main_country_donate(**data)
- return r.json().get('status') or not r.json().get('error')
-
- def write_on_country_wall(self, message: str) -> bool:
- self._get_main()
- post_to_wall_as = re.findall(r'id="post_to_country_as".*?.*',
- self.r.text, re.S | re.M)
- 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()
-
- def report_error(self, msg: str = "", is_warning: bool = 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]]]:
- return {}
- # battle = self.all_battles.get(battle_id)
- # round_id = battle.zone_id
- # division = self.division if round_id % 4 else 11
- #
- # resp = self._post_military_battle_console(battle_id, 'battleStatistics', round_id, division).json()
- # resp.pop('rounds', None)
- # ret = dict()
- # for country_id, data in resp.items():
- # ret.update({int(country_id): []})
- # for place in sorted(data.get("fighterData", {}).values(), key=lambda _: -_['raw_value']):
- # ret[int(country_id)].append((place['citizenId'], place['raw_value']))
- # return ret
-
- def to_json(self, indent: bool = False) -> str:
- return json.dumps(self.__dict__, cls=MyJSONEncoder, indent=4 if indent else None, sort_keys=True)
-
- def get_game_token_offers(self):
- r = self._post_economy_game_tokens_market('retrieve').json()
- return {v.get('id'): dict(amount=v.get('amount'), price=v.get('price')) for v in r.get("topOffers")}
-
- def fetch_organisation_account(self, org_id: int):
- r = self._get_economy_citizen_accounts(org_id)
- table = re.search(r'()', r.text, re.I | re.M | re.S)
- if table:
- account = re.findall(r'>\s*(\d+.\d+)\s*', table.group(1))
- if account:
- return {"gold": account[0], "cc": account[1], 'ok': True}
-
- return {"gold": 0, "cc": 0, 'ok': False}
-
- def get_ground_hit_dmg_value(self, rang: int = None, strength: float = None, elite: bool = None, ne: bool = False,
- booster_50: bool = False, booster_100: bool = False, tp: bool = True) -> float:
- if not rang or strength or elite is None:
- r = self._get_main_citizen_profile_json(self.details.citizen_id).json()
- if not rang:
- rang = r['military']['militaryData']['ground']['rankNumber']
- if not strength:
- strength = r['military']['militaryData']['ground']['strength']
- if elite is None:
- elite = r['citizenAttributes']['level'] > 100
- if ne:
- tp = True
-
- return calculate_hit(strength, rang, tp, elite, ne, 50 if booster_50 else 100 if booster_100 else 0)
-
- def get_air_hit_dmg_value(self, rang: int = None, elite: bool = None, ne: bool = False,
- weapon: bool = False) -> float:
- if not rang or elite is None:
- r = self._get_main_citizen_profile_json(self.details.citizen_id).json()
- if not rang:
- rang = r['military']['militaryData']['air']['rankNumber']
- if elite is None:
- elite = r['citizenAttributes']['level'] > 100
-
- return calculate_hit(0, rang, True, elite, ne, 0, 20 if weapon else 0)
-
- def endorse_article(self, article_id: int, amount: int) -> bool:
- if amount in (5, 50, 100):
- resp = self._post_main_donate_article(article_id, amount).json()
- return not bool(resp.get('error'))
- else:
- return False
-
- def vote_article(self, article_id: int) -> bool:
- resp = self._post_main_vote_article(article_id).json()
- return not bool(resp.get('error'))
-
- def get_anniversary_quest_data(self):
- return self._get_anniversary_quest_data().json()
-
- def start_unlocking_map_quest_node(self, node_id: int):
- return self._post_map_rewards_unlock(node_id)
-
- def collect_map_quest_node(self, node_id: int):
- return self._post_map_rewards_claim(node_id)
-
- def speedup_map_quest_node(self, node_id: int):
- node = self.get_anniversary_quest_data().get('cities', {}).get(str(node_id), {})
- 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.get(battle_id)
- available_weapons = self._get_military_show_weapons(battle_id).json()
- while not isinstance(available_weapons, list):
- available_weapons = self._get_military_show_weapons(battle_id).json()
- weapon_quality = -1
- weapon_damage = 0
- if not battle.is_air:
- for weapon in available_weapons:
- try:
- if weapon['weaponQuantity'] > 30 and weapon['damage'] > weapon_damage:
- weapon_quality = int(weapon['weaponId'])
- except ValueError:
- pass
- return self.change_weapon(battle_id, weapon_quality)
-
- def change_weapon(self, battle_id: int, weapon_quality: int) -> int:
- battle = self.all_battles.get(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')
-
- def get_battle_for_war(self, war_id: int) -> Optional[Battle]:
- self.update_war_info()
- war_info = self.get_war_status(war_id)
- return self.all_battles.get(war_info.get("battle_id"), None)