Compare commits

...

31 Commits

Author SHA1 Message Date
5a1f7801a2 Bump version: 0.25.1 → 0.25.1.1 2021-07-20 13:16:55 +03:00
17845f750c Bugfix 2021-07-20 13:14:13 +03:00
883af51197 Bump version: 0.25.0.4 → 0.25.1 2021-07-10 01:32:57 +03:00
814cb5ab87 Programmatic user agent spoofing 2021-07-10 01:29:31 +03:00
3c316bada3 Bump version: 0.25.0.3 → 0.25.0.4 2021-07-10 01:14:30 +03:00
ea03979943 Energy variable bugfix 2021-07-10 01:13:21 +03:00
9aae685b21 Bump version: 0.25.0.2 → 0.25.0.3 2021-07-10 00:47:51 +03:00
cae94d7aa8 requirement update 2021-07-10 00:47:40 +03:00
fae7b0fd37 Bump version: 0.25.0.1 → 0.25.0.2 2021-07-10 00:37:46 +03:00
b7771b4da2 Update 2021-07-10 00:36:22 +03:00
e3a10af101 Bump version: 0.25.0 → 0.25.0.1 2021-05-25 09:54:44 +03:00
33a5bcacf1 Backward compatability bugfix 2021-05-25 09:54:35 +03:00
342ca2e1cc Bump version: 0.24.2.4 → 0.25.0 2021-05-25 09:49:49 +03:00
580240a015 isort 2021-05-25 09:49:27 +03:00
1517103ba3 Remove deprecated eat task from examples 2021-05-25 09:48:08 +03:00
3dac8c5e74 New eating 2021-05-25 09:43:47 +03:00
8cf86fb9d3 Bump version: 0.24.2.3 → 0.24.2.4 2021-04-27 12:21:54 +03:00
cf927df6e6 bugfix 2021-04-27 12:21:50 +03:00
a6f5dbd05f Bump version: 0.24.2.2 → 0.24.2.3 2021-04-27 12:19:22 +03:00
967afa472f Rank.__str__ 2021-04-27 12:18:56 +03:00
a65568cd0c Bump version: 0.24.2.1 → 0.24.2.2 2021-04-27 11:05:57 +03:00
6e45334d99 Rank comparision 2021-04-27 11:05:51 +03:00
936a1010a6 Bump version: 0.24.2 → 0.24.2.1 2021-04-26 13:12:32 +03:00
acc528cb1d bugfix 2021-04-26 13:12:28 +03:00
614d273104 Bump version: 0.24.1 → 0.24.2 2021-04-23 13:46:51 +03:00
95966764e8 Updated rank constants 2021-04-23 13:45:58 +03:00
f52b078e6a Captcha challange solving simplification 2021-03-05 16:11:52 +02:00
3af27f6512 Cookie dump migration 2021-03-03 13:15:26 +02:00
6276242260 bugfixes 2021-03-03 13:13:19 +02:00
45623de97b Reporter update to queue messages if network error occures 2021-02-06 15:33:08 +02:00
25f932121c Code cleanup and JSONEncoder update to support dumping Logger instances 2021-02-06 15:32:30 +02:00
14 changed files with 347 additions and 337 deletions

View File

@ -21,6 +21,7 @@ insert_final_newline = false
indent_style = tab
[*.py]
max_line_length = 240
line_length=120
multi_line_output=0
balanced_wrapping=True

View File

@ -10,4 +10,4 @@ repos:
- id: check-added-large-files
default_language_version:
python: python3.7
python: python3.8

View File

@ -36,14 +36,6 @@ erepublik.constants module
:undoc-members:
:show-inheritance:
erepublik.types module
----------------------
.. automodule:: erepublik.types
:members:
:undoc-members:
:show-inheritance:
erepublik.utils module
----------------------
@ -52,6 +44,14 @@ erepublik.utils module
:undoc-members:
:show-inheritance:
erepublik.ws module
-------------------
.. automodule:: erepublik.ws
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------

View File

@ -4,9 +4,8 @@
__author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv'
__version__ = '0.24.1'
__version__ = '0.25.1.1'
from erepublik import classes, constants, utils
from erepublik.citizen import Citizen
__all__ = ["classes", "utils", "Citizen", 'constants']
__all__ = ['Citizen', '__version__']

View File

@ -49,7 +49,7 @@ class ErepublikFormatter(logging.Formatter):
default_fmt = "[%(asctime)s] %(levelname)s: %(msg)s"
def converter(self, timestamp: Union[int, float]) -> datetime.datetime:
return datetime.datetime.utcfromtimestamp(timestamp).astimezone(erep_tz)
return datetime.datetime.fromtimestamp(timestamp).astimezone(erep_tz)
def format(self, record: logging.LogRecord) -> str:
"""

View File

@ -3,11 +3,11 @@ import hashlib
import random
import time
from typing import Any, Dict, List, Mapping, Union
from requests_toolbelt.utils import dump
from requests import Response, Session
from requests_toolbelt.utils import dump
from . import constants, utils
from erepublik import constants, utils
__all__ = ['SlowRequests', 'CitizenAPI']
@ -15,27 +15,6 @@ __all__ = ['SlowRequests', 'CitizenAPI']
class SlowRequests(Session):
last_time: datetime.datetime
timeout: datetime.timedelta = datetime.timedelta(milliseconds=500)
_uas: List[str] = [
# Chrome
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
# FireFox
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0',
]
debug: bool = False
def __init__(self, proxies: Dict[str, str] = None, user_agent: str = None):
@ -43,7 +22,7 @@ class SlowRequests(Session):
if proxies:
self.proxies = proxies
if user_agent is None:
user_agent = random.choice(self._uas)
user_agent = random.choice(self.get_random_user_agent())
self.request_log_name = utils.get_file(utils.now().strftime("debug/requests_%Y-%m-%d.log"))
self.last_time = utils.now()
self.headers.update({'User-Agent': user_agent})
@ -115,6 +94,36 @@ class SlowRequests(Session):
data = dump.dump_all(response)
utils.write_file(f'debug/dumps/{fd_time}_{fd_name}{fd_extra}.{fd_ext}.dump', data.decode('utf8'))
@staticmethod
def get_random_user_agent() -> str:
windows_x64 = 'Windows NT 10.0; Win64; x64'
linux_x64 = 'X11; Linux x86_64'
android_11 = 'Android 11; Mobile'
android_10 = 'Android 10; Mobile'
android_9 = 'Android 9; Mobile'
firefox_tmplt = 'Mozilla/5.0 ({osystem}; rv:{version}.0) Gecko/20100101 Firefox/{version}.0'
ff_version = range(85, 92)
chrome_tmplt = 'Mozilla/5.0 ({osystem}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version} Safari/537.36'
chrome_version = ['85.0.4183.121',
'86.0.4240.183',
'87.0.4280.141',
'88.0.4324.182',
'89.0.4389.128',
'90.0.4430.18',
'91.0.4472.73',
'92.0.4515.14']
uas = []
for osystem in [windows_x64, linux_x64, android_9, android_10, android_11]:
for version in ff_version:
uas.append(firefox_tmplt.format(osystem=osystem, version=version))
for version in chrome_version:
uas.append(chrome_tmplt.format(osystem=osystem, version=version))
return random.choice(uas)
class CitizenBaseAPI:
url: str = "https://www.erepublik.com/en"
@ -144,7 +153,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 +163,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)
@ -163,18 +173,12 @@ class CitizenBaseAPI:
def _post_main_session_unlock(
self, captcha_id: int, image_id: str, challenge_id: str, coords: List[Dict[str, int]], src: str
) -> Response:
if not self._req.cookies.get('l_chathwe'):
self._req.cookies.set('l_chathwe', 1, expires=int(time.time())+300, path="/en/military", domain='.www.erepublik.com', secure=True)
if self._req.cookies.get('sh'):
self._req.cookies.pop('sh')
if self._req.cookies.get('ch'):
self._req.cookies.pop('ch')
c = [cookie.name for cookie in self._req.cookies if cookie.domain == '.www.erepublik.com' and not cookie.name.startswith('erpk')]
env = dict(l=['tets'], s=[], c=c + ['l_chathwe'], 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)
cookies = dict(sh=hashlib.sha256(','.join(env['l']+env['s']).encode('utf8')).hexdigest(),
ch=hashlib.sha256(','.join(env['c']).encode('utf8')).hexdigest())
cookie_kwargs = dict(expires=int(time.time())+120, path="/en/main/sessionUnlock", domain='.www.erepublik.com',
secure=True)
secure=True, rest={'HttpOnly': True})
self._req.cookies.set('sh', cookies['sh'], **cookie_kwargs)
self._req.cookies.set('ch', cookies['ch'], **cookie_kwargs)
b64_env = utils.b64json(env)
@ -183,6 +187,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:

View File

@ -9,11 +9,11 @@ from threading import Event
from time import sleep
from typing import Any, Dict, List, NoReturn, Optional, Set, Tuple, Union
from requests import RequestException, Response, HTTPError
from requests import HTTPError, RequestException, Response
from . import access_points, classes, constants, _types as types, utils
from .classes import OfferItem
from ._logging import ErepublikErrorHTTTPHandler, ErepublikFileHandler, ErepublikFormatter, ErepublikLogConsoleHandler
from erepublik import _types as types
from erepublik import access_points, classes, constants, utils
from erepublik._logging import ErepublikErrorHTTTPHandler, ErepublikFileHandler, ErepublikFormatter, ErepublikLogConsoleHandler
class BaseCitizen(access_points.CitizenAPI):
@ -77,7 +77,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()
@ -205,13 +206,16 @@ class BaseCitizen(access_points.CitizenAPI):
citizen_js = utils.json.loads(ugly_js)
citizen = citizen_js.get('citizen', {})
self.details.citizen_id = int(citizen['citizenId'])
self.name = citizen['name']
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.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(
@ -278,8 +282,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]]:
raise NotImplemented
def solve_captcha(self, src: str) -> Optional[List[Dict[str, int]]]:
class _API_RESULT(dict):
x: int
y: int
class _API_RETURN(dict):
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:
@ -569,8 +584,14 @@ class BaseCitizen(access_points.CitizenAPI):
with open(dump_name) as f:
data = utils.json.load(f, object_hook=utils.json_decode_object_hook)
player = cls(data['config']['email'], "")
for cookie in data['cookies']:
player._req.cookies.set(**cookie)
if data.get('cookies'):
cookies = data.get('cookies')
if isinstance(cookies, list):
for cookie in data['cookies']:
player._req.cookies.set(**cookie)
else:
player._req.cookies.update(cookies)
player._req.headers.update({"User-Agent": data['user_agent']})
for k, v in data.get('config', {}).items():
if hasattr(player.config, k):
@ -581,11 +602,11 @@ class BaseCitizen(access_points.CitizenAPI):
def _resume_session(self):
resp = self._req.get(self.url)
re_name_id = re.search(r'<a data-fblog="profile_avatar" href="/en/citizen/profile/(\d+)" '
r'class="user_avatar" title="(.*?)">', resp.text)
if re_name_id:
self.name = re_name_id.group(2)
self.details.citizen_id = re_name_id.group(1)
try:
self.update_citizen_info(resp.text)
if not self.name:
raise classes.ErepublikException("Unable to find player name")
self.write_log(f"Resumed as: {self.name}")
if re.search('<div id="accountSecurity" class="it-hurts-when-ip">', resp.text):
self.restricted_ip = True
@ -593,7 +614,7 @@ class BaseCitizen(access_points.CitizenAPI):
self.logged_in = True
self.get_csrf_token()
else:
except classes.ErepublikException:
self._login()
def __str__(self) -> str:
@ -629,7 +650,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
@ -656,10 +677,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
@ -668,7 +688,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:
@ -692,8 +712,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
@ -767,28 +787,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)
@ -797,11 +795,7 @@ class BaseCitizen(access_points.CitizenAPI):
if r.url == f"{self.url}/login":
self.report_error("Citizen email and/or password is incorrect!")
else:
re_name_id = re.search(r'<a data-fblog="profile_avatar" href="/en/citizen/profile/(\d+)" '
r'class="user_avatar" title="(.*?)">', r.text)
self.name = re_name_id.group(2)
self.details.citizen_id = re_name_id.group(1)
self.update_citizen_info(r.text)
self.write_log(f"Logged in as: {self.name}")
self.get_csrf_token()
if re.search('<div id="accountSecurity" class="it-hurts-when-ip">', self.r.text):
@ -820,9 +814,12 @@ class BaseCitizen(access_points.CitizenAPI):
pass
if response.status_code >= 400:
self.r = response
if '<title>Attention Required! | Cloudflare</title>' 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()
@ -1327,7 +1324,7 @@ class CitizenEconomy(CitizenTravel):
self._report_action('BOUGHT_PRODUCTS', json_ret.get('message'), kwargs=json_ret)
return json_ret
def buy_market_offer(self, offer: OfferItem, amount: int = None) -> Optional[Dict[str, Any]]:
def buy_market_offer(self, offer: classes.OfferItem, amount: int = None) -> Optional[Dict[str, Any]]:
if amount is None or amount > offer.amount:
amount = offer.amount
traveled = False
@ -1924,17 +1921,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:
@ -2002,14 +1996,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:
@ -2172,7 +2166,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
@ -2211,7 +2205,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
@ -2221,7 +2215,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
@ -2544,10 +2538,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')
@ -2564,12 +2554,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):
@ -2593,13 +2581,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):
@ -2617,13 +2603,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:
@ -2687,14 +2671,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():
@ -2713,9 +2689,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):
"""
@ -2734,9 +2711,9 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
self.buy_tg_contract()
else:
self.write_warning('Training ground contract active but '
f"don't have enough gold ({self.details.gold}g {self.details.cc}cc)")
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()
@ -2873,7 +2850,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):
@ -2882,31 +2859,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()
@ -3170,7 +3122,7 @@ class Citizen(_Citizen):
finally:
self._concurrency_lock.set()
def buy_market_offer(self, offer: OfferItem, amount: int = None) -> Optional[Dict[str, Any]]:
def buy_market_offer(self, offer: classes.OfferItem, amount: int = None) -> Optional[Dict[str, Any]]:
if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.report_error(e)

