From 3dac8c5e74a60eda6086f98bbfd26c4b7456208e Mon Sep 17 00:00:00 2001 From: Eriks K Date: Tue, 25 May 2021 09:43:47 +0300 Subject: [PATCH] New eating --- erepublik/access_points.py | 8 ++- erepublik/citizen.py | 137 ++++++++++++------------------------- erepublik/classes.py | 44 ++++++++---- 3 files changed, 80 insertions(+), 109 deletions(-) diff --git a/erepublik/access_points.py b/erepublik/access_points.py index d8e23ba..dd07486 100644 --- a/erepublik/access_points.py +++ b/erepublik/access_points.py @@ -144,7 +144,7 @@ class CitizenBaseAPI: self._req.proxies = dict(http=url, https=url) def set_http_proxy(self, host: str, port: int, username: str = None, password: str = None): - url = f'http://{username}:{password}@{host}:{port}' if username and password else f'socks5://{host}:{port}' + url = f'http://{username}:{password}@{host}:{port}' if username and password else f'http://{host}:{port}' self._req.proxies = dict(http=url) def _get_main_session_captcha(self) -> Response: @@ -154,7 +154,8 @@ class CitizenBaseAPI: return self.get(f'{self.url}/main/sessionUnlockPopup') def _post_main_session_get_challenge(self, captcha_id: int, image_id: str = "") -> Response: - env = dict(l=['tets', ], s=[], c=["l_chathwe", "l_chatroom"], m=0) + c = [cookie.name for cookie in self._req.cookies if not cookie.has_nonstandard_attr('HttpOnly')] + env = dict(l=['tets'], s=[], c=c, m=0) data = dict(_token=self.token, captchaId=captcha_id, env=utils.b64json(env)) if image_id: data.update(imageId=image_id, isRefresh=True) @@ -177,6 +178,9 @@ class CitizenBaseAPI: return self.post(f'{self.url}/main/sessionUnlock', data=data, json=data, headers={'X-Requested-With': 'XMLHttpRequest', 'Referer': 'https://www.erepublik.com/en'}) + def _post_energy_refill_get_inventory(self): + return self.post(f'{self.url}/economy/energyRefill-getInventory', data={'_token': self.token}) + class ErepublikAnniversaryAPI(CitizenBaseAPI): def _post_main_collect_anniversary_reward(self) -> Response: diff --git a/erepublik/citizen.py b/erepublik/citizen.py index f76f699..d24fff5 100644 --- a/erepublik/citizen.py +++ b/erepublik/citizen.py @@ -7,7 +7,7 @@ from decimal import Decimal from itertools import product from threading import Event from time import sleep -from typing import Any, Dict, List, NoReturn, Optional, Set, Tuple, Union +from typing import Any, Dict, List, NoReturn, Optional, Set, Tuple, Union, TypedDict from requests import HTTPError, RequestException, Response @@ -78,7 +78,8 @@ class BaseCitizen(access_points.CitizenAPI): (after 15min time of inactivity opening page in eRepublik.com redirects to home page), by explicitly requesting homepage. """ - resp = self._req.get(self.url) + # Idiots have fucked up their session manager - after logging in You might be redirected to public homepage instead of authenticated + resp = self._req.get(self.url if self.logged_in else f"{self.url}/economy/myCompanies") self.r = resp if self._errors_in_response(resp): self.get_csrf_token() @@ -210,9 +211,9 @@ class BaseCitizen(access_points.CitizenAPI): 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.energy.limit = citizen.get('energyPoolLimit', 0) + self.energy.energy = citizen.get('energy', 0) + # self.energy.set_reference_time(utils.good_timedelta(self.now, timedelta(seconds=int(next_recovery[1]) * 60 + int(next_recovery[2])))) self.details.current_region = citizen.get('regionLocationId', 0) self.details.current_country = constants.COUNTRIES.get( @@ -279,8 +280,19 @@ class BaseCitizen(access_points.CitizenAPI): def refresh_captcha_image(self, captcha_id: int, image_id: str): return self._post_main_session_get_challenge(captcha_id, image_id).json() - def solve_captcha(self, src: str) -> List[Dict[str, int]]: - return [] + def solve_captcha(self, src: str) -> Optional[List[Dict[str, int]]]: + class _API_RESULT(TypedDict): + x: int + y: int + + class _API_RETURN(TypedDict): + status: bool + message: str + result: Optional[List[_API_RESULT]] + + solve_data: _API_RETURN = self.post('https://api.erep.lv/captcha/api', data=dict(citizen_id=self.details.citizen_id, src=src, key='CaptchaDevAPI')).json() + if solve_data['status']: + return solve_data.get('result') @property def inventory(self) -> classes.Inventory: @@ -636,7 +648,7 @@ class BaseCitizen(access_points.CitizenAPI): @property def health_info(self): - ret = f"{self.energy.recovered}/{self.energy.limit} + {self.energy.recoverable}, " \ + ret = f"{self.energy.energy}/{self.energy.limit}, " \ f"{self.energy.interval}hp/6m. {self.details.xp_till_level_up}xp until level up" return ret @@ -663,10 +675,9 @@ class BaseCitizen(access_points.CitizenAPI): @property def time_till_full_ff(self) -> timedelta: - energy = self.energy.recoverable + self.energy.recovered - if energy >= self.energy.limit * 2: + if self.energy.energy >= self.energy.limit: return timedelta(0) - minutes_needed = round((self.energy.limit * 2 - energy) / self.energy.interval) * 6 + minutes_needed = round((self.energy.limit - self.energy.energy) / self.energy.interval) * 6 return (self.energy.reference_time - self.now) + timedelta(minutes=minutes_needed) @property @@ -675,7 +686,7 @@ class BaseCitizen(access_points.CitizenAPI): 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) + return timedelta(minutes=round((self.energy.limit / self.energy.interval) + 0.49) * 6) @property def is_levelup_close(self) -> bool: @@ -699,8 +710,8 @@ class BaseCitizen(access_points.CitizenAPI): If Energy limit >= xp till levelup * 10 :return: bool """ - can_reach_next_level = self.energy.recovered >= self.details.xp_till_level_up * 10 - can_do_max_amount_of_dmg = self.energy.recoverable + 2 * self.energy.interval >= self.energy.limit + can_reach_next_level = self.energy.energy >= self.details.xp_till_level_up * 10 + can_do_max_amount_of_dmg = self.energy.energy + 2 * self.energy.interval >= self.energy.limit return can_reach_next_level and can_do_max_amount_of_dmg @property @@ -774,28 +785,6 @@ class BaseCitizen(access_points.CitizenAPI): ret.update({id_: name}) return ret - def _eat(self, colour: str = 'blue') -> Response: - response = self._post_eat(colour) - r_json = response.json() - 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 11 < int(q) < 17: - self.eb_small -= amount - elif q == '17': - self.eb_triple -= amount - 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 - def _login(self): # MUST BE CALLED TROUGH self.get_csrf_token() r = self._post_login(self.config.email, self.config.password) @@ -827,9 +816,12 @@ class BaseCitizen(access_points.CitizenAPI): pass if response.status_code >= 400: self.r = response + if 'Attention Required! | Cloudflare' in response.text: + self.write_warning('Cloudflare blocked request! You must inject valid CloudFlare cookie!') + raise classes.CloudFlareSessionError(f"CloudFlare session error!", response) if response.text == 'Please verify your account.' or response.text == 'Forbidden': self.do_captcha_challenge() - return True + raise classes.CaptchaSessionError(f"CaptchaSession has expired!", response) elif response.status_code >= 500: if self.restricted_ip: self._req.cookies.clear() @@ -1931,17 +1923,14 @@ class CitizenMilitary(CitizenTravel): error_count = total_damage = total_hits = 0 ok_to_fight = True while ok_to_fight and error_count < 10 and count > 0: - while all((count > 0, error_count < 10, self.energy.recovered >= 50)): + while all((count > 0, error_count < 10, self.energy.energy >= 50)): hits, error, damage = self._shoot(battle, division, side) count -= hits total_hits += hits total_damage += damage error_count += error else: - self._eat('blue') - if count > 0 and self.energy.recovered < 50 and use_ebs: - self._eat('orange') - if self.energy.recovered < 50 or error_count >= 10 or count <= 0: + if self.energy.energy < 50 or error_count >= 10 or count <= 0: self.write_log(f"Hits: {total_hits:>4} | Damage: {total_damage}") ok_to_fight = False if total_damage: @@ -2009,14 +1998,14 @@ class CitizenMilitary(CitizenTravel): hits = r_json['user']['earnedXp'] # InfantryKit player # The almost always safe way (breaks on levelup hit) - elif self.energy.recovered >= r_json['details']['wellness']: # Haven't reached levelup - hits = (self.energy.recovered - r_json['details']['wellness']) // 10 + elif self.energy.energy >= r_json['details']['wellness']: # Haven't reached levelup + hits = (self.energy.energy - r_json['details']['wellness']) // 10 else: hits = r_json['hits'] if r_json['user']['epicBattle']: hits /= 1 + r_json['user']['epicBattle'] - self.energy.recovered = r_json['details']['wellness'] + self.energy.energy = r_json['details']['wellness'] self.details.xp = int(r_json['details']['points']) damage = r_json['user']['givenDamage'] * (1.1 if r_json['oldEnemy']['isNatural'] else 1) else: @@ -2179,7 +2168,7 @@ class CitizenMilitary(CitizenTravel): elif self.next_reachable_energy and self.config.next_energy: ret = True # 1h worth of energy - elif self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2: + elif self.energy.energy + self.energy.interval * 3 >= self.energy.limit: ret = True return ret @@ -2218,7 +2207,7 @@ class CitizenMilitary(CitizenTravel): msg = 'Continuing to fight in previous battle' # All-in (type = all-in and full ff) - elif self.config.all_in and self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2: + elif self.config.all_in and self.energy.energy + self.energy.interval * 3 >= self.energy.limit: count = self.energy.food_fights msg = "Fighting all-in. Doing %i hits" % count @@ -2228,7 +2217,7 @@ class CitizenMilitary(CitizenTravel): msg = "Fighting for +1 energy. Doing %i hits" % count # 1h worth of energy - elif self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2: + elif self.energy.energy + self.energy.interval * 3 >= self.energy.limit: count = self.energy.interval msg = "Fighting for 1h energy. Doing %i hits" % count force_fight = True @@ -2551,10 +2540,6 @@ class CitizenTasks(CitizenEconomy): d.update(tg_contract=self.tg_contract, ot_points=self.ot_points, next_ot_time=self.next_ot_time) return d - def eat(self): - """ Eat food """ - self._eat('blue') - def work(self): if self.energy.food_fights >= 1: response = self._post_economy_work('work') @@ -2571,12 +2556,10 @@ class CitizenTasks(CitizenEconomy): 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_warning(f"I don't have energy to work. Will sleep for {seconds}s") self.sleep(seconds) - self._eat('blue') self.work() def train(self): @@ -2600,13 +2583,11 @@ class CitizenTasks(CitizenEconomy): 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) sleep_seconds = utils.get_sleep_seconds(large) self.write_warning(f"I don't have energy to train. Will sleep for {sleep_seconds} seconds") self.sleep(sleep_seconds) - self._eat('blue') self.train() def work_ot(self): @@ -2624,13 +2605,11 @@ class CitizenTasks(CitizenEconomy): self.buy_food(120) 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) sleep_seconds = utils.get_sleep_seconds(large) self.write_warning(f"I don't have energy to work OT. Will sleep for {sleep_seconds}s") self.sleep(sleep_seconds) - self._eat('blue') self.work_ot() def resign_from_employer(self) -> bool: @@ -2694,14 +2673,6 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard, player.login() return player - def _eat(self, colour: str = 'blue') -> Response: - resp = super()._eat(colour) - if not any([resp.json().get('units_consumed').values()]): - if colour == 'orange' and resp.json().get('food_remaining'): - self.eat() - return self._eat(colour) - return resp - def config_setup(self, **kwargs): self.config.reset() for key, value in kwargs.items(): @@ -2720,9 +2691,10 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard, self.config.telegram_token, self.name) self.telegram.send_message(f"*Started* {utils.now():%F %T}") - self.init_logger() - self.update_all(True) + + if self.logged_in: + self.update_all(True) def update_citizen_info(self, html: str = None): """ @@ -2743,7 +2715,7 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard, self.write_warning('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) + self.telegram.report_full_energy(self.energy.energy, self.energy.limit, self.energy.interval) def check_for_notification_medals(self): notifications = self._get_main_citizen_daily_assistant().json() @@ -2880,7 +2852,7 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard, data = dict(xp=self.details.xp, cc=self.details.cc, gold=self.details.gold, pp=self.details.pp, inv_total=self.inventory.total, inv=self.inventory.used, hp_limit=self.energy.limit, - hp_interval=self.energy.interval, hp_available=self.energy.available, food=self.food['total'], ) + hp_interval=self.energy.interval, hp_available=self.energy.energy, food=self.food['total'], ) self.reporter.send_state_update(**data) def send_inventory_update(self): @@ -2889,31 +2861,6 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard, def send_my_companies_update(self): self.reporter.report_action('COMPANIES', json_val=self.my_companies.as_dict) - def eat(self): - """ - Try to eat food - """ - self._eat('blue') - if self.food['total'] > self.energy.interval: - if self.energy.limit - self.energy.recovered > self.energy.interval or not self.energy.recoverable % 2: - super().eat() - else: - self.logger.debug("I don't want to eat right now!") - else: - self.write_warning(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: - super().eat() - else: - self.write_warning('I failed to buy food') - - def eat_eb(self): - self.write_warning('Eating energy bar') - if self.energy.recoverable: - self._eat('blue') - self._eat('orange') - self.write_log(self.health_info) - def sell_produced_product(self, kind: str, quality: int = 1, amount: int = 0): if not amount: inv_resp = self._get_economy_inventory_items().json() diff --git a/erepublik/classes.py b/erepublik/classes.py index 990d8e0..9236f3c 100644 --- a/erepublik/classes.py +++ b/erepublik/classes.py @@ -1,6 +1,7 @@ import datetime import hashlib import threading +import warnings import weakref from decimal import Decimal from io import BytesIO @@ -27,6 +28,14 @@ class ErepublikNetworkException(ErepublikException): self.request = request +class CloudFlareSessionError(ErepublikNetworkException): + pass + + +class CaptchaSessionError(ErepublikNetworkException): + pass + + class Holding: id: int region: int @@ -414,26 +423,35 @@ class Config: class Energy: limit = 500 # energyToRecover interval = 10 # energyPerInterval - recoverable = 0 # energyFromFoodRemaining - recovered = 0 # energy + energy = 0 # energy _recovery_time = None def __init__(self): self._recovery_time = utils.now() def __repr__(self): - return f"{self.recovered:4}/{self.limit:4} + {self.recoverable:4}, {self.interval:3}hp/6min" + return f"{self.energy:4}/{self.limit:4}, {self.interval:3}hp/6min" + + @property + def recovered(self): + warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.energy', DeprecationWarning) + return self.energy + + @property + def recoverable(self): + warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.energy', DeprecationWarning) + return 0 def set_reference_time(self, recovery_time: datetime.datetime): self._recovery_time = recovery_time.replace(microsecond=0) @property def food_fights(self): - return self.available // 10 + return self.energy // 10 @property def reference_time(self): - if self.is_recovered_full or self._recovery_time < utils.now(): + if self.is_energy_full or self._recovery_time < utils.now(): ret = utils.now() else: ret = self._recovery_time @@ -441,26 +459,28 @@ class Energy: @property def is_recoverable_full(self): - return self.recoverable >= self.limit - 5 * self.interval + warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.is_energy_full', DeprecationWarning) + return self.is_energy_full @property def is_recovered_full(self): - return self.recovered >= self.limit - self.interval + warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.is_energy_full', DeprecationWarning) + return self.is_energy_full @property def is_energy_full(self): - return self.is_recoverable_full and self.is_recovered_full + return self.energy >= self.limit - self.interval @property def available(self): - return self.recovered + self.recoverable + warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.energy', DeprecationWarning) + return self.energy @property def as_dict(self) -> Dict[str, Union[int, datetime.datetime, bool]]: - return dict(limit=self.limit, interval=self.interval, recoverable=self.recoverable, recovered=self.recovered, + return dict(limit=self.limit, interval=self.interval, energy=self.energy, reference_time=self.reference_time, food_fights=self.food_fights, - is_recoverable_full=self.is_recoverable_full, is_recovered_full=self.is_recovered_full, - is_energy_full=self.is_energy_full, available=self.available) + is_energy_full=self.is_energy_full) class Details: