Compare commits

..

14 Commits

Author SHA1 Message Date
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
9 changed files with 144 additions and 139 deletions

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,7 +4,7 @@
__author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv'
__version__ = '0.24.2'
__version__ = '0.25.0.1'
from erepublik.citizen import Citizen

View File

@ -144,7 +144,7 @@ class CitizenBaseAPI:
self._req.proxies = dict(http=url, https=url)
def set_http_proxy(self, host: str, port: int, username: str = None, password: str = None):
url = f'http://{username}:{password}@{host}:{port}' if username and password else f'socks5://{host}:{port}'
url = f'http://{username}:{password}@{host}:{port}' if username and password else f'http://{host}:{port}'
self._req.proxies = dict(http=url)
def _get_main_session_captcha(self) -> Response:
@ -154,7 +154,8 @@ class CitizenBaseAPI:
return self.get(f'{self.url}/main/sessionUnlockPopup')
def _post_main_session_get_challenge(self, captcha_id: int, image_id: str = "") -> Response:
env = dict(l=['tets', ], s=[], c=["l_chathwe", "l_chatroom"], m=0)
c = [cookie.name for cookie in self._req.cookies if not cookie.has_nonstandard_attr('HttpOnly')]
env = dict(l=['tets'], s=[], c=c, m=0)
data = dict(_token=self.token, captchaId=captcha_id, env=utils.b64json(env))
if image_id:
data.update(imageId=image_id, isRefresh=True)
@ -177,6 +178,9 @@ class CitizenBaseAPI:
return self.post(f'{self.url}/main/sessionUnlock', data=data, json=data,
headers={'X-Requested-With': 'XMLHttpRequest', 'Referer': 'https://www.erepublik.com/en'})
def _post_energy_refill_get_inventory(self):
return self.post(f'{self.url}/economy/energyRefill-getInventory', data={'_token': self.token})
class ErepublikAnniversaryAPI(CitizenBaseAPI):
def _post_main_collect_anniversary_reward(self) -> Response:

View File

