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: