Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
5a1f7801a2 | |||
17845f750c | |||
883af51197 | |||
814cb5ab87 | |||
3c316bada3 | |||
ea03979943 | |||
9aae685b21 | |||
cae94d7aa8 | |||
fae7b0fd37 | |||
b7771b4da2 | |||
e3a10af101 | |||
33a5bcacf1 | |||
342ca2e1cc | |||
580240a015 | |||
1517103ba3 | |||
3dac8c5e74 | |||
8cf86fb9d3 | |||
cf927df6e6 | |||
a6f5dbd05f | |||
967afa472f | |||
a65568cd0c | |||
6e45334d99 | |||
936a1010a6 | |||
acc528cb1d | |||
614d273104 | |||
95966764e8 | |||
f52b078e6a | |||
3af27f6512 | |||
6276242260 | |||
45623de97b | |||
25f932121c |
@ -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
|
||||
|
@ -10,4 +10,4 @@ repos:
|
||||
- id: check-added-large-files
|
||||
|
||||
default_language_version:
|
||||
python: python3.7
|
||||
python: python3.8
|
||||
|
@ -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
|
||||
---------------
|
||||
|
||||
|
@ -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__']
|
||||
|
@ -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:
|
||||
"""
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
10
setup.cfg
10
setup.cfg
@ -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
|
||||
|
17
setup.py
17
setup.py
@ -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,
|
||||
)
|
||||
|
Reference in New Issue
Block a user