Compare commits

..

10 Commits

6 changed files with 185 additions and 96 deletions

View File

@ -1,6 +1,6 @@
* eRepublik script version:
* Python version:
* Operating System:
* eRepublik script version:
* Python version:
* Operating System:
### Description
@ -9,7 +9,7 @@ Tell us what happened, what went wrong, and what you expected to happen.
### What I Did
```
``` python
Paste the command(s) you ran and the output.
If there was a crash, please include the traceback here.
```

View File

@ -4,7 +4,7 @@
__author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv'
__version__ = '0.17.1'
__version__ = '0.17.3'
from erepublik import classes, utils
from erepublik.citizen import Citizen

View File

@ -1,10 +1,9 @@
import re
import sys
from threading import Event
from itertools import product
from datetime import datetime, timedelta
from itertools import product
from json import loads, dumps
from threading import Event
from time import sleep
from typing import Dict, List, Tuple, Any, Union, Set
@ -12,7 +11,6 @@ from requests import Response, RequestException
from erepublik.classes import (CitizenAPI, Battle, Reporter, Config, Energy, Details, Politics, MyCompanies,
TelegramBot, ErepublikException, BattleDivision, MyJSONEncoder)
from erepublik.utils import *
@ -26,9 +24,9 @@ class Citizen(CitizenAPI):
active_fs: bool = False
food = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
inventory = {"used": 0, "total": 0}
boosters = {100: {}, 50: {}}
food: Dict[str, int] = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
inventory: Dict[str, int] = {"used": 0, "total": 0}
boosters: Dict[int, Dict[int, int]] = {100: {}, 50: {}}
eb_normal = 0
eb_double = 0
@ -38,7 +36,7 @@ class Citizen(CitizenAPI):
ot_points = 0
tg_contract = None
promos = None
promos: Dict[str, datetime] = None
eday = 0
@ -164,6 +162,13 @@ class Citizen(CitizenAPI):
self.logged_in = True
def _errors_in_response(self, response: Response):
try:
j = response.json()
if j['error'] and j["message"] == 'Too many requests':
self.write_log("Made too many requests! Sleeping for 30 seconds.")
self.sleep(30)
except:
pass
if response.status_code >= 400:
self.r = response
if response.status_code >= 500:
@ -174,7 +179,7 @@ class Citizen(CitizenAPI):
return bool(re.search(r'body id="error"|Internal Server Error|'
r'CSRF attack detected|meta http-equiv="refresh"|not_authenticated', response.text))
def get(self, url: str, *args, **kwargs) -> Response:
def get(self, url: str, **kwargs) -> Response:
if (self.now - self._req.last_time).seconds >= 15 * 60:
self.get_csrf_token()
if "params" in kwargs:
@ -188,7 +193,7 @@ class Citizen(CitizenAPI):
except RequestException as e:
self.write_log("Network error while issuing GET request", e)
self.sleep(60)
return self.get(url, *args, **kwargs)
return self.get(url, **kwargs)
try:
self.update_citizen_info(html=response.text)
@ -221,7 +226,7 @@ class Citizen(CitizenAPI):
except RequestException as e:
self.write_log("Network error while issuing POST request", e)
self.sleep(60)
return self.post(url, data, json, **kwargs)
return self.post(url, data=data, json=json, **kwargs)
try:
resp_data = response.json()
@ -254,6 +259,9 @@ class Citizen(CitizenAPI):
r"<div title=\"(.*?)\">", medal, re.M | re.S)
about = info.group(1).strip()
title = info.group(2).strip()
award_id = re.search(r'"wall_enable_alerts_(\d+)', medal)
if award_id:
self._post_main_wall_post_automatic(**{'message': title, 'awardId': award_id.group(1)})
reward, currency = info.group(3).strip().split(" ")
while not isinstance(reward, float):
try:
@ -310,7 +318,11 @@ class Citizen(CitizenAPI):
return
ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1)
promos = loads(normalize_html_json(ugly_js))
self.promos = {k: v for k, v in (self.promos.items() if self.promos else {}) if v > self.now}
if self.promos is None:
self.promos = {}
else:
self.promos.clear()
self.promos.update({k: v for k, v in self.promos.items() if v > self.now})
send_mail = False
for promo in promos:
promo_name = promo.get("id")
@ -367,7 +379,7 @@ class Citizen(CitizenAPI):
self._post_military_group_missions()
self.details.next_pp.sort()
for id_, skill in citizen.get("mySkills", {}).items():
for skill in citizen.get("mySkills", {}).values():
self.details.mayhem_skills.update({int(skill["terrain_id"]): int(skill["skill_points"])})
if citizen.get('party', []):
@ -537,10 +549,18 @@ class Citizen(CitizenAPI):
if not self.details.current_country:
self.update_citizen_info()
resp_json = self._get_military_campaigns().json()
resp_json = self._get_military_campaigns_json_list().json()
if resp_json.get("countries"):
self.all_battles = {}
self.countries = {}
if self.all_battles is None:
self.all_battles = {}
else:
self.all_battles.clear()
if self.countries is None:
self.countries = {}
else:
self.countries.clear()
for c_id, c_data in resp_json.get("countries").items():
if int(c_id) not in self.countries:
self.countries.update({
@ -550,8 +570,8 @@ class Citizen(CitizenAPI):
self.countries[int(c_id)].update(allies=c_data.get("allies"))
self.__last_war_update_data = resp_json
if resp_json.get("battles"):
for battle_id, battle_data in resp_json.get("battles", {}).items():
self.all_battles.update({int(battle_id): Battle(battle_data)})
for battle_data in resp_json.get("battles", {}).values():
self.all_battles.update({battle_data.get('id'): Battle(battle_data)})
def eat(self):
"""
@ -583,8 +603,7 @@ class Citizen(CitizenAPI):
r_json = response.json()
next_recovery = r_json.get("food_remaining_reset").split(":")
self.energy.set_reference_time(
good_timedelta(self.now,
timedelta(seconds=int(next_recovery[1]) * 60 + int(next_recovery[2])))
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")
@ -646,8 +665,7 @@ class Citizen(CitizenAPI):
break
error_count = 0
while self.energy.food_fights > 5 and error_count < 20:
errors = self.fight(battle_id, side_id=side, is_air=False,
count=self.energy.food_fights - 5)
errors = self.fight(battle_id, side_id=side, count=self.energy.food_fights - 5)
if errors:
error_count += errors
if self.config.epic_hunt_ebs:
@ -659,7 +677,7 @@ class Citizen(CitizenAPI):
self.active_fs = active_fs
def sorted_battles(self, sort_by_time: bool = False) -> List[int]:
def sorted_battles(self, sort_by_time: bool = True) -> List[int]:
cs_battles_air: List[int] = []
cs_battles_ground: List[int] = []
deployed_battles_air: List[int] = []
@ -670,8 +688,12 @@ class Citizen(CitizenAPI):
other_battles_ground: List[int] = []
ret_battles = []
for bid, battle in sorted(self.all_battles.items(), key=lambda b: b[1].start if sort_by_time else b[0],
reverse=sort_by_time):
if sort_by_time:
battle_list = sorted(self.all_battles.values(), key=lambda b: b.start)
battle_list.reverse()
else:
battle_list = sorted(self.all_battles.values(), key=lambda b: b.id)
for battle in battle_list:
battle_sides = [battle.invader.id, battle.defender.id]
# Previous battles
@ -779,15 +801,17 @@ class Citizen(CitizenAPI):
if not self.travel_to_battle(battle_id, country_ids_to_travel):
break
self.fight(battle_id, side_id, battle.is_air)
self.fight(battle_id, side_id)
self.travel_to_residence()
self.collect_weekly_reward()
break
def fight(self, battle_id: int, side_id: int, is_air: bool = False, count: int = None):
if not is_air and self.config.boosters:
def fight(self, battle_id: int, side_id: int, count: int = None):
battle = self.all_battles[battle_id]
zone_id = battle.div[11 if battle.is_air else self.division].battle_zone_id
if not battle.is_air and self.config.boosters:
self.activate_dmg_booster()
data = dict(sideId=side_id, battleId=battle_id)
self.set_default_weapon(battle_id)
error_count = 0
ok_to_fight = True
if count is None:
@ -798,7 +822,7 @@ class Citizen(CitizenAPI):
while ok_to_fight and error_count < 10 and count > 0:
while all((count > 0, error_count < 10, self.energy.recovered >= 50)):
hits, error, damage = self._shoot(is_air, data)
hits, error, damage = self._shoot(battle.is_air, battle_id, side_id, zone_id)
count -= hits
total_hits += hits
total_damage += damage
@ -810,15 +834,15 @@ class Citizen(CitizenAPI):
ok_to_fight = False
if total_damage:
self.reporter.report_action(json_val=dict(battle=battle_id, side=side_id, dmg=total_damage,
air=is_air, hits=total_hits), action="FIGHT")
air=battle.is_air, hits=total_hits), action="FIGHT")
if error_count:
return error_count
def _shoot(self, air: bool, data: dict):
def _shoot(self, air: bool, battle_id: int, side_id: int, zone_id: int):
if air:
response = self._post_military_fight_air(data['battleId'], data['sideId'])
response = self._post_military_fight_air(battle_id, side_id, zone_id)
else:
response = self._post_military_fight_ground(data['battleId'], data['sideId'])
response = self._post_military_fight_ground(battle_id, side_id, zone_id)
if "Zone is not meant for " in response.text:
self.sleep(5)
@ -833,9 +857,11 @@ class Citizen(CitizenAPI):
if j_resp.get("error"):
if j_resp.get("message") == "SHOOT_LOCKOUT" or j_resp.get("message") == "ZONE_INACTIVE":
pass
elif j_resp.get("message") == "NOT_ENOUGH_WEAPONS":
self.set_default_weapon(battle_id)
else:
if j_resp.get("message") == "UNKNOWN_SIDE":
self._rw_choose_side(data["battleId"], data["sideId"])
self._rw_choose_side(battle_id, side_id)
err = True
elif j_resp.get("message") == "ENEMY_KILLED":
hits = (self.energy.recovered - j_resp["details"]["wellness"]) // 10
@ -1323,7 +1349,7 @@ class Citizen(CitizenAPI):
if not self.get_active_ground_damage_booster():
duration = 0
for length, amount in self.boosters[50].items():
if amount > 1:
if amount > 2:
duration = length
break
if duration:
@ -1471,7 +1497,7 @@ class Citizen(CitizenAPI):
if self.max_time_till_full_ff > self.time_till_week_change:
max_count = (int(self.time_till_week_change.total_seconds()) // 360 * self.energy.interval) // 10
log_msg = ("End for Weekly challenge is near "
log_msg = ("End for Weekly challenge is near "
f"(Recoverable until WC end {max_count}hp | want to do {count}hits)")
count = count if max_count > count else max_count
@ -1494,8 +1520,7 @@ class Citizen(CitizenAPI):
@property
def next_wc_start(self) -> datetime:
days = 1 - self.now.weekday() if 1 - self.now.weekday() > 0 else 1 - self.now.weekday() + 7
return good_timedelta(self.now.replace(hour=0, minute=0, second=0, microsecond=0),
timedelta(days=days))
return good_timedelta(self.now.replace(hour=0, minute=0, second=0, microsecond=0), timedelta(days=days))
@property
def time_till_week_change(self) -> timedelta:
@ -1949,19 +1974,19 @@ class Citizen(CitizenAPI):
process_error(msg, self.name, sys.exc_info(), self, self.commit_id, False)
def get_battle_top_10(self, battle_id: int) -> Dict[int, List[Tuple[int, int]]]:
battle = self.all_battles.get(battle_id)
round_id = battle.get('zone_id')
division = self.division if round_id % 4 else 11
resp = self._post_military_battle_console(battle_id, round_id, division).json()
resp.pop('rounds', None)
ret = dict()
for country_id, data in resp.items():
ret.update({int(country_id): []})
for place in sorted(data.get("fighterData", {}).values(), key=lambda _: -_['raw_value']):
ret[int(country_id)].append((place['citizenId'], place['raw_value']))
return ret
return {}
# battle = self.all_battles.get(battle_id)
# round_id = battle.zone_id
# division = self.division if round_id % 4 else 11
#
# resp = self._post_military_battle_console(battle_id, 'battleStatistics', round_id, division).json()
# resp.pop('rounds', None)
# ret = dict()
# for country_id, data in resp.items():
# ret.update({int(country_id): []})
# for place in sorted(data.get("fighterData", {}).values(), key=lambda _: -_['raw_value']):
# ret[int(country_id)].append((place['citizenId'], place['raw_value']))
# return ret
def to_json(self, indent: bool = False) -> str:
return dumps(self.__dict__, cls=MyJSONEncoder, indent=4 if indent else None, sort_keys=True)
@ -2029,3 +2054,23 @@ class Citizen(CitizenAPI):
def speedup_map_quest_node(self, node_id: int):
node = self.get_anniversary_quest_data().get('cities', {}).get(str(node_id), {})
return self._post_map_rewards_speedup(node_id, node.get("skipCost", 0))
def get_available_weapons(self, battle_id: int):
return self._get_military_show_weapons(battle_id).json()
def set_default_weapon(self, battle_id: int) -> int:
battle = self.all_battles[battle_id]
available_weapons = self._get_military_show_weapons(battle_id).json()
weapon_quality = -1
if not battle.is_air:
for weapon in available_weapons:
if weapon['weaponId'] == 7 and weapon['weaponQuantity'] > 30:
weapon_quality = 7
break
return self.change_weapon(battle_id, weapon_quality)
def change_weapon(self, battle_id: int, weapon_quality: int) -> int:
battle = self.all_battles[battle_id]
battle_zone = battle.div[11 if battle.is_air else self.division].battle_zone_id
r = self._post_military_change_weapon(battle_id, battle_zone, weapon_quality)
return r.json().get('weaponInfluence')

View File

@ -271,7 +271,7 @@ class Config:
work = True
train = True
wam = False
auto_sell: List[str] = list()
auto_sell: List[str] = None
auto_sell_all = False
employees = False
fight = False
@ -295,6 +295,9 @@ class Config:
telegram_chat_id = 0
telegram_token = ""
def __init__(self):
self.auto_sell = []
@property
def wt(self):
return self.work and self.train
@ -379,7 +382,7 @@ class Details:
pp = 0
pin = None
gold = 0
next_pp: List[int] = []
next_pp: List[int] = None
citizen_id = 0
citizenship = 0
current_region = 0
@ -390,6 +393,9 @@ class Details:
daily_task_reward = False
mayhem_skills = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, }
def __init__(self):
self.next_pp = []
@property
def xp_till_level_up(self):
if self.xp >= 10000:
@ -452,8 +458,8 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
"""
self._req = SlowRequests()
def post(self, url: str, *args, **kwargs) -> Response:
return self._req.post(url, *args, **kwargs)
def post(self, url: str, data=None, json=None, **kwargs) -> Response:
return self._req.post(url, data, json, **kwargs)
def get(self, url: str, **kwargs) -> Response:
return self._req.get(url, **kwargs)
@ -464,6 +470,9 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_military_battlefield_choose_side(self, battle: int, side: int) -> Response:
return self.get("{}/military/battlefield-choose-side/{}/{}".format(self.url, battle, side))
def _get_military_show_weapons(self, battle: int) -> Response:
return self.get("{}/military/show-weapons".format(self.url), params={'_token': self.token, 'battleId': battle})
def _get_candidate_party(self, party_slug: str) -> Response:
return self.post("{}/candidate/{}".format(self.url, party_slug))
@ -529,6 +538,9 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_military_campaigns(self) -> Response:
return self.get("{}/military/campaigns-new/".format(self.url))
def _get_military_campaigns_json_list(self) -> Response:
return self.get("{}/military/campaignsJson/list".format(self.url))
def _get_military_show_weapons(self, battle_id: int) -> Response:
params = {"_token": self.token, "battleId": battle_id}
return self.get("{}/military/show-weapons".format(self.url), params=params)
@ -757,6 +769,10 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(type=kind, quality=quality, duration=duration, battleId=battle, _token=self.token)
return self.post("{}/military/fight-activateBooster".format(self.url), data=data)
def _post_military_change_weapon(self, battle: int, battle_zone: int, weapon_level: int,) -> Response:
data = dict(battleId=battle, _token=self.token, battleZoneId=battle_zone, customizationLevel=weapon_level)
return self.post("{}/military/change-weapon".format(self.url), data=data)
def _post_login(self, email: str, password: str) -> Response:
data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember='on')
return self.post("{}/login".format(self.url), data=data)
@ -780,21 +796,16 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data.update(page=page)
return self.post("{}/military/battle-console".format(self.url), data=data)
def _post_military_change_weapon(self, battle_id: int, battle_zone_id: int, customization_level: int) -> Response:
data = dict(_token=self.token, battleZoneId=battle_zone_id, battleId=battle_id,
customizationLevel=customization_level)
return self.post("{}/military/change-weapon".format(self.url), data=data)
def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response:
data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token)
return self.post("{}/military/deploy-bomb".format(self.url), data=data)
def _post_military_fight_air(self, battle_id: int, side_id: int) -> Response:
data = dict(sideId=side_id, battleId=battle_id, _token=self.token)
def _post_military_fight_air(self, battle_id: int, side_id: int, zone_id: int) -> Response:
data = dict(sideId=side_id, battleId=battle_id, _token=self.token, battleZoneId=zone_id)
return self.post("{}/military/fight-shoooot/{}".format(self.url, battle_id), data=data)
def _post_military_fight_ground(self, battle_id: int, side_id: int) -> Response:
data = dict(sideId=side_id, battleId=battle_id, _token=self.token)
def _post_military_fight_ground(self, battle_id: int, side_id: int, zone_id: int) -> Response:
data = dict(sideId=side_id, battleId=battle_id, _token=self.token, battleZoneId=zone_id)
return self.post("{}/military/fight-shooot/{}".format(self.url, battle_id), data=data)
def _post_military_group_missions(self) -> Response:
@ -894,6 +905,10 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = {"_token": self.token, "post_message": body}
return self.post("{}/main/wall-post/create/json".format(self.url), data=data)
def _post_main_wall_post_automatic(self, **kwargs) -> Response:
kwargs.update(_token=self.token)
return self.post("{}/main/wall-post/create/json".format(self.url), data=kwargs)
def _post_main_wall_post_retrieve(self) -> Response:
data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post("{}/main/wall-post/retrieve/json".format(self.url), data=data)
@ -914,9 +929,19 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = {'nodeId': node_id, '_token': self.token}
return self.post("{}/main/map-rewards-claim".format(self.url), data=data)
def _post_new_war(self, self_country_id: int, attack_country_id: int, debate: str = "") -> Response:
data = dict(requirments=1, _token=self.token, debate=debate,
countryNameConfirm=utils.COUNTRY_LINK[attack_country_id])
return self.post("{}/{}/new-war".format(self.url, utils.COUNTRY_LINK[self_country_id]), data=data)
def _post_new_donation(self, country_id: int, amount: int, org_name: str, debate: str = "") -> Response:
data = dict(requirments=1, _token=self.token, debate=debate, currency=1, value=amount, commit='Propose',
type_name=org_name)
return self.post("{}/{}/new-donation".format(self.url, utils.COUNTRY_LINK[country_id]), data=data)
class Reporter:
__to_update: List[Dict[Any, Any]] = []
__to_update: List[Dict[Any, Any]] = None
name: str = ""
email: str = ""
citizen_id: int = 0
@ -932,6 +957,7 @@ class Reporter:
self._req = Session()
self.url = "https://api.erep.lv"
self._req.headers.update({"user-agent": "Bot reporter v2"})
self.__to_update = []
self.__registered: bool = False
def do_init(self, name: str = "", email: str = "", citizen_id: int = 0):
@ -1033,30 +1059,39 @@ class BattleSide:
class BattleDivision:
end: datetime.datetime
epic: bool
dom_pts: Dict[str, int] = None
wall: Dict[str, Union[int, float]] = None
dom_pts: Dict[str, int]
wall: Dict[str, Union[int, float]]
battle_zone_id: int
def_medal: Dict[str, int]
inv_medal: Dict[str, int]
@property
def div_end(self) -> bool:
return utils.now() >= self.end
def __init__(self, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int, wall_for: int, wall_dom: float):
def __init__(
self, div_id: int, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int,
wall_for: int, wall_dom: float, def_medal: Tuple[int, int], inv_medal: Tuple[int, int]
):
self.battle_zone_id = div_id
self.end = end
self.epic = epic
self.dom_pts = dict({"inv": inv_pts, "def": def_pts})
self.wall = dict({"for": wall_for, "dom": wall_dom})
self.def_medal = {"id": def_medal[0], "dmg": def_medal[1]}
self.inv_medal = {"id": inv_medal[0], "dmg": inv_medal[1]}
class Battle:
id: int = 0
war_id: int = 0
zone_id: int = 0
is_rw: bool = False
is_dict_lib: bool = False
start: datetime.datetime = None
invader: BattleSide = None
defender: BattleSide = None
div: Dict[int, BattleDivision] = None
id: int
war_id: int
zone_id: int
is_rw: bool
is_dict_lib: bool
start: datetime.datetime
invader: BattleSide
defender: BattleSide
div: Dict[int, BattleDivision]
@property
def is_air(self) -> bool:
@ -1087,11 +1122,18 @@ class Battle:
else:
end = utils.localize_dt(datetime.datetime.max - datetime.timedelta(days=1))
battle_div = BattleDivision(
end=end, epic=data.get('epic_type') in [1, 5],
inv_pts=data.get('dom_pts').get("inv"), def_pts=data.get('dom_pts').get("def"),
wall_for=data.get('wall').get("for"), wall_dom=data.get('wall').get("dom")
)
if not data['stats']['def']:
def_medal = (0, 0)
else:
def_medal = (data['stats']['def']['citizenId'], data['stats']['def']['damage'])
if not data['stats']['inv']:
inv_medal = (0, 0)
else:
inv_medal = (data['stats']['inv']['citizenId'], data['stats']['inv']['damage'])
battle_div = BattleDivision(end=end, epic=data.get('epic_type') in [1, 5], div_id=data.get('id'),
inv_pts=data.get('dom_pts').get("inv"), def_pts=data.get('dom_pts').get("def"),
wall_for=data.get('wall').get("for"), wall_dom=data.get('wall').get("dom"),
def_medal=def_medal, inv_medal=inv_medal)
self.div.update({div: battle_div})
@ -1141,17 +1183,19 @@ class EnergyToFight:
class TelegramBot:
__initialized = False
__queue: List[str] = []
__queue: List[str]
chat_id = 0
api_url = ""
player_name = ""
__thread_stopper: threading.Event = None
_last_time: datetime.datetime = None
_last_full_energy_report: datetime.datetime = None
_next_time: datetime.datetime = None
_threads: List[threading.Thread] = []
__thread_stopper: threading.Event
_last_time: datetime.datetime
_last_full_energy_report: datetime.datetime
_next_time: datetime.datetime
_threads: List[threading.Thread]
def __init__(self, stop_event: threading.Event = None):
self._threads = []
self.__queue = []
self.__thread_stopper = threading.Event() if stop_event is None else stop_event
def __dict__(self):

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.17.1
current_version = 0.17.3
commit = True
tag = True

View File

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