View File

@ -1,13 +1,16 @@
import datetime
import hashlib
import threading
import warnings
import weakref
from decimal import Decimal
from typing import Any, Dict, Generator, Iterable, List, NamedTuple, NoReturn, Union
from io import BytesIO
from typing import Any, Dict, Generator, Iterable, List, NamedTuple, NoReturn, Optional, Tuple, Union
from requests import Response, Session, post
from requests import HTTPError, Response, Session, post
from . import constants, _types as types, utils
from erepublik import _types as types
from erepublik import constants, utils
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
'ErepublikNetworkException', 'EnergyToFight', 'Holding', 'Inventory', 'MyCompanies', 'OfferItem', 'Politics',
@ -25,6 +28,14 @@ class ErepublikNetworkException(ErepublikException):
self.request = request
class CloudFlareSessionError(ErepublikNetworkException):
pass
class CaptchaSessionError(ErepublikNetworkException):
pass
class Holding:
id: int
region: int
@ -412,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
@ -439,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:
@ -606,20 +628,32 @@ class Reporter:
for unreported_data in self.__to_update:
unreported_data.update(player_id=self.citizen_id, key=self.key)
unreported_data = utils.json_loads(utils.json_dumps(unreported_data))
self._req.post(f"{self.url}/bot/update", json=unreported_data)
r = self._req.post(f"{self.url}/bot/update", json=unreported_data)
r.raise_for_status()
self.__to_update.clear()
data = utils.json.loads(utils.json_dumps(data))
r = self._req.post(f"{self.url}/bot/update", json=data)
r.raise_for_status()
return r
def _bot_update(self, data: Dict[str, Any]) -> Optional[Response]:
if not self.__registered:
self.do_init()
if self.allowed:
try:
return self.__bot_update(data)
except HTTPError:
self.__to_update.append(data)
else:
self.__to_update.append(data)
def register_account(self):
if not self.__registered:
try:
r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id))
r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id))
if r:
if not r.json().get('status'):
self._req.post(f"{self.url}/bot/register", json=dict(name=self.name, email=self.email,
player_id=self.citizen_id))
finally:
self.__registered = True
self.allowed = True
self.report_action('STARTED', value=utils.now().strftime("%F %T"))
@ -631,9 +665,7 @@ class Reporter:
xp=xp, cc=cc, gold=gold, inv_total=inv_total, inv_free=inv_total - inv, inv=inv, food=food,
pp=pp, hp_limit=hp_limit, hp_interval=hp_interval, hp_available=hp_available,
))
if self.allowed:
self.__bot_update(data)
self._bot_update(data)
def report_action(self, action: str, json_val: Dict[Any, Any] = None, value: str = None):
json_data = dict(
@ -645,10 +677,7 @@ class Reporter:
json_data['log'].update(dict(value=value))
if not any([self.key, self.email, self.name, self.citizen_id]):
return
if self.allowed:
self.__bot_update(json_data)
else:
self.__to_update.append(json_data)
self._bot_update(json_data)
def report_fighting(self, battle: 'Battle', invader: bool, division: 'BattleDivision', damage: float, hits: int):
side = battle.invader if invader else battle.defender
@ -960,7 +989,7 @@ class TelegramReporter:
if token is None:
token = "864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o"
self.chat_id = chat_id
self.api_url = f"https://api.telegram.org/bot{token}/sendMessage"
self.api_url = f"https://api.telegram.org/bot{token}"
self.player_name = player_name or ""
self.__initialized = True
self._last_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-5))
@ -1017,13 +1046,21 @@ class TelegramReporter:
message = "\n\n".join(self.__queue)
if self.player_name:
message = f"Player *{self.player_name}*\n\n" + message
response = post(self.api_url, json=dict(chat_id=self.chat_id, text=message, parse_mode='Markdown'))
response = post(f"{self.api_url}/sendMessage", json=dict(chat_id=self.chat_id, text=message, parse_mode='Markdown'))
self._last_time = utils.now()
if response.json().get('ok'):
self.__queue.clear()
return True
return False
def send_photos(self, photos: List[Tuple[str, BytesIO]]):
for photo_title, photo in photos:
photo.seek(0)
post(f"https://{self.api_url}/sendPhoto",
data=dict(chat_id=self.chat_id, caption=photo_title),
files=[('photo', ("f{utils.slugify(photo_title)}.png", photo))])
return
class OfferItem(NamedTuple):
price: float = 999_999_999.