@ -13,8 +13,7 @@ from requests import HTTPError, RequestException, Response
from erepublik import _types as types
from erepublik import access_points, classes, constants, utils
from erepublik._logging import ErepublikErrorHTTTPHandler, ErepublikFileHandler, ErepublikFormatter, \
ErepublikLogConsoleHandler
from erepublik._logging import ErepublikErrorHTTTPHandler, ErepublikFileHandler, ErepublikFormatter, ErepublikLogConsoleHandler
class BaseCitizen(access_points.CitizenAPI):
@ -78,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()
@ -210,9 +210,9 @@ class BaseCitizen(access_points.CitizenAPI):
self.division = int(citizen.get('division', 0))
self.energy.interval = citizen.get('energyPerInterval', 0)
self.energy.limit = citizen.get('energyToRecover', 0)
self.energy.recovered = citizen.get('energy', 0)
self.energy.recoverable = citizen.get('energyFromFoodRemaining', 0)
self.energy.limit = citizen.get('energyPoolLimit', 0)
self.energy.energy = citizen.get('energy', 0)
# self.energy.set_reference_time(utils.good_timedelta(self.now, timedelta(seconds=int(next_recovery[1]) * 60 + int(next_recovery[2]))))
self.details.current_region = citizen.get('regionLocationId', 0)
self.details.current_country = constants.COUNTRIES.get(
@ -279,8 +279,19 @@ class BaseCitizen(access_points.CitizenAPI):
def refresh_captcha_image(self, captcha_id: int, image_id: str):
return self._post_main_session_get_challenge(captcha_id, image_id).json()
def solve_captcha(self, src: str) -> List[Dict[str, int]]:
return []
def solve_captcha(self, src: str) -> Optional[List[Dict[str, int]]]:
class _API_RESULT(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:
@ -636,7 +647,7 @@ class BaseCitizen(access_points.CitizenAPI):
@property
def health_info(self):
ret = f"{self.energy.recovered}/{self.energy.limit} + {self.energy.recoverable}, " \
ret = f"{self.energy.energy}/{self.energy.limit}, " \
f"{self.energy.interval}hp/6m. {self.details.xp_till_level_up}xp until level up"
return ret
@ -663,10 +674,9 @@ class BaseCitizen(access_points.CitizenAPI):
@property
def time_till_full_ff(self) -> timedelta:
energy = self.energy.recoverable + self.energy.recovered
if energy >= self.energy.limit * 2:
if self.energy.energy >= self.energy.limit:
return timedelta(0)
minutes_needed = round((self.energy.limit * 2 - energy) / self.energy.interval) * 6
minutes_needed = round((self.energy.limit - self.energy.energy) / self.energy.interval) * 6
return (self.energy.reference_time - self.now) + timedelta(minutes=minutes_needed)
@property
@ -675,7 +685,7 @@ class BaseCitizen(access_points.CitizenAPI):
Max required time for 0 to full energy (0/0 -> limit/limit) (last interval rounded up)
:return:
"""
return timedelta(minutes=round((self.energy.limit * 2 / self.energy.interval) + 0.49) * 6)
return timedelta(minutes=round((self.energy.limit / self.energy.interval) + 0.49) * 6)
@property
def is_levelup_close(self) -> bool:
@ -699,8 +709,8 @@ class BaseCitizen(access_points.CitizenAPI):
If Energy limit >= xp till levelup * 10
:return: bool
"""
can_reach_next_level = self.energy.recovered >= self.details.xp_till_level_up * 10
can_do_max_amount_of_dmg = self.energy.recoverable + 2 * self.energy.interval >= self.energy.limit
can_reach_next_level = self.energy.energy >= self.details.xp_till_level_up * 10
can_do_max_amount_of_dmg = self.energy.energy + 2 * self.energy.interval >= self.energy.limit
return can_reach_next_level and can_do_max_amount_of_dmg
@property
@ -774,28 +784,6 @@ class BaseCitizen(access_points.CitizenAPI):
ret.update({id_: name})
return ret
def _eat(self, colour: str = 'blue') -> Response:
response = self._post_eat(colour)
r_json = response.json()
for q, amount in r_json.get('units_consumed').items():
if f"q{q}" in self.food:
self.food[f"q{q}"] -= amount
elif q == '10':
self.eb_normal -= amount
elif q == '11':
self.eb_double -= amount
elif 11 < int(q) < 17:
self.eb_small -= amount
elif q == '17':
self.eb_triple -= amount
next_recovery = r_json.get('food_remaining_reset').split(":")
self.energy.set_reference_time(
utils.good_timedelta(self.now, timedelta(seconds=int(next_recovery[1]) * 60 + int(next_recovery[2])))
)
self.energy.recovered = r_json.get('health')
self.energy.recoverable = r_json.get('food_remaining')
return response
def _login(self):
# MUST BE CALLED TROUGH self.get_csrf_token()
r = self._post_login(self.config.email, self.config.password)
@ -827,9 +815,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()
@ -1931,17 +1922,14 @@ class CitizenMilitary(CitizenTravel):
error_count = total_damage = total_hits = 0
ok_to_fight = True
while ok_to_fight and error_count < 10 and count > 0:
while all((count > 0, error_count < 10, self.energy.recovered >= 50)):
while all((count > 0, error_count < 10, self.energy.energy >= 50)):
hits, error, damage = self._shoot(battle, division, side)
count -= hits
total_hits += hits
total_damage += damage
error_count += error
else:
self._eat('blue')
if count > 0 and self.energy.recovered < 50 and use_ebs:
self._eat('orange')
if self.energy.recovered < 50 or error_count >= 10 or count <= 0:
if self.energy.energy < 50 or error_count >= 10 or count <= 0:
self.write_log(f"Hits: {total_hits:>4} | Damage: {total_damage}")
ok_to_fight = False
if total_damage:
@ -2009,14 +1997,14 @@ class CitizenMilitary(CitizenTravel):
hits = r_json['user']['earnedXp']
# InfantryKit player
# The almost always safe way (breaks on levelup hit)
elif self.energy.recovered >= r_json['details']['wellness']: # Haven't reached levelup
hits = (self.energy.recovered - r_json['details']['wellness']) // 10
elif self.energy.energy >= r_json['details']['wellness']: # Haven't reached levelup
hits = (self.energy.energy - r_json['details']['wellness']) // 10
else:
hits = r_json['hits']
if r_json['user']['epicBattle']:
hits /= 1 + r_json['user']['epicBattle']
self.energy.recovered = r_json['details']['wellness']
self.energy.energy = r_json['details']['wellness']
self.details.xp = int(r_json['details']['points'])
damage = r_json['user']['givenDamage'] * (1.1 if r_json['oldEnemy']['isNatural'] else 1)
else:
@ -2179,7 +2167,7 @@ class CitizenMilitary(CitizenTravel):
elif self.next_reachable_energy and self.config.next_energy:
ret = True
# 1h worth of energy
elif self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2:
elif self.energy.energy + self.energy.interval * 3 >= self.energy.limit:
ret = True
return ret
@ -2218,7 +2206,7 @@ class CitizenMilitary(CitizenTravel):
msg = 'Continuing to fight in previous battle'
# All-in (type = all-in and full ff)
elif self.config.all_in and self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2:
elif self.config.all_in and self.energy.energy + self.energy.interval * 3 >= self.energy.limit:
count = self.energy.food_fights
msg = "Fighting all-in. Doing %i hits" % count
@ -2228,7 +2216,7 @@ class CitizenMilitary(CitizenTravel):
msg = "Fighting for +1 energy. Doing %i hits" % count
# 1h worth of energy
elif self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2:
elif self.energy.energy + self.energy.interval * 3 >= self.energy.limit:
count = self.energy.interval
msg = "Fighting for 1h energy. Doing %i hits" % count
force_fight = True
@ -2551,10 +2539,6 @@ class CitizenTasks(CitizenEconomy):
d.update(tg_contract=self.tg_contract, ot_points=self.ot_points, next_ot_time=self.next_ot_time)
return d
def eat(self):
""" Eat food """
self._eat('blue')
def work(self):
if self.energy.food_fights >= 1:
response = self._post_economy_work('work')
@ -2571,12 +2555,10 @@ class CitizenTasks(CitizenEconomy):
else:
self.reporter.report_action('WORK', json_val=js)
else:
self._eat('blue')
if self.energy.food_fights < 1:
seconds = (self.energy.reference_time - self.now).total_seconds()
self.write_warning(f"I don't have energy to work. Will sleep for {seconds}s")
self.sleep(seconds)
self._eat('blue')
self.work()
def train(self):
@ -2600,13 +2582,11 @@ class CitizenTasks(CitizenEconomy):
else:
self.reporter.report_action('TRAIN', response.json())
else:
self._eat('blue')
if self.energy.food_fights < len(tgs):
large = max(self.energy.reference_time, self.now)
sleep_seconds = utils.get_sleep_seconds(large)
self.write_warning(f"I don't have energy to train. Will sleep for {sleep_seconds} seconds")
self.sleep(sleep_seconds)
self._eat('blue')
self.train()
def work_ot(self):
@ -2624,13 +2604,11 @@ class CitizenTasks(CitizenEconomy):
self.buy_food(120)
self.reporter.report_action('WORK_OT', r.json())
elif self.energy.food_fights < 1 and self.ot_points >= 24:
self._eat('blue')
if self.energy.food_fights < 1:
large = max(self.energy.reference_time, self.now)
sleep_seconds = utils.get_sleep_seconds(large)
self.write_warning(f"I don't have energy to work OT. Will sleep for {sleep_seconds}s")
self.sleep(sleep_seconds)
self._eat('blue')
self.work_ot()
def resign_from_employer(self) -> bool:
@ -2694,14 +2672,6 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
player.login()
return player
def _eat(self, colour: str = 'blue') -> Response:
resp = super()._eat(colour)
if not any([resp.json().get('units_consumed').values()]):
if colour == 'orange' and resp.json().get('food_remaining'):
self.eat()
return self._eat(colour)
return resp
def config_setup(self, **kwargs):
self.config.reset()
for key, value in kwargs.items():
@ -2720,9 +2690,10 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
self.config.telegram_token,
self.name)
self.telegram.send_message(f"*Started* {utils.now():%F %T}")
self.init_logger()
self.update_all(True)
if self.logged_in:
self.update_all(True)
def update_citizen_info(self, html: str = None):
"""
@ -2743,7 +2714,7 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
self.write_warning('Training ground contract active but '
f"don't have enough gold ({self.details.gold}g {self.details.cc}cc)")
if self.energy.is_energy_full and self.config.telegram:
self.telegram.report_full_energy(self.energy.available, self.energy.limit, self.energy.interval)
self.telegram.report_full_energy(self.energy.energy, self.energy.limit, self.energy.interval)
def check_for_notification_medals(self):
notifications = self._get_main_citizen_daily_assistant().json()
@ -2880,7 +2851,7 @@ class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
data = dict(xp=self.details.xp, cc=self.details.cc, gold=self.details.gold, pp=self.details.pp,
inv_total=self.inventory.total, inv=self.inventory.used,
hp_limit=self.energy.limit,
hp_interval=self.energy.interval, hp_available=self.energy.available, food=self.food['total'], )
hp_interval=self.energy.interval, hp_available=self.energy.energy, food=self.food['total'], )
self.reporter.send_state_update(**data)
def send_inventory_update(self):
@ -2889,31 +2860,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()

View File

@ -1,6 +1,7 @@
import datetime
import hashlib
import threading
import warnings
import weakref
from decimal import Decimal
from io import BytesIO
@ -27,6 +28,14 @@ class ErepublikNetworkException(ErepublikException):
self.request = request
class CloudFlareSessionError(ErepublikNetworkException):
pass
class CaptchaSessionError(ErepublikNetworkException):
pass
class Holding:
id: int
region: int
@ -414,26 +423,35 @@ class Config:
class Energy:
limit = 500 # energyToRecover
interval = 10 # energyPerInterval
recoverable = 0 # energyFromFoodRemaining
recovered = 0 # energy
energy = 0 # energy
_recovery_time = None
def __init__(self):
self._recovery_time = utils.now()
def __repr__(self):
return f"{self.recovered:4}/{self.limit:4} + {self.recoverable:4}, {self.interval:3}hp/6min"
return f"{self.energy:4}/{self.limit:4}, {self.interval:3}hp/6min"
@property
def recovered(self):
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.energy', DeprecationWarning)
return self.energy
@property
def recoverable(self):
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.energy', DeprecationWarning)
return 0
def set_reference_time(self, recovery_time: datetime.datetime):
self._recovery_time = recovery_time.replace(microsecond=0)
@property
def food_fights(self):
return self.available // 10
return self.energy // 10
@property
def reference_time(self):
if self.is_recovered_full or self._recovery_time < utils.now():
if self.is_energy_full or self._recovery_time < utils.now():
ret = utils.now()
else:
ret = self._recovery_time
@ -441,26 +459,28 @@ class Energy:
@property
def is_recoverable_full(self):
return self.recoverable >= self.limit - 5 * self.interval
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.is_energy_full', DeprecationWarning)
return self.is_energy_full
@property
def is_recovered_full(self):
return self.recovered >= self.limit - self.interval
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.is_energy_full', DeprecationWarning)
return self.is_energy_full
@property
def is_energy_full(self):
return self.is_recoverable_full and self.is_recovered_full
return self.energy >= self.limit - self.interval
@property
def available(self):
return self.recovered + self.recoverable
warnings.warn('Deprecated since auto auto-eat! Will be removed soon. Use Energy.energy', DeprecationWarning)
return self.energy
@property
def as_dict(self) -> Dict[str, Union[int, datetime.datetime, bool]]:
return dict(limit=self.limit, interval=self.interval, recoverable=self.recoverable, recovered=self.recovered,
return dict(limit=self.limit, interval=self.interval, energy=self.energy,
reference_time=self.reference_time, food_fights=self.food_fights,
is_recoverable_full=self.is_recoverable_full, is_recovered_full=self.is_recovered_full,
is_energy_full=self.is_energy_full, available=self.available)
is_energy_full=self.is_energy_full)
class Details:

View File

@ -89,10 +89,59 @@ class Rank:
def __init__(self, id: int, name: str, rank_points: int, is_air: bool = False):
self.id = id
self._name = name
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*****',
@ -195,7 +244,7 @@ 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], True) for i in range(1, 90)}
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()

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,5 +1,5 @@
[bumpversion]
current_version = 0.24.2
current_version = 0.25.0.1
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?

View File

@ -51,6 +51,6 @@ setup(
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/',
version='0.24.2',
version='0.25.0.1',
zip_safe=False,
)