erepublik-ebot/ebot/player.py
2022-07-04 10:44:45 +03:00

770 lines
33 KiB
Python

""" eBot
Copyright (C) 2022 Eriks K
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import re
import signal
import sys
import threading
from collections import defaultdict
from datetime import datetime, timedelta
from random import randint
from typing import Any, Callable, Dict, List, NoReturn, Optional, Set, Tuple, Union
import httpx
import sentry_sdk
from dateutil.parser import parse
from erepublik import Citizen, utils
from erepublik.classes import Battle
from httpx import Response
from ebot import __version__
from ebot.helpers import BotStop, Task, Tasks
from ebot.utils import _norm, jsonify
VERSION = __version__
EVERSION = utils.VERSION
class BasePlayer(Citizen):
do_captcha: bool = False
_last_donation_time: datetime
__last_update_times: Dict[str, Dict[str, Union[datetime, Any]]]
def __init__(self, email, password, auto_login: bool = False):
super().__init__(email, password, auto_login)
self._last_donation_time = self.now
self.__last_update_times = defaultdict(lambda: dict(time=_norm(self.now - timedelta(days=1)), value=None))
def setup_citizen(self, config):
for _key in [
"work",
"train",
"ot",
"wam",
"employees",
"auto_sell_all",
"auto_buy_raw",
"force_wam",
"telegram",
"spin_wheel_of_fortune",
"interactive",
]:
if hasattr(self.config, _key):
setattr(self.config, _key, bool(config.get(_key, False)))
else:
raise AttributeError(f'{self.__class__.__name__}.config has no attribute "{_key}"!')
self.config.auto_sell = config.get("auto_sell", [])
self.config.telegram_token = config.get("telegram_token") or None
self.config.telegram_chat_id = config.get("telegram_chat_id", "")
self.reporter.allowed = not config.get("reporting_is_not_allowed")
self.set_debug(config.get("debug", False))
self.set_interactive(config.get("interactive", False))
if config.get("proxy"):
proxy_data = config.get("proxy")
if hasattr(self, f"set_{proxy_data.get('kind')}_proxy"):
args = (
proxy_data.get("host"),
proxy_data.get("port"),
proxy_data.get("username"),
proxy_data.get("password"),
)
getattr(self, f"set_{proxy_data.get('kind')}_proxy")(*args)
else:
self.logger.error(f"Can't set '{proxy_data.get('kind')}' proxy")
raise LookupError(f"Can't set '{proxy_data.get('kind')}' proxy")
def setup_tasks(self, config: Dict[str, Any]):
pass
def signal_quit(self, sig_num, frame):
if frame:
pass
self.set_locks()
self.sleep(2)
self.logger.debug(f"Received: {sig_num} (Quit)")
if not self.restricted_ip:
self.dump_instance()
raise BotStop(0)
def signal_reload(self, sig_num, frame):
if frame:
pass
self.logger.debug(f"Received: {sig_num} (Update)")
self.logger.warning("Forcing full update...")
self.update_all(True)
self.send_state_update()
self.send_inventory_update()
self.write_log("Forced full update completed!")
def signal_config_reload(self, sig_num, frame):
if frame:
pass
self.logger.debug(f"Received: {sig_num} (Update)")
self.logger.warning("Reloading config file...")
self.load_config()
self.update_all(True)
self.send_state_update()
self.send_inventory_update()
self.write_log("Configs reloaded successfully!")
def report_error(self, msg: str = "", sentry: bool = True, extra: Dict[str, Any] = None):
if sentry:
sentry_sdk.capture_exception()
if extra is None:
extra = {}
extra.update({"ebot_version": VERSION, "erep_version": EVERSION})
super().report_error(msg, extra)
def _report_action(self, action: str, msg: str, **kwargs):
super()._report_action(action, msg, **jsonify(kwargs))
def report_action(self, action: str, msg: str, **kwargs):
self._report_action(action, msg, **jsonify(kwargs))
def _get_main_citizen_profile_json(self, c_id: int):
if _norm(self.__last_update_times[f"q7hit__{c_id}"]["time"] + timedelta(hours=4)) <= self.now:
self.__last_update_times[f"q7hit__{c_id}"]["value"] = super()._get_main_citizen_profile_json(c_id)
self.__last_update_times[f"q7hit__{c_id}"]["time"] = self.now
return self.__last_update_times[f"q7hit__{c_id}"]["value"]
@classmethod
def load_from_dump(cls, dump_name: str = ""):
filename = dump_name if dump_name else f"{cls.__name__}__dump.json"
with open(filename) as f:
data = utils.json.load(f)
instance = cls(data["config"]["email"], "", False)
for cookie in data["cookies"]:
instance._req.cookies.set(cookie["name"], cookie["value"], cookie["domain"], cookie["path"])
if data.get("user_agent"):
instance._req.headers.update({"User-Agent": data["user_agent"]})
instance.load_config("config.json")
instance._resume_session()
instance.login()
return instance
def load_config(self, config_path: str = "config.json"):
if os.path.isfile(config_path):
with open(config_path) as f:
try:
configs = utils.json.load(f)
except utils.json.JSONDecodeError:
self.logger.error(f"Config file '{config_path}' must be a JSON file!")
return
self.setup_citizen(configs)
else:
self.logger.warning(f"Config file '{config_path}' not found!")
def donate_money(self, *args, **kwargs) -> bool:
self.sleep((self._last_donation_time + timedelta(seconds=5) - self.now).total_seconds()) # noqa
ret = super().donate_money(*args, **kwargs)
self._last_donation_time = self.now
return ret
def candidate_for_party_presidency(self) -> Optional[Response]:
if self.politics.is_party_member:
return super().candidate_for_party_presidency()
else:
self.report_action("POLITIC_CONGRESS", "Unable to apply for party president elections - not a party member")
return None
def candidate_for_congress(self, presentation: str = "") -> Optional[Response]:
if self.politics.is_party_member:
return super().candidate_for_congress(presentation)
else:
self.report_action("POLITIC_CONGRESS", "Unable to apply for congress elections - not a party member")
return None
@property
def as_dict(self):
d = super().as_dict
d.update(_last_donation_time=self._last_donation_time, __last_update_times=self.__last_update_times)
return d
def do_captcha_challenge(self, *args, **kwargs):
if self.do_captcha:
return super().do_captcha_challenge(*args, **kwargs)
return False
def solve_captcha(self, src: str) -> List[Dict[str, int]]:
r = self.reporter._req.post(f"{self.reporter.url}/captcha/api", data={"password": "CaptchaDevAPI", "src": src})
r = r.json()
if r["status"]:
return [dict(x=icon["x"] + randint(-4, 4), y=icon["y"] + randint(-4, 4)) for icon in r["result"]["result"]]
return []
class PlayerTasks(BasePlayer):
tasks: Tasks
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tasks = Tasks()
def setup_tasks(self, config):
super().setup_tasks(config)
self._setup_player_tasks(config)
@property
def as_dict(self):
ret = super().as_dict
ret["tasks"] = self.tasks.as_dict
return ret
def do_tasks(self) -> NoReturn:
if self.restricted_ip:
self.tasks.pop("wam")
self.tasks.pop("employ")
self.tasks.sort()
for task in list(self.tasks):
if task.time <= self.now:
task_function_name: str = f"task_{task.name}"
try:
task_function: Callable = getattr(self, task_function_name)
except (AttributeError, TypeError):
self.report_error(f"Task '{task.name}' has no task's function '{task_function_name}' defined!")
raise NotImplementedError(f"Task '{task_function_name}' not implemented!")
if callable(task_function):
self.write_log(f"Doing task: {task.human_name}")
task_function()
else:
raise TypeError(f"Task '{task_function_name}' is not a function!")
def get_next_task(self) -> Task:
self.tasks.sort()
return self.tasks[0]
def enable_task(self, title: str, time: datetime = None, priority: bool = False):
next_time = time if time is not None else self.now
self.tasks[title] = (next_time, priority)
def task_work(self):
self.update_citizen_info()
self.work()
if self.config.ot:
self.task_ot()
self.collect_daily_task()
self.tasks["work"] = _norm(self.now.replace(hour=0, minute=0, second=0) + timedelta(days=1)), True
def task_ot(self):
self.update_inventory()
if self.ot_points >= 24 and self.now >= self.my_companies.next_ot_time:
self.work_ot()
self.update_inventory()
try:
hours = ((24 - self.ot_points) // len(self.inventory.active["House"])) + 1
except ZeroDivisionError:
hours = 24
self.tasks["ot"] = _norm(self.now + timedelta(hours=hours if hours > 1 else 1)), True
def task_train(self):
self.update_citizen_info()
self.train()
self.collect_daily_task()
next_time = _norm(self.now.replace(hour=0, minute=0, second=0) + timedelta(days=1))
self.tasks["train"] = next_time, True
def task_wam(self):
if self.restricted_ip:
self.tasks.pop("wam")
return
has_more_to_wam = self.work_as_manager()
if has_more_to_wam:
next_time = _norm(self.tasks["wam"].time + timedelta(hours=1))
else:
next_time = _norm(
self.now.replace(hour=self.tasks.get_default("wam_hour", 14), minute=0, second=0, microsecond=0)
+ timedelta(days=1)
)
self.tasks["wam"] = next_time
def task_employees(self):
if self.restricted_ip:
self.tasks.pop("employees")
return
if self.employ_employees():
_h = self.tasks.get_default("employ_hour", 8)
next_time = _norm(self.now.replace(hour=_h, minute=0, second=0) + timedelta(days=1))
else:
next_time = _norm(self.tasks["employees"].time + timedelta(minutes=30))
self.tasks["employees"] = next_time
def task_buy_gold(self):
for offer in self.get_monetary_offers():
if offer["amount"] >= 10 and self.details.cc >= 20 * offer["price"]:
# TODO: check allowed amount to buy
self.buy_monetary_market_offer(offer=offer["offer_id"], amount=10, currency=62)
break
self.tasks["buy_gold"] = _norm(self.tasks["buy_gold"].time + timedelta(days=1)), True
def task_congress(self):
now = self.now
if 1 <= now.day < 16:
next_time = _norm(now.replace(day=16))
elif 16 <= now.day < 24:
self.candidate_for_congress()
if not now.month == 12:
next_time = _norm(now.replace(month=now.month + 1, day=16))
else:
next_time = _norm(now.replace(year=now.year + 1, month=1, day=16))
else:
if not now.month == 12:
next_time = _norm(now.replace(month=now.month + 1, day=16))
else:
next_time = _norm(now.replace(year=now.year + 1, month=1, day=16))
self.tasks["congress"] = _norm(next_time.replace(hour=1, minute=30, second=0, microsecond=0))
def task_party_president(self):
next_time = self.now
if next_time.day == 15:
pass
elif next_time.day < 15: # now.day ∈[1;15)
self.candidate_for_party_presidency()
else: # now.day ∈ (15;31]
self.candidate_for_party_presidency()
next_time = next_time.replace(day=16)
if self.now.month == 12:
next_time = next_time.replace(year=self.now.year + 1, month=1)
else:
next_time = next_time.replace(month=self.now.month + 1)
self.tasks["party_president"] = _norm(next_time.replace(day=16, hour=0, minute=1, second=0, microsecond=0))
def task_contribute_cc(self):
if not self.now.weekday():
self.update_money()
cc = (self.details.cc // self.tasks.get_default("contribute_cc")) * self.tasks.get_default("contribute_cc")
self.contribute_cc_to_country(cc, self.details.citizenship)
next_time = _norm((self.now + timedelta(days=7 - self.now.weekday())).replace(hour=2, minute=0, second=0))
self.tasks["contribute_cc"] = next_time
def task_renew_houses(self):
end_times = self.renew_houses()
if end_times:
self.tasks["renew_houses"] = _norm(min(end_times.values()) - timedelta(hours=24))
else:
self.logger.warning("No houses found!")
for q in range(1, self.tasks.get_default("renew_houses", 0) + 1):
self.write_log(f"Buying and activating q{q}...")
if not self.buy_and_activate_house(q):
break
end_times = self.check_house_durability()
if end_times:
next_time = _norm(min(end_times.values()) - timedelta(hours=24))
else:
next_time = _norm(self.now + timedelta(hours=6))
self.tasks["renew_houses"] = next_time
def task_spin_wheel_of_fortune(self):
percents_to_lose = self.tasks.get_default("spin_wheel_of_fortune")
self.update_citizen_info()
if self.wheel_of_fortune and percents_to_lose:
if isinstance(percents_to_lose, bool):
percents_to_lose = 10
else:
percents_to_lose = int(percents_to_lose)
max_spend = ((self.details.cc * (percents_to_lose / 100)) // 100) * 100
max_cost = 0
next_cost = max_cost + 100 if max_cost else 500
current_sum = 0
while current_sum < max_spend:
max_cost = next_cost
next_cost += 100
current_sum += max_cost
self.spin_wheel_of_fortune(max_cost)
self.tasks["spin_wheel_of_fortune"] = _norm(self.now.replace(hour=0, minute=0, second=0) + timedelta(days=1))
def _setup_player_tasks(self, config: Dict[str, Any]):
now = self.now
self.tasks.set_default("random_sleep", config.get("random_sleep"))
for _action in ["work", "train", "ot"]: # "fight", "epic_hunt"
if getattr(self.config, _action):
self.enable_task(_action, now, priority=True)
for _action in ["congress", "party_president"]:
if config.get(_action, True):
self.enable_task(_action)
for _action in ["wam", "employees"]:
if getattr(self.config, _action) and not self.restricted_ip:
_hour = 14
if not isinstance(config[_action], bool):
try:
_hour = abs(int(config[_action])) % 24
except ValueError:
_hour = 14
self.tasks.set_default(f"{_action}_hour", _hour)
self.enable_task(_action, _norm(now.replace(hour=_hour, minute=0, second=0, microsecond=0)))
if config.get("buy_gold"):
self.enable_task("buy_gold", _norm(now.replace(hour=23, minute=57, second=0, microsecond=0)), True)
if config.get("contribute_cc", 0):
self.tasks.set_default("contribute_cc", int(config.get("contribute_cc", 0)))
self.enable_task("contribute_cc", _norm(now.replace(hour=2, minute=0, second=0)))
if config.get("renew_houses"):
self.tasks.set_default("renew_houses", config.get("renew_houses"))
self.enable_task("renew_houses")
if config.get("spin_wheel_of_fortune", False):
spin_value = config.get("spin_wheel_of_fortune")
percent = int(10 if isinstance(spin_value, bool) else spin_value)
self.tasks.set_default("spin_wheel_of_fortune", percent)
self.enable_task("spin_wheel_of_fortune")
class PlayerBackgroundTasks(BasePlayer):
_preset_pay_table: Dict[int, int]
_default_pay_table: Dict[int, int]
_hits_to_pay: Dict[int, int]
_bg_task_queue: List[threading.Thread]
def __init__(self, *args, **kwargs):
self._preset_pay_table = {}
self._default_pay_table = {1: 400, 2: 500, 3: 600}
self._bg_task_queue = []
self._hits_to_pay = defaultdict(int)
super().__init__(*args, **kwargs)
def setup_tasks(self, config):
super().setup_tasks(config)
self._setup_player_bg_tasks(config)
def signal_quit(self, sig_num, frame):
self.set_locks()
if self._hits_to_pay:
self.write_log("Paying for hits, before quitting")
for player_id, amount in self._hits_to_pay.items():
if amount:
self.donate_money(player_id, amount, 1)
self._hits_to_pay[player_id] = 0
for thread in self._bg_task_queue:
if thread.is_alive():
self.write_log(f"Waiting on thread '{thread.name:^32}'")
thread.join()
return super().signal_quit(sig_num, frame)
def signal_reload(self, sig_num, frame):
for thread in self._bg_task_queue:
self.write_log(f"Thread '{thread.name:^32}', alive {thread.is_alive()}")
if self._hits_to_pay:
self.write_log("Clearing hit table...")
for player_id, amount in self._hits_to_pay.items():
if amount:
self.donate_money(player_id, amount, 1)
self._hits_to_pay[player_id] = 0
else:
self._hits_to_pay.clear()
return super().signal_reload(sig_num, frame)
def thread_name(self, name: str) -> str:
return f"{self.name}__{threading.active_count() - 1}__{name}"
def background_task_start_battles(self, wars):
def update_war_info():
rj = self._get_military_campaigns_json_list().json()
if rj.get("countries"):
if rj.get("battles"):
return {battle_data.get("id"): Battle(battle_data) for battle_data in rj.get("battles").values()}
return {}
wars: Dict[str, Dict[str, Union[bool, List[int]]]]
finished_war_ids: Set[int] = {*[]}
war_data: Dict[str, Dict[str, Union[bool, List[int]]]] = wars
war_ids: Set[int] = {int(war_id) for war_id in war_data.keys()}
next_attack_time = self.now
next_attack_time = _norm(next_attack_time.replace(minute=next_attack_time.minute // 5 * 5, second=0))
while not self.stop_threads.is_set():
try:
attacked = False
all_battles = update_war_info()
running_wars = {b.war_id for b in all_battles.values()}
if all_battles:
for war_id in war_ids - finished_war_ids - running_wars:
war = war_data[str(war_id)]
war_regions = set(war.get("regions"))
auto_attack = war.get("auto_attack")
try:
start_h = war.get("attack_time")[0]
end_h = war.get("attack_time")[1]
except (TypeError, IndexError):
start_h = 21
end_h = 3
if start_h <= end_h:
time_check = start_h <= self.now.hour < end_h
else:
time_check = start_h <= self.now.hour or self.now.hour < end_h
status = self.get_war_status(war_id)
if status.get("ended", False):
finished_war_ids.add(war_id)
continue
if not status.get("can_attack"):
continue
if auto_attack or time_check:
for reg in war_regions:
if attacked:
break
if reg in status.get("regions", {}).keys():
reg_name = status.get("regions", {}).get(reg)
self._post_wars_attack_region(war_id, reg, reg_name)
self._report_action("MILITARY_QUEUE_ATTACK", f"Battle for *{reg_name}* queued")
break
if attacked:
break
war_ids -= finished_war_ids
if attacked:
next_attack_time = _norm(next_attack_time + timedelta(hours=1))
else:
next_attack_time = _norm(next_attack_time + timedelta(minutes=5))
self.stop_threads.wait(utils.get_sleep_seconds(next_attack_time))
except Exception as e:
self.report_error(f"Task error: start_battles {e.args}")
def background_task_report_game_token_price(self):
while not self.stop_threads.is_set():
try:
_next_time = _norm(self.now.replace(second=0, microsecond=0))
if _next_time.minute < 50:
_next_time = _norm(_next_time.replace(minute=(_next_time.minute // 10 + 1) * 10))
else:
_next_time = _norm(_next_time.replace(minute=0) + timedelta(hours=1))
offers = self.get_game_token_offers()
httpx.post("https://erep.lv/market/gametoken/add/", json=dict(all_offers=True, **offers))
self.stop_threads.wait(utils.get_sleep_seconds(_next_time))
except Exception as e:
self.report_error(f"Task error: game_token {e}")
def background_task_report_org_accounts(self, org_list: List[int] = None):
if org_list is None:
org_list = []
watch_organisations = org_list
while not self.stop_threads.is_set():
try:
next_report_time = _norm(self.now.replace(minute=0, second=0, microsecond=0))
while not self.stop_threads.is_set():
for organisation_id in watch_organisations:
to_report = {"organisation_id": organisation_id}
account = self.fetch_organisation_account(organisation_id)
if account.get("ok"):
to_report.update(**account)
httpx.post("https://erep.lv/org/account/add/", json=to_report)
next_report_time = _norm(next_report_time + timedelta(hours=1))
to_sleep = (next_report_time - self.now).total_seconds()
self.stop_threads.wait(to_sleep if to_sleep > 0 else 0)
except Exception as e:
self.report_error(f"Task error: report_org_accounts {e}")
def background_task_birthday_journey(self):
priority = [
"anniversary_decoration",
"permanent_energy_house",
"energy_house",
"energy_booster",
"house_pool_bonus",
"ground_vehicle_blueprint",
"air_vehicle_blueprint",
"air_damage_booster_50",
"air_damage_booster_20",
"gold",
"energy_bars",
"winter_treat",
"trump_bomb",
"big_bomb",
"stinger_missile",
"damage_booster_100",
"overtime_points",
"speed_booster_2",
"air_deploy_rank_points_booster_20",
"air_deploy_rank_points_booster_10",
"air_deploy_influence_booster_20",
"air_deploy_influence_booster_10",
"deploy_size_booster_200",
"deploy_size_booster_100",
"ground_deploy_rank_points_booster_20",
"ground_deploy_rank_points_booster_10",
"ground_deploy_influence_booster_20",
"ground_deploy_influence_booster_10",
"vehicle_discharge_document",
"fuel",
]
try:
node_map: Dict[int, Set[int]] = defaultdict(set)
while not self.stop_threads.is_set():
quest_data = self.get_anniversary_quest_data()
# can_claim_extra = quest_data.get("rewards", {}).get("canCollectExtra")
if not node_map:
for pair in quest_data["neighbors"]:
node_map[pair["nodeId"]].add(pair["neighborId"])
node_map[pair["neighborId"]].add(pair["nodeId"])
visited_nodes: List[Tuple[int, datetime, str]] = []
if not quest_data.get("status", {}).get("progress"):
self.start_unlocking_map_quest_node(30219)
quest_data = self.get_anniversary_quest_data()
if quest_data["status"]["progress"]:
for node in quest_data["status"]["progress"].values():
visited_nodes.append((node["nodeId"], parse(node["finishUnlockTime"]), node["nodeStatus"]))
visited_nodes.sort(key=lambda o: o[1])
visited_nodes.reverse()
current_queue = []
available_nodes = {*[]}
for node_id, finish_datetime, status in visited_nodes:
if status in ("claimed", "unclaimed"):
available_nodes.update(node_map[node_id])
# elif status == "unclaimed":
# available_nodes.update(node_map[node_id])
# collection_data = self.collect_map_quest_node(node_id).json()
# if collection_data.get("error"):
# self.logger.error(collection_data.get("error", {}).get("message"))
# claimable_node = quest_data.get("cities").get(str(node_id))
# if quest_data.get("status").get("inventory").get("miles") < claimable_node.get("miles"):
# self.travel_to_region(713)
# self.travel_to_residence()
# else:
# if can_claim_extra:
# self.collect_map_quest_node(node_id, True)
elif status == "unlocking":
current_queue.append(finish_datetime)
if not current_queue:
available_nodes -= {nid for nid, _, __ in visited_nodes}
if available_nodes:
for kind in priority:
for node_id in available_nodes:
if kind in [r["type"] for r in quest_data["cities"][str(node_id)]["rewards"]]:
self.start_unlocking_map_quest_node(node_id)
quest_data = self.get_anniversary_quest_data()
current_queue.append(
parse(quest_data["status"]["progress"][str(node_id)]["finishUnlockTime"])
)
if current_queue:
break
else:
continue
break
current_queue.sort()
if current_queue:
next_finish = min(current_queue)
else:
self.report_error("Task Birthday Journey: Empty queue!", sentry=False)
return
to_sleep = utils.get_sleep_seconds(next_finish)
self.write_log(
f"Task: 'Birthday Journey' sleeping until (eRep): {next_finish} (sleeping for {to_sleep}s)"
)
self.stop_threads.wait(to_sleep if to_sleep > 0 else 0)
except Exception as e:
self.report_error(f"Task error: Birthday Journey {e}")
def _setup_player_bg_tasks(self, config: Dict[str, Any]):
if config.get("state_update_repeater", True):
t = threading.Thread(target=self.state_update_repeater, name=self.thread_name("state_update_repeater"))
t.start()
self._bg_task_queue.append(t)
for bg_task in ["start_battles", "game_tokens", "org_fetcher", "birthday_journey"]: # "clear_bhs",
if config.get(bg_task):
try:
_task_fn = getattr(self, f"background_task_{bg_task}")
except AttributeError:
self.report_error(f"Task '{bg_task}' has no task's function 'background_task_{bg_task}' defined!")
raise NotImplementedError(f"Task 'background_task_{bg_task}' not implemented!")
args = (config.get(bg_task),) if bg_task in ["start_battles", "clear_bhs", "org_fetcher"] else tuple()
t = threading.Thread(target=_task_fn, args=args, name=self.thread_name(bg_task))
t.start()
self._bg_task_queue.append(t)
@property
def as_dict(self):
ret = super().as_dict
ret.update(hits_payment_aggregation=self._hits_to_pay)
return ret
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(rf'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'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" '
r'class="join" title="Join"><span>Join</span></a>',
html,
):
battle_id = re.search(
r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" '
r'class="join" title="Join"><span>Join</span></a>',
html,
).group(1)
ret.update(can_attack=False, battle_id=int(battle_id))
elif re.search(r"This war is no longer active.", html):
ret.update(can_attack=False, ended=True)
else:
ret.update(can_attack=False)
return ret
class Player(PlayerTasks, PlayerBackgroundTasks, BasePlayer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
signal.signal(signal.SIGINT, self.signal_quit)
signal.signal(signal.SIGTERM, self.signal_quit)
signal.signal(signal.SIGABRT, self.signal_quit)
if sys.platform.startswith("win"):
signal.signal(signal.SIGBREAK, self.signal_quit)
else:
signal.signal(signal.SIGUSR1, self.signal_reload)
signal.signal(signal.SIGUSR2, self.signal_config_reload)
signal.signal(signal.SIGHUP, self.signal_quit)
pid = os.getpid()
for action, _signal in [("UPDATE", "SIGUSR1"), ("RELOAD", "SIGUSR2"), ("QUIT", "SIGINT")]:
self.logger.debug(
f"To {action:^6}, run: 'kill -{getattr(signal, _signal):<2} {pid}' or 'kill -{_signal:<7} {pid}'"
)
def setup_tasks(self, config: Dict[str, Any]):
super().setup_tasks(config)
def check_house_durability(self) -> Dict[int, datetime]:
ret = {}
for q, dt in super().check_house_durability().items():
if q <= self.tasks.get_default("renew_houses", 0):
ret.update({q: dt})
return ret
def to_json(self, indent: bool = False) -> str:
return utils.json_dumps(self.as_dict, indent=False, sort_keys=True)
def setup_citizen(self, config):
super().setup_citizen(config)
self.tasks.set_default("cheap_medals", config.get("cheap_medals", False))