View File

@ -81,24 +81,94 @@ class Industries:
return dict(by_id=self.__by_id, by_name=self.__by_name)
AIR_RANKS: Dict[int, str] = {
1: 'Airman', 2: 'Airman 1st Class', 3: 'Airman 1st Class*', 4: 'Airman 1st Class**', 5: 'Airman 1st Class***',
6: 'Airman 1st Class****', 7: 'Airman 1st Class*****', 8: 'Senior Airman', 9: 'Senior Airman*',
10: 'Senior Airman**', 11: 'Senior Airman***', 12: 'Senior Airman****', 13: 'Senior Airman*****',
14: 'Staff Sergeant', 15: 'Staff Sergeant*', 16: 'Staff Sergeant**', 17: 'Staff Sergeant***',
18: 'Staff Sergeant****', 19: 'Staff Sergeant*****', 20: 'Aviator', 21: 'Aviator*', 22: 'Aviator**',
23: 'Aviator***', 24: 'Aviator****', 25: 'Aviator*****', 26: 'Flight Lieutenant', 27: 'Flight Lieutenant*',
28: 'Flight Lieutenant**', 29: 'Flight Lieutenant***', 30: 'Flight Lieutenant****', 31: 'Flight Lieutenant*****',
32: 'Squadron Leader', 33: 'Squadron Leader*', 34: 'Squadron Leader**', 35: 'Squadron Leader***',
36: 'Squadron Leader****', 37: 'Squadron Leader*****', 38: 'Chief Master Sergeant', 39: 'Chief Master Sergeant*',
40: 'Chief Master Sergeant**', 41: 'Chief Master Sergeant***', 42: 'Chief Master Sergeant****',
43: 'Chief Master Sergeant*****', 44: 'Wing Commander', 45: 'Wing Commander*', 46: 'Wing Commander**',
47: 'Wing Commander***', 48: 'Wing Commander****', 49: 'Wing Commander*****', 50: 'Group Captain',
51: 'Group Captain*', 52: 'Group Captain**', 53: 'Group Captain***', 54: 'Group Captain****',
55: 'Group Captain*****', 56: 'Air Commodore', 57: 'Air Commodore*', 58: 'Air Commodore**', 59: 'Air Commodore***',
60: 'Air Commodore****', 61: 'Air Commodore*****',
class Rank:
id: int
name: str
rank_points: int
is_air: bool
def __init__(self, id: int, name: str, rank_points: int, is_air: bool = False):
self.id = id
self.name = name
self.rank_points = rank_points
self.is_air = bool(is_air)
def __int__(self):
return self.id
def __eq__(self, other):
if isinstance(other, Rank):
return self.id == other.id if other.is_air == self.is_air else False
else:
return self.id == int(other)
def __ne__(self, other):
if isinstance(other, Rank):
return not self.id == other.id if other.is_air == self.is_air else True
else:
return not self.id == int(other)
def __lt__(self, other):
if isinstance(other, Rank):
return self.id < other.id if other.is_air == self.is_air else False
else:
return self.id < int(other)
def __le__(self, other):
if isinstance(other, Rank):
return self.id <= other.id if other.is_air == self.is_air else False
else:
return self.id <= int(other)
def __gt__(self, other):
if isinstance(other, Rank):
return self.id > other.id if other.is_air == self.is_air else False
else:
return self.id > int(other)
def __ge__(self, other):
if isinstance(other, Rank):
return self.id >= other.id if other.is_air == self.is_air else False
else:
return self.id >= int(other)
@property
def as_dict(self):
return dict(id=self.id, name=self.name, rank_points=self.rank_points, is_air=self.is_air)
def __str__(self):
return f"{'Air' if self.is_air else 'Ground'}Rank<#{self.id} {self.name}>"
def __repr__(self):
return str(self)
AIR_RANK_NAMES: Dict[int, str] = {
1: 'Airman', 2: 'Airman 1st Class', 3: 'Airman 1st Class*', 4: 'Airman 1st Class**', 5: 'Airman 1st Class***', 6: 'Airman 1st Class****', 7: 'Airman 1st Class*****',
8: 'Senior Airman', 9: 'Senior Airman*', 10: 'Senior Airman**', 11: 'Senior Airman***', 12: 'Senior Airman****', 13: 'Senior Airman*****',
14: 'Staff Sergeant', 15: 'Staff Sergeant*', 16: 'Staff Sergeant**', 17: 'Staff Sergeant***', 18: 'Staff Sergeant****', 19: 'Staff Sergeant*****',
20: 'Aviator', 21: 'Aviator*', 22: 'Aviator**', 23: 'Aviator***', 24: 'Aviator****', 25: 'Aviator*****',
26: 'Flight Lieutenant', 27: 'Flight Lieutenant*', 28: 'Flight Lieutenant**', 29: 'Flight Lieutenant***', 30: 'Flight Lieutenant****', 31: 'Flight Lieutenant*****',
32: 'Squadron Leader', 33: 'Squadron Leader*', 34: 'Squadron Leader**', 35: 'Squadron Leader***', 36: 'Squadron Leader****', 37: 'Squadron Leader*****',
38: 'Chief Master Sergeant', 39: 'Chief Master Sergeant*', 40: 'Chief Master Sergeant**', 41: 'Chief Master Sergeant***', 42: 'Chief Master Sergeant****', 43: 'Chief Master Sergeant*****',
44: 'Wing Commander', 45: 'Wing Commander*', 46: 'Wing Commander**', 47: 'Wing Commander***', 48: 'Wing Commander****', 49: 'Wing Commander*****',
50: 'Group Captain', 51: 'Group Captain*', 52: 'Group Captain**', 53: 'Group Captain***', 54: 'Group Captain****', 55: 'Group Captain*****',
56: 'Air Commodore', 57: 'Air Commodore*', 58: 'Air Commodore**', 59: 'Air Commodore***', 60: 'Air Commodore****', 61: 'Air Commodore*****',
62: 'Air Vice Marshal', 63: 'Air Vice Marshal*', 64: 'Air Vice Marshal**', 65: 'Air Vice Marshal***', 66: 'Air Vice Marshal****', 67: 'Air Vice Marshal*****',
68: 'Air Marshal', 69: 'Air Marshal*', 70: 'Air Marshal**', 71: 'Air Marshal***', 72: 'Air Marshal****', 73: 'Air Marshal*****',
74: 'Air Chief Marshal', 75: 'Air Chief Marshal*', 76: 'Air Chief Marshal**', 77: 'Air Chief Marshal***', 78: 'Air Chief Marshal****', 79: 'Air Chief Marshal*****',
}
AIR_RANK_POINTS: Dict[int, Optional[int]] = {
1: 0, 2: 10, 3: 25, 4: 45, 5: 70, 6: 100, 7: 140, 8: 190, 9: 270, 10: 380, 11: 530, 12: 850, 13: 1300, 14: 2340, 15: 3300, 16: 4200, 17: 5150, 18: 6100, 19: 7020, 20: 9100, 21: 12750, 22: 16400, 23: 20000, 24: 23650, 25: 27300,
26: 35500, 27: 48000, 28: 60000, 29: 72400, 30: 84500, 31: 97000, 32: 110000, 33: 140000, 34: 170000, 35: 210000, 36: 290000, 37: 350000, 38: 429000, 39: 601000, 40: 772000, 41: 944000, 42: 1115000, 43: 1287000,
44: 1673000, 45: 2238000, 46: 2804000, 47: 3369000, 48: 3935000, 49: 4500000, 50: 5020000, 51: 7028000, 52: 9036000, 53: 11044000, 54: 13052000, 55: 15060000,
56: 19580000, 57: 27412000, 58: 35244000, 59: 43076000, 60: 50908000, 61: 58740000, 62: 76360000, 63: 113166443, 64: 137448000, 65: None, 66: None, 67: None,
68: None, 69: None, 70: None, 71: None, 72: None, 73: None, 74: None, 75: None, 76: None, 77: None, 78: None, 79: None,
}
AIR_RANKS: Dict[int, Rank] = {i: Rank(i, AIR_RANK_NAMES[i], AIR_RANK_POINTS[i], True) for i in range(1, 80)}
COUNTRIES: Dict[int, Country] = {
1: Country(1, 'Romania', 'Romania', 'ROU'), 9: Country(9, 'Brazil', 'Brazil', 'BRA'),
10: Country(10, 'Italy', 'Italy', 'ITA'), 11: Country(11, 'France', 'France', 'FRA'),
@ -144,28 +214,18 @@ COUNTRIES: Dict[int, Country] = {
FOOD_ENERGY: Dict[str, int] = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
GROUND_RANKS: Dict[int, str] = {
1: 'Recruit', 2: 'Private', 3: 'Private*', 4: 'Private**', 5: 'Private***',
6: 'Corporal', 7: 'Corporal*', 8: 'Corporal**', 9: 'Corporal***',
10: 'Sergeant', 11: 'Sergeant*', 12: 'Sergeant**', 13: 'Sergeant***',
14: 'Lieutenant', 15: 'Lieutenant*', 16: 'Lieutenant**', 17: 'Lieutenant***',
18: 'Captain', 19: 'Captain*', 20: 'Captain**', 21: 'Captain***',
22: 'Major', 23: 'Major*', 24: 'Major**', 25: 'Major***',
26: 'Commander', 27: 'Commander*', 28: 'Commander**', 29: 'Commander***',
30: 'Lt Colonel', 31: 'Lt Colonel*', 32: 'Lt Colonel**', 33: 'Lt Colonel***',
34: 'Colonel', 35: 'Colonel*', 36: 'Colonel**', 37: 'Colonel***',
38: 'General', 39: 'General*', 40: 'General**', 41: 'General***',
42: 'Field Marshal', 43: 'Field Marshal*', 44: 'Field Marshal**', 45: 'Field Marshal***',
46: 'Supreme Marshal', 47: 'Supreme Marshal*', 48: 'Supreme Marshal**', 49: 'Supreme Marshal***',
50: 'National Force', 51: 'National Force*', 52: 'National Force**', 53: 'National Force***',
54: 'World Class Force', 55: 'World Class Force*', 56: 'World Class Force**', 57: 'World Class Force***',
58: 'Legendary Force', 59: 'Legendary Force*', 60: 'Legendary Force**', 61: 'Legendary Force***',
62: 'God of War', 63: 'God of War*', 64: 'God of War**', 65: 'God of War***',
GROUND_RANK_NAMES: Dict[int, str] = {
1: 'Recruit', 2: 'Private', 3: 'Private*', 4: 'Private**', 5: 'Private***', 6: 'Corporal', 7: 'Corporal*', 8: 'Corporal**', 9: 'Corporal***',
10: 'Sergeant', 11: 'Sergeant*', 12: 'Sergeant**', 13: 'Sergeant***', 14: 'Lieutenant', 15: 'Lieutenant*', 16: 'Lieutenant**', 17: 'Lieutenant***',
18: 'Captain', 19: 'Captain*', 20: 'Captain**', 21: 'Captain***', 22: 'Major', 23: 'Major*', 24: 'Major**', 25: 'Major***',
26: 'Commander', 27: 'Commander*', 28: 'Commander**', 29: 'Commander***', 30: 'Lt Colonel', 31: 'Lt Colonel*', 32: 'Lt Colonel**', 33: 'Lt Colonel***',
34: 'Colonel', 35: 'Colonel*', 36: 'Colonel**', 37: 'Colonel***', 38: 'General', 39: 'General*', 40: 'General**', 41: 'General***',
42: 'Field Marshal', 43: 'Field Marshal*', 44: 'Field Marshal**', 45: 'Field Marshal***', 46: 'Supreme Marshal', 47: 'Supreme Marshal*', 48: 'Supreme Marshal**', 49: 'Supreme Marshal***',
50: 'National Force', 51: 'National Force*', 52: 'National Force**', 53: 'National Force***', 54: 'World Class Force', 55: 'World Class Force*', 56: 'World Class Force**', 57: 'World Class Force***',
58: 'Legendary Force', 59: 'Legendary Force*', 60: 'Legendary Force**', 61: 'Legendary Force***', 62: 'God of War', 63: 'God of War*', 64: 'God of War**', 65: 'God of War***',
66: 'Titan', 67: 'Titan*', 68: 'Titan**', 69: 'Titan***',
70: 'Legends I', 71: 'Legends II', 72: 'Legends III', 73: 'Legends IV', 74: 'Legends V', 75: 'Legends VI',
76: 'Legends VII', 77: 'Legends VIII', 78: 'Legends IX', 79: 'Legends X', 80: 'Legends XI', 81: 'Legends XII',
82: 'Legends XIII', 83: 'Legends XIV', 84: 'Legends XV', 85: 'Legends XVI', 86: 'Legends XVII', 87: 'Legends XVIII',
88: 'Legends XIX', 89: 'Legends XX',
70: 'Legends I', 71: 'Legends II', 72: 'Legends III', 73: 'Legends IV', 74: 'Legends V', 75: 'Legends VI', 76: 'Legends VII', 77: 'Legends VIII', 78: 'Legends IX', 79: 'Legends X',
80: 'Legends XI', 81: 'Legends XII', 82: 'Legends XIII', 83: 'Legends XIV', 84: 'Legends XV', 85: 'Legends XVI', 86: 'Legends XVII', 87: 'Legends XVIII', 88: 'Legends XIX', 89: 'Legends XX'
}
GROUND_RANK_POINTS: Dict[int, int] = {
@ -184,6 +244,8 @@ GROUND_RANK_POINTS: Dict[int, int] = {
86: 180000000000, 87: 190000000000, 88: 200000000000, 89: 210000000000
}
GROUND_RANKS: Dict[int, Rank] = {i: Rank(i, GROUND_RANK_NAMES[i], GROUND_RANK_POINTS[i], False) for i in range(1, 90)}
INDUSTRIES = Industries()
TERRAINS: Dict[int, str] = {0: 'Standard', 1: 'Industrial', 2: 'Urban', 3: 'Suburbs', 4: 'Airport', 5: 'Plains',

View File

@ -7,6 +7,7 @@ import unicodedata
import warnings
from base64 import b64encode
from decimal import Decimal
from logging import Logger
from pathlib import Path
from typing import Any, Dict, List, Union
@ -14,7 +15,7 @@ import pytz
import requests
from requests import Response
from . import __version__, constants
from erepublik import __version__, constants
try:
import simplejson as json
@ -102,27 +103,6 @@ def interactive_sleep(sleep_seconds: int):
silent_sleep = time.sleep
# def _write_log(msg, timestamp: bool = True, should_print: bool = False):
# erep_time_now = now()
# txt = f"[{erep_time_now.strftime('%F %T')}] {msg}" if timestamp else msg
# if not os.path.isdir('log'):
# os.mkdir('log')
# with open(f'log/{erep_time_now.strftime("%F")}.log', 'a', encoding='utf-8') as f:
# f.write(f'{txt}\n')
# if should_print:
# print(txt)
#
#
# def write_interactive_log(*args, **kwargs):
# kwargs.pop('should_print', None)
# _write_log(should_print=True, *args, **kwargs)
#
#
# def write_silent_log(*args, **kwargs):
# kwargs.pop('should_print', None)
# _write_log(should_print=False, *args, **kwargs)
def get_file(filepath: str) -> str:
file = Path(filepath)
if file.exists():
@ -228,27 +208,6 @@ def deprecation(message):
warnings.warn(message, DeprecationWarning, stacklevel=2)
# def wait_for_lock(function):
# def wrapper(instance, *args, **kwargs):
# if not instance.concurrency_available.wait(600):
# e = 'Concurrency not freed in 10min!'
# instance.write_log(e)
# if instance.debug:
# instance.report_error(e)
# return None
# else:
# instance.concurrency_available.clear()
# try:
# ret = function(instance, *args, **kwargs)
# except Exception as e:
# instance.concurrency_available.set()
# raise e
# instance.concurrency_available.set()
# return ret
#
# return wrapper
def json_decode_object_hook(
o: Union[Dict[str, Any], List[Any], int, float, str]
) -> Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta]:
@ -296,7 +255,7 @@ def json_dumps(obj, *args, **kwargs):
def b64json(obj: Union[Dict[str, Union[int, List[str]]], List[str]]):
if isinstance(obj, list):
return b64encode(json.dumps(obj).replace(' ', '').encode('utf-8')).decode('utf-8')
return b64encode(json.dumps(obj, separators=(',', ':')).encode('utf-8')).decode('utf-8')
elif isinstance(obj, (int, str)):
return obj
elif isinstance(obj, dict):
@ -305,31 +264,36 @@ def b64json(obj: Union[Dict[str, Union[int, List[str]]], List[str]]):
else:
from .classes import ErepublikException
raise ErepublikException(f'Unhandled object type! obj is {type(obj)}')
return b64encode(json.dumps(obj).replace(' ', '').encode('utf-8')).decode('utf-8')
return b64encode(json.dumps(obj, separators=(',', ':')).encode('utf-8')).decode('utf-8')
class ErepublikJSONEncoder(json.JSONEncoder):
def default(self, o):
from erepublik.citizen import Citizen
if isinstance(o, Decimal):
return float(f"{o:.02f}")
elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', date=o.strftime("%Y-%m-%d"), time=o.strftime("%H:%M:%S"),
tzinfo=str(o.tzinfo) if o.tzinfo else None)
elif isinstance(o, datetime.date):
return dict(__type__='date', date=o.strftime("%Y-%m-%d"))
elif isinstance(o, datetime.timedelta):
return dict(__type__='timedelta', days=o.days, seconds=o.seconds,
microseconds=o.microseconds, total_seconds=o.total_seconds())
elif isinstance(o, Response):
return dict(headers=dict(o.__dict__['headers']), url=o.url, text=o.text, status_code=o.status_code)
elif hasattr(o, 'as_dict'):
return o.as_dict
elif isinstance(o, set):
return list(o)
elif isinstance(o, Citizen):
return o.to_json()
try:
return super().default(o)
from erepublik.citizen import Citizen
if isinstance(o, Decimal):
return float(f"{o:.02f}")
elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', date=o.strftime("%Y-%m-%d"), time=o.strftime("%H:%M:%S"),
tzinfo=str(o.tzinfo) if o.tzinfo else None)
elif isinstance(o, datetime.date):
return dict(__type__='date', date=o.strftime("%Y-%m-%d"))
elif isinstance(o, datetime.timedelta):
return dict(__type__='timedelta', days=o.days, seconds=o.seconds,
microseconds=o.microseconds, total_seconds=o.total_seconds())
elif isinstance(o, Response):
return dict(headers=dict(o.__dict__['headers']), url=o.url, text=o.text, status_code=o.status_code)
elif hasattr(o, 'as_dict'):
return o.as_dict
elif isinstance(o, set):
return list(o)
elif isinstance(o, Citizen):
return o.to_json()
elif isinstance(o, Logger):
return str(o)
elif hasattr(o, '__dict__'):
return o.__dict__
else:
return super().default(o)
except Exception as e: # noqa
return 'Object is not JSON serializable'
return str(e)

View File

@ -26,9 +26,7 @@ def main():
player.login()
now = player.now.replace(second=0, microsecond=0)
dt_max = constants.max_datetime
tasks = {
'eat': now,
}
tasks = {}
if player.config.work:
tasks.update({'work': now})
if player.config.train:
@ -61,7 +59,6 @@ def main():
if tasks.get('wam', dt_max) <= now:
player.write_log("Doing task: Work as manager")
success = player.work_as_manager()
player.eat()
if success:
next_time = utils.good_timedelta(now.replace(hour=14, minute=0, second=0, microsecond=0),
timedelta(days=1))
@ -70,19 +67,8 @@ def main():
tasks.update({'wam': next_time})
if tasks.get('eat', dt_max) <= now:
player.write_log("Doing task: eat")
player.eat()
if player.energy.food_fights > player.energy.limit // 10:
next_minutes = 12
else:
next_minutes = (player.energy.limit - 5 * player.energy.interval) // player.energy.interval * 6
next_time = player.energy.reference_time + timedelta(minutes=next_minutes)
tasks.update({'eat': next_time})
if tasks.get('ot', dt_max) <= now:
player.update_job_info()
player.write_log("Doing task: work overtime")
if now > player.my_companies.next_ot_time:
player.work_ot()

View File

@ -1,20 +1,21 @@
bump2version==1.0.1
coverage==5.4
edx-sphinx-theme==1.6.1
flake8==3.8.4
ipython>=7.19.0
coverage==5.5
edx-sphinx-theme==3.0.0
flake8==3.9.2
ipython>=7.25.0
jedi!=0.18.0
isort==5.7.0
pip==21.0
pre-commit==2.9.3
pur==5.3.0
PyInstaller==4.2
isort==5.9.2
pip==21.1.3
pre-commit==2.13.0
pur==5.4.2
PyInstaller==4.3
PySocks==1.7.1
pytest==6.2.2
pytz>=2020.5
requests>=2.25.1
responses==0.12.1
setuptools==52.0.0
Sphinx==3.4.3
twine==3.3.0
pytest==6.2.4
pytz==2021.1
requests==2.25.1
requests-toolbelt==0.9.1
responses==0.13.3
setuptools==57.1.0
Sphinx==4.0.3
twine==3.4.1
wheel==0.36.2

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.24.1
current_version = 0.25.1.1
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
@ -19,15 +19,15 @@ universal = 1
[flake8]
exclude = docs,.git,log,debug,venv
max-line-length = 120
max-line-length = 240
ignore = D100,D101,D102,D103
[pycodestyle]
max-line-length = 120
max-line-length = 240
exclude = .git,log,debug,venv, build
[mypy]
python_version = 3.7
python_version = 3.8
check_untyped_defs = True
ignore_missing_imports = False
warn_unused_ignores = True
@ -36,4 +36,4 @@ warn_unused_configs = True
[isort]
multi_line_output = 2
line_length = 120
line_length = 240

View File

@ -12,16 +12,17 @@ with open('HISTORY.rst') as history_file:
history = history_file.read()
requirements = [
'pytz>=2020.0',
'requests>=2.24.0,<2.26.0',
'PySocks==1.7.1'
'PySocks>=1.7.1',
'pytz>=2021.1',
'requests>=2.25.0',
'requests-toolbelt>=0.9.0',
]
setup_requirements = []
test_requirements = [
"pytest==6.1.2",
"responses==0.12.1"
"pytest==6.2.4",
"responses==0.13.3"
]
setup(
@ -33,8 +34,8 @@ setup(
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
description="Python package for automated eRepublik playing",
entry_points={},
@ -45,11 +46,11 @@ setup(
keywords='erepublik',
name='eRepublik',
packages=find_packages(include=['erepublik']),
python_requires='>=3.7, <4',
python_requires='>=3.8, <4',
setup_requires=setup_requirements,
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/',
version='0.24.1',
version='0.25.1.1',
zip_safe=False,
)