Compare commits

...

36 Commits

Author SHA1 Message Date
e23a67231e Bump version: 0.23.4 → 0.23.4.1 2021-01-05 16:19:43 +02:00
6a03d99abf type bugfix 2021-01-05 16:19:27 +02:00
2e344749a6 Bump version: 0.23.3.4 → 0.23.4 2021-01-05 15:50:55 +02:00
5aecefbd9d Protect those precious air boosters and 100% ground boosters 2021-01-05 15:50:38 +02:00
8b9ee5042d Bugfixes and inventory redone 2021-01-05 15:41:51 +02:00
1e93006c3d History updates 2020-12-17 18:18:46 +02:00
b13bfcdbf3 Bump version: 0.23.3.3 → 0.23.3.4 2020-12-17 18:09:43 +02:00
771dbdf826 With invalid bomb (bomb not in inventory) quit after first try 2020-12-17 18:03:04 +02:00
9646d112d2 If exception occures - set concurrency as available 2020-12-17 18:02:14 +02:00
a09c37a065 Bump version: 0.23.3.2 → 0.23.3.3 2020-12-17 15:23:39 +02:00
ba75e961fa Updated config generator 2020-12-17 15:20:21 +02:00
3b5780dbd6 Updated config generator 2020-12-17 15:18:22 +02:00
fccd0134b5 Merge branch 'remove-tox' 2020-12-17 14:07:46 +02:00
b9010fa856 Fixed MRO error 2020-12-17 14:07:29 +02:00
3e5410289e . 2020-12-17 14:03:45 +02:00
661a019b0a Broken MRO 2020-12-17 14:03:30 +02:00
23d682959d Party presidency and congress election bugfix if player is not part of a party 2020-12-17 12:13:44 +02:00
5806ccb6ca Buy food if unable to work/train because of food shortage 2020-12-14 13:51:16 +02:00
a9bc78b701 Sleep accepts floats and decimals, not only integers. Bomb deploy should use Citizen.sleep instead of utils.sleep 2020-12-11 17:22:45 +02:00
c458eb4b1c Bump version: 0.23.3.1 → 0.23.3.2 2020-12-11 11:12:34 +02:00
4af4d284c9 Updated dev requirement versions 2020-12-11 11:12:27 +02:00
104c1a0b16 Bump required python version 2020-12-11 11:12:01 +02:00
86f820771b Hits done bugfix 2020-12-11 11:11:25 +02:00
2a7af0cb7d Bump version: 0.23.3 → 0.23.3.1 2020-12-10 14:37:12 +02:00
94de509026 __all__ definitions 2020-12-10 14:36:34 +02:00
82d913bc47 Bump version: 0.23.2.11 → 0.23.3 2020-12-10 13:26:11 +02:00
c462eac369 Bomb deploy update 2020-12-10 13:25:47 +02:00
d522a4faeb Bump version: 0.23.2.10 → 0.23.2.11 2020-12-09 13:33:26 +02:00
084a47b07a TelegramBot renamed to TelegramReporter 2020-12-09 13:30:18 +02:00
5082855440 Default Telegram's token moved to TelegramReporter 2020-12-09 13:29:28 +02:00
c9971f3570 str.format() -> f-string 2020-12-09 13:28:56 +02:00
c0122eccdf Bump version: 0.23.2.9 → 0.23.2.10 2020-12-08 18:05:21 +02:00
2524173da0 Telegram bot's token can be reused 2020-12-08 18:05:18 +02:00
4a4b991faf Telegram bot's token can be reused 2020-12-08 18:05:01 +02:00
e87c48124c Bump version: 0.23.2.8 → 0.23.2.9 2020-12-07 14:10:52 +02:00
f0f47566a0 Bugfix: Don't travel to renew house if You can't afford the house 2020-12-07 14:10:40 +02:00
18 changed files with 508 additions and 273 deletions

1
.gitignore vendored
View File

@ -37,7 +37,6 @@ pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache

View File

@ -1,14 +0,0 @@
# Config file for automatic testing at travis-ci.org
language: python
python:
- 3.8
- 3.7
# Command to install dependencies, e.g. pip install -r requirements_dev.txt --use-mirrors
install: pip install -U tox-travis
# Command to run tests, e.g. python setup.py test
script: tox

View File

@ -70,14 +70,14 @@ Ready to contribute? Here's how to set up `erepublik` for local development.
Now you can make your changes locally.
5. When you're done making changes, check that your changes pass flake8 and the
tests, including testing other Python versions with tox::
5. When you're done making changes, check that your changes pass flake8, isort and the
tests::
$ flake8 erepublik tests
$ python setup.py test or py.test
$ tox
$ isort erepublik
$ python setup.py test
To get flake8 and tox, just pip install them into your virtualenv.
To get flake8 and isort, just pip install them into your virtualenv.
6. Commit your changes and push your branch to GitHub::

View File

@ -2,6 +2,25 @@
History
=======
0.23.4 (2021-01-05)
-------------------
* Added expiration data to inventory items
* Inventory is now based on `classes.Inventory`
* Requirement update to make them more flexible regarding versions required
* Restructured inventory
0.23.3 (2020-12-17)
-------------------
* Fixed carpet bombing
* Fixed hits done amount when fighting on ground
* Minor requirement updates
* Minor tweaks to method signatures
* Fixed buy food if unable to work or train because not enough energy and not enough food
* Fixed applications for party presidency and congress if not a party member
* Removed tox
* Updates to github.io config generator
* Fixed `Citizen.concurrency_available` stuck in unset state if exception is being raised while doing concurrency task
0.23.2 (2020-12-01)
-------------------
* Added concurrency checks to guard against simultaneous fighting/wam'ing/traveling
@ -17,7 +36,7 @@ History
0.23.0 (2020-11-26)
-------------------
* ***0.23 - last supported version for Python 3.7.***
* ***0.23 - last officially supported version for Python 3.7.***
* Added `Config.maverick` switch, to allow/deny automated fighting in non native divisions if the player has MaverickPack
* Added `CitizenMedia.get_article(article_id:int)` method to get article data
* Added `CitizenMedia.delete_article(article_id:int)` method to delete article

View File

@ -47,7 +47,6 @@ clean-pyc: ## remove Python file artifacts
rm -rf log/
clean-test: ## remove test and coverage artifacts
rm -fr .tox/
rm -f .coverage
rm -fr htmlcov/
rm -fr .pytest_cache
@ -58,9 +57,6 @@ lint: ## check style with flake8
test: ## run tests quickly with the default Python
python setup.py test
test-all: ## run tests on every Python version with tox
tox
coverage: ## check code coverage quickly with the default Python
coverage run --source erepublik setup.py test
coverage report -m
@ -80,6 +76,7 @@ servedocs: docs ## compile the docs watching for changes
release: dist ## package and upload a release
twine upload dist/*
clean
dist: clean ## builds source and wheel package
python setup.py sdist

View File

@ -8,15 +8,12 @@
<meta name="generator" content="Jekyll v4.0.1">
<title>eBot configuration</title>
<!-- CSS only -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<!-- JS, Popper.js, and jQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
@ -37,14 +34,14 @@
<div class="col-12 col-sm-8 col-md-6">
<h3>Login data</h3>
<div class="form-group">
<input type="email" class="form-control form-control-sm" onchange="updateJson()" id="email" placeholder="E-mail...">
<input type="password" class="form-control form-control-sm mt-3" onchange="updateJson()" id="password" disabled placeholder="Password..."
<label for="email" class="hidden"></label><input type="email" class="form-control" onchange="updateJson()" id="email" placeholder="E-mail...">
<label for="password" class="hidden"></label><input type="password" class="form-control" onchange="updateJson()" id="password" disabled placeholder="Password..."
aria-describedby="passwordHelpBlock">
<small id="passwordHelpBlock" class="form-text text-muted"><strong>NEVER</strong> enter Your passwords on 3rd party sites and <strong class="text-upper">DO NOT</strong> reuse Your
password!</small>
</div>
</div>
<div class="col-6 col-md-3">
<div class="col-6 col-sm-4 col-md-3">
<h3>Basic tasks</h3>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="work" checked>
@ -59,29 +56,6 @@
<label class="custom-control-label" for="ot">Work overtime</label>
</div>
</div>
<div class="col-6 col-md-3">
<h3>Misc</h3>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="renew_houses" checked>
<label class="custom-control-label" for="renew_houses">Auto renew houses</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="random_sleep" checked>
<label class="custom-control-label" for="random_sleep">Random sleep</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="buy_gold">
<label class="custom-control-label" for="buy_gold">Auto buy 10g</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="interactive" checked>
<label class="custom-control-label" for="interactive">Interactive</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="debug">
<label class="custom-control-label" for="debug">Debug</label>
</div>
</div>
</div>
<div class="row">
@ -200,6 +174,90 @@
<label class="custom-control-label" for="epic_hunt_ebs">Spend <small>[all]</small> EBs in epics</label>
</div>
</div>
<div class="col-12 col-sm-6">
<h3 class="mt-4">Misc</h3>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="renew_houses" checked>
<label class="custom-control-label" for="renew_houses">Auto renew houses</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="random_sleep" checked>
<label class="custom-control-label" for="random_sleep">Random sleep</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="buy_gold">
<label class="custom-control-label" for="buy_gold">Auto buy 10g</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="interactive" checked>
<label class="custom-control-label" for="interactive">Interactive</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="debug">
<label class="custom-control-label" for="debug">Debug</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="spin_wheel_of_fortune">
<label class="custom-control-label" for="spin_wheel_of_fortune">Auto spin 10% of cc in WheelOfFortune</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="congress">
<label class="custom-control-label" for="congress">Auto candidate for congress</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="party_president">
<label class="custom-control-label" for="party_president">Auto candidate for party presidency</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="contribute_cc">
<label class="custom-control-label" for="contribute_cc">Contribute cc to country's account (weekly)</label>
</div>
</div>
<div class="col-12 col-sm-6">
<h3 class="mt-4">Advanced</h3>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="telegram">
<label class="custom-control-label" for="telegram">Notify trough Telegram</label>
</div>
<label for="telegram_chat_id">Telegram's chat ID</label>
<input type="text" class="form-control" onchange="updateJson()" id="telegram_chat_id" placeholder="Chat ID">
<label for="telegram_token">Telegram Bot token</label>
<input type="text" class="form-control" onchange="updateJson()" id="telegram_token" placeholder="864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o">
<small id="telegramTokenHelp" class="form-text text-muted">Only enter token if You want to use your own Telegram bot for notification sending</small>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="proxy">
<label class="custom-control-label" for="proxy">Use proxy</label>
</div>
<label for="proxy_kind">Proxy kind</label>
<div class="form-group">
<select class="form-control custom-select" id="proxy_kind">
<option value="socks" selected>SOCKS5</option>
<option value="http">HTTP</option>
</select>
</div>
<div class="form-group">
<label for="proxy_host">Proxy hostname or IP address</label>
<input type="text" class="form-control" onchange="updateJson()" id="proxy_host" placeholder="localhost">
</div>
<div class="form-group">
<label for="proxy_port">Proxy port</label>
<input type="text" class="form-control" onchange="updateJson()" id="proxy_port" placeholder="8080">
</div>
<div class="form-group">
<label for="proxy_user">Proxy username (optional)</label>
<input type="text" class="form-control" onchange="updateJson()" id="proxy_user" placeholder="user">
</div>
<div class="form-group">
<label for="proxy_password">Proxy password (optional)</label>
<input type="password" class="form-control" onchange="updateJson()" id="proxy_password" placeholder="password" disabled>
<small id="proxyHelpBlock" class="form-text text-muted"><strong>NEVER</strong> enter Your passwords on 3rd party sites and <strong class="text-upper">DO NOT</strong> reuse Your password!</small>
</div>
</div>
</div>
</form>
</div>
@ -212,7 +270,9 @@
function disable(element){
element.checked = false;
element.disabled = true;
element.value = null;
}
function updateJson() {
let config = {};
let email = document.getElementById('email'); // Generated
@ -228,6 +288,14 @@
config.renew_houses = renew_houses.checked;
let random_sleep = document.getElementById('random_sleep'); // Generated
config.random_sleep = random_sleep.checked;
let spin_wheel_of_fortune = document.getElementById('spin_wheel_of_fortune'); // Generated
config.spin_wheel_of_fortune = spin_wheel_of_fortune.checked;
let congress = document.getElementById('congress'); // Generated
config.congress = congress.checked;
let party_president = document.getElementById('party_president'); // Generated
config.party_president = party_president.checked;
let contribute_cc = document.getElementById('contribute_cc'); // Generated
config.contribute_cc = contribute_cc.checked ? 10000 : false;
let buy_gold = document.getElementById('buy_gold'); // Generated
config.buy_gold = buy_gold.checked;
let interactive = document.getElementById('interactive'); // Generated
@ -297,7 +365,7 @@
let travel_to_fight = document.getElementById('travel_to_fight'); // Generated
let epic_hunt = document.getElementById('epic_hunt'); // Generated
let epic_hunt_ebs = document.getElementById('epic_hunt_ebs'); // Generated
if (config.fight){
if (config.fight) {
air.disabled = false;
ground.disabled = false;
boosters.disabled = false;
@ -334,6 +402,60 @@
config.travel_to_fight = travel_to_fight.checked;
config.epic_hunt = epic_hunt.checked;
config.epic_hunt_ebs = config.epic_hunt ? epic_hunt_ebs.checked : config.epic_hunt;
// Advanced
let telegram = document.getElementById('telegram'); // Generated
config.telegram = telegram.checked;
let telegram_chat_id = document.getElementById('telegram_chat_id'); // Generated
let telegram_token = document.getElementById('telegram_token'); // Generated
if (config.telegram) {
telegram_chat_id.disabled = false;
telegram_token.disabled = false;
} else {
disable(telegram_chat_id);
disable(telegram_token);
}
config.telegram_chat_id = telegram_chat_id.value;
config.telegram_token = telegram_token.value;
let _proxy = {};
let proxy = document.getElementById('proxy'); // Generated
let proxy_kind = document.getElementById('proxy_kind'); // Generated
let proxy_host = document.getElementById('proxy_host'); // Generated
let proxy_port = document.getElementById('proxy_port'); // Generated
let proxy_user = document.getElementById('proxy_user'); // Generated
if (proxy.checked) {
proxy_kind.disabled = false;
proxy_host.disabled = false;
proxy_port.disabled = false;
proxy_user.disabled = false;
} else {
disable(proxy_kind);
disable(proxy_host);
disable(proxy_port);
disable(proxy_user);
}
_proxy.kind = proxy_kind.value;
_proxy.host = proxy_host.value;
_proxy.port = proxy_port.value;
_proxy.username = proxy_user.value;
_proxy.password = ""
if (proxy.checked) {
delete config._proxy;
config.proxy = _proxy;
} else {
delete config.proxy;
config._proxy = {
'kind': 'socks or http',
'host': 'localhost',
'port': 8080,
'username': 'optional',
'password': 'optional'
}
}
let pre = document.getElementById('json-output');
pre.textContent = JSON.stringify(config, null, 2);
}

View File

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

View File

@ -15,10 +15,10 @@ class SlowRequests(Session):
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/86.0.4240.183 Safari/537.36', # noqa
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36', # noqa
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36', # noqa
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36', # noqa
'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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 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',
@ -81,8 +81,7 @@ class SlowRequests(Session):
if params:
args.update({"params": params})
body = "[{dt}]\tURL: '{url}'\tMETHOD: {met}\tARGS: {args}\n".format(dt=utils.now().strftime("%F %T"),
url=url, met=method, args=args)
body = f"[{utils.now().strftime('%F %T')}]\tURL: '{url}'\tMETHOD: {method}\tARGS: {args}\n"
with open(self.request_log_name, 'ab') as file:
file.write(body.encode("UTF-8"))
pass
@ -95,20 +94,18 @@ class SlowRequests(Session):
self._log_request(hist_resp.request.url, "REDIRECT")
self._log_response(hist_resp.request.url, hist_resp, redirect=True)
file_data = {
"path": 'debug/requests',
"time": self.last_time.strftime('%Y/%m/%d/%H-%M-%S'),
"name": utils.slugify(url[len(Citizen.url):]),
"extra": "_REDIRECT" if redirect else ""
}
fd_path = 'debug/requests'
fd_time = self.last_time.strftime('%Y/%m/%d/%H-%M-%S')
fd_name = utils.slugify(url[len(Citizen.url):])
fd_extra = "_REDIRECT" if redirect else ""
try:
utils.json.loads(resp.text)
file_data.update({"ext": "json"})
fd_ext = 'json'
except utils.json.JSONDecodeError:
file_data.update({"ext": "html"})
fd_ext = 'html'
filename = 'debug/requests/{time}_{name}{extra}.{ext}'.format(**file_data)
filename = f'{fd_path}/{fd_time}_{fd_name}{fd_extra}.{fd_ext}'
utils.write_file(filename, resp.text)
pass
@ -288,7 +285,6 @@ class ErepublikCountryAPI(CitizenBaseAPI):
def _post_main_country_donate(self, country_id: int, action: str, value: Union[int, float],
quality: int = None) -> Response:
data = dict(countryId=country_id, action=action, _token=self.token, value=value, quality=quality)
return self.post(f"{self.url}/main/country-donate", data=data,
headers={"Referer": f"{self.url}/country/economy/Latvia"})
@ -363,16 +359,20 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
class ErepublikLeaderBoardAPI(CitizenBaseAPI):
def _get_main_leaderboards_damage_aircraft_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0) -> Response: # noqa
def _get_main_leaderboards_damage_aircraft_rankings(self, country_id: int, weeks: int = 0,
mu_id: int = 0) -> Response: # noqa
return self.get(f"{self.url}/main/leaderboards-damage-aircraft-rankings/{country_id}/{weeks}/{mu_id}/0")
def _get_main_leaderboards_damage_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0, div: int = 0) -> Response: # noqa
def _get_main_leaderboards_damage_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0,
div: int = 0) -> Response: # noqa
return self.get(f"{self.url}/main/leaderboards-damage-rankings/{country_id}/{weeks}/{mu_id}/{div}")
def _get_main_leaderboards_kills_aircraft_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0) -> Response: # noqa
def _get_main_leaderboards_kills_aircraft_rankings(self, country_id: int, weeks: int = 0,
mu_id: int = 0) -> Response: # noqa
return self.get(f"{self.url}/main/leaderboards-kills-aircraft-rankings/{country_id}/{weeks}/{mu_id}/0")
def _get_main_leaderboards_kills_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0, div: int = 0) -> Response: # noqa
def _get_main_leaderboards_kills_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0,
div: int = 0) -> Response: # noqa
return self.get(f"{self.url}/main/leaderboards-kills-rankings/{country_id}/{weeks}/{mu_id}/{div}")
@ -438,8 +438,9 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
data.update(page=page)
return self.post(f"{self.url}/military/battle-console", 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)
def _post_military_deploy_bomb(self, battle_id: int, division_id: int, side_id: int, bomb_id: int) -> Response:
data = dict(battleId=battle_id, battleZoneId=division_id, sideId=side_id, sideCountryId=side_id,
bombId=bomb_id, _token=self.token)
return self.post(f"{self.url}/military/deploy-bomb", data=data)
def _post_military_fight_air(self, battle_id: int, side_id: int, zone_id: int) -> Response:
@ -483,7 +484,7 @@ class ErepublikPoliticsAPI(CitizenBaseAPI):
class ErepublikPresidentAPI(CitizenBaseAPI):
def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response:
data = {'_token': self.token, 'warId': war_id, 'regionName': region_name, 'regionNameConfirm': region_name}
return self.post('{}/wars/attack-region/{}/{}'.format(self.url, war_id, region_id), data=data)
return self.post(f'{self.url}/wars/attack-region/{war_id}/{region_id}', 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,

View File

@ -2,7 +2,7 @@ import re
import sys
import warnings
import weakref
from datetime import datetime, timedelta
from datetime import datetime, timedelta, time
from decimal import Decimal
from itertools import product
from threading import Event
@ -11,7 +11,7 @@ from typing import Any, Dict, List, NoReturn, Optional, Set, Tuple, Union
from requests import HTTPError, RequestException, Response
from . import utils, classes, access_points, constants
from . import access_points, classes, constants, utils, types
from .classes import OfferItem
@ -20,9 +20,7 @@ class BaseCitizen(access_points.CitizenAPI):
_last_inventory_update: datetime = constants.min_datetime
promos: Dict[str, datetime] = None
inventory: Dict[str, Dict[str, Dict[int, Dict[str, Union[str, int, float]]]]]
inventory_status: Dict[str, int]
boosters: Dict[int, Dict[int, int]] = {50: {}, 100: {}}
inventory: classes.Inventory
ot_points: int = 0
food: Dict[str, int] = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
@ -43,7 +41,7 @@ class BaseCitizen(access_points.CitizenAPI):
reporter: classes.Reporter = None
stop_threads: Event = None
concurrency_available: Event = None
telegram: classes.TelegramBot = None
telegram: classes.TelegramReporter = None
r: Response = None
name: str = "Not logged in!"
@ -61,12 +59,11 @@ class BaseCitizen(access_points.CitizenAPI):
self.stop_threads = Event()
self.concurrency_available = Event()
self.concurrency_available.set()
self.telegram = classes.TelegramBot(stop_event=self.stop_threads)
self.telegram = classes.TelegramReporter(stop_event=self.stop_threads)
self.config.email = email
self.config.password = password
self.inventory = {}
self.inventory_status = dict(used=0, total=0)
self.inventory = classes.Inventory()
self.wheel_of_fortune = False
def get_csrf_token(self):
@ -248,7 +245,7 @@ class BaseCitizen(access_points.CitizenAPI):
"""
self._update_inventory_data(self._get_economy_inventory_items().json())
def get_inventory(self, force: bool = False):
def get_inventory(self, force: bool = False) -> classes.Inventory:
if utils.good_timedelta(self._last_inventory_update, timedelta(minutes=2)) < self.now or force:
self.update_inventory()
return self.inventory
@ -257,17 +254,25 @@ class BaseCitizen(access_points.CitizenAPI):
if not isinstance(inv_data, dict):
raise TypeError("Parameter `inv_data` must be dict not '{type(data)}'!")
def _expire_value_to_python(_expire_value: str) -> Dict[str, Union[int, datetime]]:
_data = re.search(
r'((?P<amount>\d+) item\(s\) )?[eE]xpires? on Day (?P<eday>\d,\d{3}), (?P<time>\d\d:\d\d)',
_expire_value).groupdict()
eday = utils.date_from_eday(int(_data['eday'].replace(',', '')))
dt = constants.erep_tz.localize(datetime.combine(eday, time(*[int(_) for _ in _data['time'].split(':')])))
return {'amount': _data.get('amount'), 'expiration': dt}
status = inv_data.get("inventoryStatus", {})
if status:
self.inventory_status.clear()
self.inventory_status.update(used=status.get("usedStorage"), total=status.get("totalStorage"))
self.inventory.used = status.get("usedStorage")
self.inventory.total = status.get("totalStorage")
data = inv_data.get('inventoryItems', {})
if not data:
return
self._last_inventory_update = self.now
self.food.update({"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0})
self.eb_small = self.eb_double = self.eb_normal = 0
active_items: Dict[str, Dict[int, Dict[str, Union[str, int]]]] = {}
active_items: types.InvFinal = {}
if data.get("activeEnhancements", {}).get("items", {}):
for item_data in data.get("activeEnhancements", {}).get("items", {}).values():
if item_data.get('token'):
@ -278,29 +283,34 @@ class BaseCitizen(access_points.CitizenAPI):
kind = constants.INDUSTRIES[constants.INDUSTRIES[kind]]
if kind not in active_items:
active_items[kind] = {}
expiration_info = []
if item_data.get('attributes').get('expirationInfo'):
expire_info = item_data.get('attributes').get('expirationInfo')
expiration_info = [_expire_value_to_python(v) for v in expire_info['value']]
icon = item_data['icon'] if item_data[
'icon'] else "//www.erepublik.net/images/modules/manager/tab_storage.png"
item_data = dict(name=item_data.get("name"), time_left=item_data['active']['time_left'], icon=icon,
kind=kind,
quality=item_data.get("quality", 0))
inv_item: types.InvFinalItem = dict(
name=item_data.get("name"), time_left=item_data['active']['time_left'], icon=icon,
kind=kind, expiration=expiration_info, quality=item_data.get("quality", 0)
)
if item_data.get('isPackBooster'):
active_items[kind].update({0: item_data})
active_items[kind].update({0: inv_item})
else:
active_items[kind].update({item_data.get("quality"): item_data})
active_items[kind].update({inv_item.get("quality"): inv_item})
final_items: Dict[str, Dict[int, Dict[str, Union[str, int]]]] = {}
final_items: types.InvFinal = {}
boosters: types.InvBooster = {}
if data.get("finalProducts", {}).get("items", {}):
for item_data in data.get("finalProducts", {}).get("items", {}).values():
is_booster: bool = False
name = item_data['name']
if item_data.get('type'):
if item_data.get('type') in ['damageBoosters', "aircraftDamageBoosters"]:
kind = f"{item_data['type']}{item_data['quality']}"
if item_data['quality'] == 5:
self.boosters[50].update({item_data['duration']: item_data['amount']})
elif item_data['quality'] == 10:
self.boosters[100].update({item_data['duration']: item_data['amount']})
# in ['damageBoosters', "aircraftDamageBoosters", 'prestigePointsBoosters']
if item_data.get('type').endswith('Boosters'):
is_booster = True
kind = item_data['type']
delta = item_data['duration']
if delta // 3600:
@ -337,8 +347,14 @@ class BaseCitizen(access_points.CitizenAPI):
if constants.INDUSTRIES[kind]:
kind = constants.INDUSTRIES[constants.INDUSTRIES[kind]]
if kind not in final_items:
final_items[kind] = {}
if is_booster:
if kind not in boosters:
boosters[kind] = {}
if item_data.get('quality', 0) not in boosters[kind]:
boosters[kind][item_data['quality']] = {}
else:
if kind not in final_items:
final_items[kind] = {}
if item_data['icon']:
icon = item_data['icon']
@ -355,10 +371,23 @@ class BaseCitizen(access_points.CitizenAPI):
icon = "/images/modules/pvp/ghost_boosters/icon_booster_30_60.png"
else:
icon = "//www.erepublik.net/images/modules/manager/tab_storage.png"
_item_data = dict(kind=kind, quality=item_data.get('quality', 0), amount=item_data.get('amount', 0),
durability=item_data.get('duration', 0), icon=icon, name=name)
if item_data.get('type') in ('damageBoosters', "aircraftDamageBoosters"):
_item_data = {_item_data['durability']: _item_data}
expiration_info = []
if item_data.get('attributes'):
if item_data.get('attributes').get('expirationInfo'):
expire_info = item_data.get('attributes').get('expirationInfo')
expiration_info = [_expire_value_to_python(v) for v in expire_info['value']]
elif item_data.get('attributes').get('expiration'):
_exp = item_data.get('attributes').get('expiration')
exp_dt = (utils.date_from_eday(int(_exp['value'].replace(',', ''))))
expiration_info = [{'amount': item_data.get('amount'), 'expiration': exp_dt}]
_inv_item: Dict[int, types.InvFinalItem]
inv_item: types.InvFinalItem = dict(
kind=kind, quality=item_data.get('quality', 0), icon=icon, expiration=expiration_info,
amount=item_data.get('amount'), durability=item_data.get('duration', 0), name=name
)
if is_booster:
_inv_item = {inv_item['durability']: inv_item}
else:
if item_data.get('type') == 'bomb':
firepower = 0
@ -367,11 +396,14 @@ class BaseCitizen(access_points.CitizenAPI):
except AttributeError:
pass
finally:
_item_data.update(fire_power=firepower)
_item_data = {_item_data['quality']: _item_data}
final_items[kind].update(_item_data)
inv_item.update(fire_power=firepower)
_inv_item = {inv_item['quality']: inv_item}
if is_booster:
boosters[kind][inv_item['quality']].update(_inv_item)
else:
final_items[kind].update(_inv_item)
raw_materials: Dict[str, Dict[int, Dict[str, Union[str, int]]]] = {}
raw_materials: types.InvRaw = {}
if data.get("rawMaterials", {}).get("items", {}):
for item_data in data.get("rawMaterials", {}).get("items", {}).values():
if item_data['isPartial']:
@ -401,8 +433,11 @@ class BaseCitizen(access_points.CitizenAPI):
offers[kind] = {}
offers[kind].update(offer_data)
self.inventory.clear()
self.inventory.update(active=active_items, final=final_items, raw=raw_materials, offers=offers)
self.inventory.active = active_items
self.inventory.final = final_items
self.inventory.boosters = boosters
self.inventory.raw = raw_materials
self.inventory.offers = offers
self.food["total"] = sum([self.food[q] * constants.FOOD_ENERGY[q] for q in constants.FOOD_ENERGY])
def write_log(self, *args, **kwargs):
@ -417,7 +452,7 @@ class BaseCitizen(access_points.CitizenAPI):
else:
utils.process_error(msg, self.name, sys.exc_info(), self, None, None)
def sleep(self, seconds: int):
def sleep(self, seconds: Union[int, float, Decimal]):
if seconds < 0:
seconds = 0
if self.config.interactive:
@ -607,8 +642,8 @@ class BaseCitizen(access_points.CitizenAPI):
except AttributeError:
continue
if data:
msgs = ["{count} x {kind}, totaling {} {currency}\n"
"{about}".format(d["count"] * d["reward"], **d) for d in data.values()]
msgs = [f"{d['count']} x {d['kind']}, totaling {d['count'] * d['reward']} "
f"{d['currency']}" for d in data.values()]
msgs = "\n".join(msgs)
if self.config.telegram:
@ -946,13 +981,13 @@ class CitizenCompanies(BaseCitizen):
raw_factories = wam_holding.get_wam_companies(raw_factory=True)
fin_factories = wam_holding.get_wam_companies(raw_factory=False)
free_inventory = self.inventory_status["total"] - self.inventory_status["used"]
free_inventory = self.inventory.total - self.inventory.used
wam_list = raw_factories + fin_factories
wam_list = wam_list[:self.energy.food_fights]
if int(free_inventory * 0.75) < self.my_companies.get_needed_inventory_usage(wam_list):
self.update_inventory()
free_inventory = self.inventory_status["total"] - self.inventory_status["used"]
free_inventory = self.inventory.total - self.inventory.used
while wam_list and free_inventory < self.my_companies.get_needed_inventory_usage(wam_list):
wam_list.pop(-1)
@ -1021,7 +1056,7 @@ class CitizenEconomy(CitizenTravel):
def check_house_durability(self) -> Dict[int, datetime]:
ret = {}
inv = self.get_inventory()
for house_quality, active_house in inv['active'].get('House', {}).items():
for house_quality, active_house in inv.active.get('House', {}).items():
till = utils.good_timedelta(self.now, timedelta(seconds=active_house['time_left']))
ret.update({house_quality: till})
return ret
@ -1030,7 +1065,7 @@ class CitizenEconomy(CitizenTravel):
original_region = self.details.current_country, self.details.current_region
ok_to_activate = False
inv = self.get_inventory()
if not inv['final'].get('House', {}).get(q, {}):
if not inv.final.get('House', {}).get(q, {}):
countries = [self.details.citizenship, ]
if self.details.current_country != self.details.citizenship:
countries.append(self.details.current_country)
@ -1039,16 +1074,22 @@ class CitizenEconomy(CitizenTravel):
global_cheapest = self.get_market_offers("House", q)[f"q{q}"]
if global_cheapest.price + 200 < local_cheapest.price:
if self.travel_to_country(global_cheapest.country):
buy = self.buy_market_offer(global_cheapest, 1)
if global_cheapest.price + 2000 < self.details.cc:
if self.travel_to_country(global_cheapest.country):
buy = self.buy_market_offer(global_cheapest, 1)
else:
buy = dict(error=True, message='Unable to travel!')
else:
buy = {'error': True, 'message': 'Unable to travel!'}
buy = dict(error=True, message='Not enough money to buy house!')
else:
buy = self.buy_market_offer(local_cheapest, 1)
if local_cheapest.price < self.details.cc:
buy = self.buy_market_offer(local_cheapest, 1)
else:
buy = dict(error=True, message='Not enough money to buy house!')
if buy is None:
pass
elif buy["error"]:
msg = f"Unable to buy q{q} house! \n{buy['message']}"
msg = f'Unable to buy q{q} house! \n{buy["message"]}'
self.write_log(msg)
else:
ok_to_activate = True
@ -1078,7 +1119,8 @@ class CitizenEconomy(CitizenTravel):
r: Dict[str, Any] = self._post_economy_activate_house(quality).json()
self._update_inventory_data(r)
if r.get("status") and not r.get("error"):
house: Dict[str, Union[int, str]] = self.get_inventory()['active']['House'][quality]
inventory = self.get_inventory()
house = inventory.active.get('House', {}).get(quality)
time_left = timedelta(seconds=house["time_left"])
active_until = utils.good_timedelta(self.now, time_left)
self._report_action(
@ -1158,12 +1200,14 @@ class CitizenEconomy(CitizenTravel):
self.write_log(f"Trying to sell unsupported industry {industry}")
_inv_qlt = quality if industry in [1, 2, 3, 4, 23] else 0
_kind = 'final' if industry in [1, 2, 4, 23] else 'raw'
final_kind = industry in [1, 2, 4, 23]
inventory = self.get_inventory()
items = inventory[_kind].get(constants.INDUSTRIES[industry], {_inv_qlt: {'amount': 0}})
items = (inventory.final if final_kind else inventory.raw).get(constants.INDUSTRIES[industry],
{_inv_qlt: {'amount': 0}})
if items[_inv_qlt]['amount'] < amount:
inventory = self.get_inventory(True)
items = inventory[_kind].get(constants.INDUSTRIES[industry], {_inv_qlt: {'amount': 0}})
items = (inventory.final if final_kind else inventory.raw).get(constants.INDUSTRIES[industry],
{_inv_qlt: {'amount': 0}})
if items[_inv_qlt]['amount'] < amount:
self._report_action("ECONOMY_SELL_PRODUCTS", "Unable to sell! Not enough items in storage!",
kwargs=dict(inventory=items[_inv_qlt], amount=amount))
@ -1465,9 +1509,8 @@ class CitizenMedia(BaseCitizen):
article_id = 0
return article_id
else:
raise classes.ErepublikException("Article kind must be one of:\n{}\n'{}' is not supported".format(
"\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), kind
))
kinds = "\n".join([f"{k}: {v}" for k, v in kinds.items()])
raise classes.ErepublikException(f"Article kind must be one of:\n{kinds}\n'{kind}' is not supported")
def get_article(self, article_id: int) -> Dict[str, Any]:
return self._get_main_article_json(article_id).json()
@ -1783,7 +1826,7 @@ class CitizenMilitary(CitizenTravel):
self._report_action("IP_BLACKLISTED", "Fighting is not allowed from restricted IP!")
return 1
if not division.is_air and self.config.boosters:
self.activate_dmg_booster()
self.activate_damage_booster(not division.is_air)
if side is None:
side = battle.defender if self.details.citizenship in battle.defender.allies + [
battle.defender.country] else battle.invader
@ -1806,7 +1849,7 @@ class CitizenMilitary(CitizenTravel):
else:
self._eat('blue')
if self.energy.recovered < 50 or error_count >= 10 or count <= 0:
self.write_log("Hits: {:>4} | Damage: {}".format(total_hits, total_damage))
self.write_log(f"Hits: {total_hits:>4} | Damage: {total_damage}")
ok_to_fight = False
if total_damage:
self.reporter.report_fighting(battle, not side.is_defender, division, total_damage, total_hits)
@ -1858,7 +1901,18 @@ class CitizenMilitary(CitizenTravel):
self.travel_to_battle(battle, countries)
err = True
elif r_json.get("message") == "ENEMY_KILLED":
hits = (self.energy.recovered - r_json["details"]["wellness"]) // 10
# Non-InfantryKit players
if r_json['user']['earnedXp']:
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
else:
hits = r_json['hits']
if r_json['user']['epicBattle']:
hits /= 1 + r_json['user']['epicBattle']
self.energy.recovered = 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)
@ -1868,14 +1922,20 @@ class CitizenMilitary(CitizenTravel):
return hits, err, damage
@utils.wait_for_lock
def deploy_bomb(self, battle: classes.Battle, bomb_id: int, inv_side: bool = None, count: int = 1) -> Optional[int]:
def deploy_bomb(self, battle: classes.Battle, division: classes.BattleDivision, bomb_id: int, inv_side: bool,
count: int = 1) -> Optional[int]:
"""Deploy bombs in a battle for given side.
:param battle: Battle
:type battle: Battle
:type battle: classes.Battle
:param division: BattleDivision
:type division: classes.BattleDivision
:param bomb_id: int bomb id
:param inv_side: should deploy on invader side, if None then will deploy in currently available side
:param count: int how many bombs to deploy
:type bomb_id: int
:param inv_side: should deploy on invader side
:type inv_side: bool
:param count: how many bombs to deploy
:type count: int
:return: Deployed count
:rtype: int
"""
@ -1890,22 +1950,22 @@ class CitizenMilitary(CitizenTravel):
good_countries = [battle.invader.country] + battle.invader.deployed
if self.details.current_country not in good_countries:
has_traveled = self.travel_to_battle(battle, good_countries)
elif inv_side is not None:
good_countries = [battle.defender.country] + battle.defender.deployed
if self.details.current_country not in good_countries:
has_traveled = self.travel_to_battle(battle, good_countries)
else:
involved = [battle.invader.country,
battle.defender.country] + battle.invader.deployed + battle.defender.deployed
if self.details.current_country not in involved:
count = 0
side = battle.invader if inv_side else battle.defender
errors = deployed_count = 0
while (not deployed_count == count) and errors < 10:
r = self._post_military_deploy_bomb(battle.id, bomb_id).json()
r = self._post_military_deploy_bomb(battle.id, division.id, side.id, bomb_id).json()
if not r.get('error'):
deployed_count += 1
self.sleep(0.5)
elif r.get('message') == 'LOCKED':
sleep(0.5)
self.sleep(0.5)
elif r.get('message') == 'INVALID_BOMB':
errors = 10
else:
errors += 1
@ -1958,34 +2018,45 @@ class CitizenMilitary(CitizenTravel):
return utils.calculate_hit(0, rang, True, elite, ne, 0, 20 if weapon else 0)
def activate_dmg_booster(self):
if self.config.boosters:
if not self.get_active_ground_damage_booster():
duration = 0
for length, amount in self.boosters[50].items():
if amount > 2:
duration = length
def activate_damage_booster(self, ground: bool = True):
kind = 'damageBoosters' if ground else 'aircraftDamageBoosters'
if self.config.boosters and not self.get_active_damage_booster(ground):
booster: Optional[types.InvFinalItem] = None
for quality, data in sorted(self.inventory.boosters.get(kind, {}).items(), key=lambda x: x[0]):
for _duration, _booster in sorted(data.items(), key=lambda y: y[0]):
critical_amount = 2 if quality < 10 and ground else 10
if _booster.get('amount') > critical_amount:
booster = _booster
break
if duration:
self._report_action("MILITARY_BOOSTER", f"Activated 50% {duration / 60}h damage booster")
self._post_economy_activate_booster(5, duration, "damage")
break
if booster:
self._report_action("MILITARY_BOOSTER", f"Activated {booster['name']}")
self._post_economy_activate_booster(booster['quality'], booster['durability'],
'damage' if ground else 'air_damage')
def get_active_damage_booster(self, ground: bool = True) -> int:
kind = 'damageBoosters' if ground else 'aircraftDamageBoosters'
inventory = self.get_inventory()
boosters = inventory.active.get(kind, {})
quality = 0
for q, boost in boosters.items():
if boost['quality'] * 10 > quality:
quality = boost['quality'] * 10
return quality
def get_active_ground_damage_booster(self) -> int:
inventory = self.get_inventory()
quality = 0
if inventory['active'].get('damageBoosters', {}).get(10):
quality = 100
elif inventory['active'].get('damageBoosters', {}).get(5):
quality = 50
return quality
return self.get_active_damage_booster(True)
def get_active_air_damage_booster(self) -> int:
return self.get_active_damage_booster(False)
def activate_battle_effect(self, battle_id: int, kind: str) -> Response:
self._report_action('MILITARY_BOOSTER', f'Activated {kind} booster')
return self._post_main_activate_battle_effect(battle_id, kind, self.details.citizen_id)
def activate_pp_booster(self, battle_id: int) -> Response:
self._report_action('MILITARY_BOOSTER', 'Activated PrestigePoint booster')
return self._post_military_fight_activate_booster(battle_id, 1, 180, "prestige_points")
def activate_pp_booster(self, pp_item: types.InvFinalItem) -> Response:
self._report_action('MILITARY_BOOSTER', f'Activated {pp_item["name"]}')
return self._post_economy_activate_booster(pp_item['quality'], pp_item['durability'], 'prestige_points')
def _rw_choose_side(self, battle: classes.Battle, side: classes.BattleSide) -> Response:
return self._post_main_battlefield_travel(side.id, battle.id)
@ -2155,13 +2226,22 @@ class CitizenPolitics(BaseCitizen):
ret.update({int(id_): name})
return ret
def candidate_for_congress(self, presentation: str = "") -> Response:
self._report_action('POLITIC_CONGRESS', 'Applied for congress elections')
return self._post_candidate_for_congress(presentation)
def candidate_for_party_presidency(self) -> Optional[Response]:
if self.politics.is_party_member:
self._report_action('POLITIC_PARTY_PRESIDENT', 'Applied for party president elections')
return self._get_candidate_party(self.politics.party_slug)
else:
self._report_action('POLITIC_CONGRESS',
'Unable to apply for party president elections - not a party member')
return None
def candidate_for_party_presidency(self) -> Response:
self._report_action('POLITIC_PARTY_PRESIDENT', 'Applied for party president elections')
return self._get_candidate_party(self.politics.party_slug)
def candidate_for_congress(self, presentation: str = "") -> Optional[Response]:
if self.politics.is_party_member:
self._report_action('POLITIC_CONGRESS', 'Applied for congress elections')
return self._post_candidate_for_congress(presentation)
else:
self._report_action('POLITIC_CONGRESS', 'Unable to apply for congress elections - not a party member')
return None
def get_country_president_election_result(
self, country: constants.Country, year: int, month: int
@ -2285,7 +2365,7 @@ class CitizenSocial(BaseCitizen):
return self._get_main_city_data_residents(city_id, params={"search": name}).json()
class CitizenTasks(BaseCitizen):
class CitizenTasks(CitizenEconomy):
tg_contract: dict = {}
ot_points: int = 0
next_ot_time: datetime = None
@ -2303,6 +2383,8 @@ class CitizenTasks(BaseCitizen):
if js.get('message') in ['employee', 'money']:
self.resign_from_employer()
self.find_new_job()
elif js.get('message') in ['not_enough_health_food']:
self.buy_food(120)
self.update_citizen_info()
self.work()
else:
@ -2311,7 +2393,7 @@ class CitizenTasks(BaseCitizen):
self._eat("blue")
if self.energy.food_fights < 1:
seconds = (self.energy.reference_time - self.now).total_seconds()
self.write_log("I don't have energy to work. Will sleep for {}s".format(seconds))
self.write_log(f"I don't have energy to work. Will sleep for {seconds}s")
self.sleep(seconds)
self._eat("blue")
self.work()
@ -2357,6 +2439,8 @@ class CitizenTasks(BaseCitizen):
else:
if r.json().get('message') == 'employee':
self.find_new_job()
elif r.json().get('message') == 'not_enough_health_food':
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")
@ -2413,8 +2497,8 @@ class CitizenTasks(BaseCitizen):
self.ot_points = ot.get("points", 0)
class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeaderBoard,
CitizenMedia, CitizenMilitary, CitizenPolitics, CitizenSocial, CitizenTasks):
class Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
CitizenMedia, CitizenPolitics, CitizenSocial, CitizenMilitary, CitizenTasks):
def __init__(self, email: str = "", password: str = "", auto_login: bool = False):
super().__init__(email, password)
self._last_full_update = constants.min_datetime
@ -2442,9 +2526,10 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self.update_citizen_info()
self.reporter.do_init()
if self.config.telegram:
if self.config.telegram_chat_id and self.config.telegram_token:
self.telegram.do_init(self.config.telegram_chat_id, self.config.telegram_token, self.name)
if self.config.telegram and self.config.telegram_chat_id:
self.telegram.do_init(self.config.telegram_chat_id,
self.config.telegram_token,
self.name)
self.telegram.send_message(f"*Started* {utils.now():%F %T}")
self.update_all(True)
@ -2501,8 +2586,8 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
data[(title, reward)]['count'] += count
self._post_main_global_alerts_close(medal.get('id'))
if data:
msgs = ["{count} x {kind},"
" totaling {} {currency}".format(d["count"] * d["reward"], **d) for d in data.values()]
msgs = [f"{d['count']} x {d['kind']}, totaling {d['count'] * d['reward']} "
f"{d['currency']}" for d in data.values()]
msgs = "\n".join(msgs)
if self.config.telegram:
@ -2562,7 +2647,8 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
f"(Recoverable until WC end {max_count}hp | want to do {count}hits)")
count = count if max_count > count else max_count
self.write_log(log_msg, False)
if not silent:
self.write_log(log_msg, False)
return count, log_msg, force_fight
@ -2595,13 +2681,13 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
def send_state_update(self):
data = dict(xp=self.details.xp, cc=self.details.cc, gold=self.details.gold, pp=self.details.pp,
inv_total=self.inventory_status['total'], inv=self.inventory_status['used'],
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'], )
self.reporter.send_state_update(**data)
def send_inventory_update(self):
self.reporter.report_action("INVENTORY", json_val=self.get_inventory(True))
self.reporter.report_action("INVENTORY", json_val=self.get_inventory(True).as_dict)
def send_my_companies_update(self):
self.reporter.report_action('COMPANIES', json_val=self.my_companies.as_dict)
@ -2635,7 +2721,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
if not amount:
inv_resp = self._get_economy_inventory_items().json()
category = "rawMaterials" if kind.endswith("Raw") else "finalProducts"
item = "{}_{}".format(constants.INDUSTRIES[kind], quality)
item = f"{constants.INDUSTRIES[kind]}_{quality}"
amount = inv_resp.get("inventoryItems").get(category).get("items").get(item).get("amount", 0)
if amount >= 1:
@ -2664,7 +2750,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
elif kind.endswith("Raw"):
self.sell_produced_product(kind, 1)
else:
raise classes.ErepublikException("Unknown kind produced '{kind}'".format(kind=kind))
raise classes.ErepublikException(f"Unknown kind produced '{kind}'")
elif self.config.auto_buy_raw and re.search(r"not_enough_[^_]*_raw", response.get("message")):
raw_kind = re.search(r"not_enough_(\w+)_raw", response.get("message"))
if raw_kind:
@ -2710,7 +2796,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self._report_action("WORK_AS_MANAGER", "Not enough money to work as manager!", kwargs=response)
self.write_log("Not enough money to work as manager!")
else:
msg = "I was not able to wam and or employ because:\n{}".format(response)
msg = f"I was not able to wam and or employ because:\n{response}"
self._report_action("WORK_AS_MANAGER", f"Worked as manager failed: {msg}", kwargs=response)
self.write_log(msg)
@ -2739,7 +2825,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
for holding in regions.values():
raw_usage = holding.get_wam_raw_usage()
free_storage = self.inventory_status['total'] - self.inventory_status['used']
free_storage = self.inventory.total - self.inventory.used
if (raw_usage['frm'] + raw_usage['wrm']) * 100 > free_storage:
self._report_action('WAM_UNAVAILABLE', 'Not enough storage!')
continue
@ -2749,7 +2835,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
wam_count = self.my_companies.get_total_wam_count()
if wam_count:
self.write_log("Wam ff lockdown is now {}, was {}".format(wam_count, self.my_companies.ff_lockdown))
self.write_log(f"Wam ff lockdown is now {wam_count}, was {self.my_companies.ff_lockdown}")
self.my_companies.ff_lockdown = wam_count
self.travel_to_residence()
return bool(wam_count)

View File

@ -3,14 +3,16 @@ import hashlib
import threading
import weakref
from decimal import Decimal
from typing import Any, Dict, List, NamedTuple, Tuple, Union, NoReturn, Generator, Iterable
from typing import Any, Dict, Generator, Iterable, List, NamedTuple, NoReturn, Tuple, Union
from requests import Response, Session, post
from . import utils, constants
from . import constants, utils, types
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
'Holding', 'MyCompanies', 'MyJSONEncoder', 'OfferItem', 'Politics', 'Reporter', 'TelegramBot']
'ErepublikNetworkException', 'EnergyToFight',
'Holding', 'MyCompanies', 'MyJSONEncoder', 'OfferItem', 'Politics', 'Reporter', 'TelegramReporter',
'Inventory']
class ErepublikException(Exception):
@ -395,11 +397,11 @@ class Config:
self.spin_wheel_of_fortune = False
@property
def as_dict(self):
def as_dict(self) -> Dict[str, Union[bool, int, str, List[str]]]:
return dict(email=self.email, work=self.work, train=self.train, wam=self.wam, ot=self.ot,
auto_sell=self.auto_sell, auto_sell_all=self.auto_sell_all, employees=self.employees,
fight=self.fight, air=self.air, ground=self.ground, all_in=self.all_in,
next_energy=self.next_energy, boosters=self.boosters, travel_to_fight=self.travel_to_fight,
next_energy=self.next_energy, travel_to_fight=self.travel_to_fight,
always_travel=self.always_travel, epic_hunt=self.epic_hunt, epic_hunt_ebs=self.epic_hunt_ebs,
rw_def_side=self.rw_def_side, interactive=self.interactive, maverick=self.maverick,
continuous_fighting=self.continuous_fighting, auto_buy_raw=self.auto_buy_raw,
@ -419,7 +421,7 @@ class Energy:
self._recovery_time = utils.now()
def __repr__(self):
return "{:4}/{:4} + {:4}, {:3}hp/6min".format(self.recovered, self.limit, self.recoverable, self.interval)
return f"{self.recovered:4}/{self.limit:4} + {self.recoverable:4}, {self.interval:3}hp/6min"
def set_reference_time(self, recovery_time: datetime.datetime):
self._recovery_time = recovery_time.replace(microsecond=0)
@ -453,7 +455,7 @@ class Energy:
return self.recovered + self.recoverable
@property
def as_dict(self):
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,
reference_time=self.reference_time, food_fights=self.food_fights,
is_recoverable_full=self.is_recoverable_full, is_recovered_full=self.is_recovered_full,
@ -508,7 +510,7 @@ class Details:
return next_level_up - self.xp
@property
def as_dict(self):
def as_dict(self) -> Dict[str, Union[int, float, str, constants.Country, bool]]:
return dict(xp=self.xp, cc=self.cc, pp=self.pp, pin=self.pin, gold=self.gold, next_pp=self.next_pp,
citizen_id=self.citizen_id, citizenship=self.citizenship, current_region=self.current_region,
current_country=self.current_country, residence_region=self.residence_region,
@ -527,7 +529,7 @@ class Politics:
is_country_president: bool = False
@property
def as_dict(self):
def as_dict(self) -> Dict[str, Union[bool, int, str]]:
return dict(is_party_member=self.is_party_member, party_id=self.party_id, party_slug=self.party_slug,
is_party_president=self.is_party_president, is_congressman=self.is_congressman,
is_country_president=self.is_country_president)
@ -565,7 +567,7 @@ class Reporter:
return self.citizen.details.citizen_id
@property
def as_dict(self):
def as_dict(self) -> Dict[str, Union[bool, int, str, List[Dict[Any, Any]]]]:
return dict(name=self.name, email=self.email, citizen_id=self.citizen_id, key=self.key, allowed=self.allowed,
queue=self.__to_update)
@ -598,10 +600,10 @@ 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, cls=MyJSONEncoder))
self._req.post("{}/bot/update".format(self.url), json=unreported_data)
self._req.post(f"{self.url}/bot/update", json=unreported_data)
self.__to_update.clear()
data = utils.json.loads(utils.json.dumps(data, cls=MyJSONEncoder))
r = self._req.post("{}/bot/update".format(self.url), json=data)
r = self._req.post(f"{self.url}/bot/update", json=data)
return r
def register_account(self):
@ -609,8 +611,8 @@ class Reporter:
try:
r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id))
if not r.json().get("status"):
self._req.post("{}/bot/register".format(self.url), json=dict(name=self.name, email=self.email,
player_id=self.citizen_id))
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
@ -729,7 +731,7 @@ class BattleSide:
return self.country.iso
@property
def as_dict(self):
def as_dict(self) -> Dict[str, Union[int, constants.Country, bool, List[constants.Country]]]:
return dict(points=self.points, country=self.country, is_defender=self.is_defender, allies=self.allies,
deployed=self.deployed)
@ -895,9 +897,9 @@ class Battle:
time_now = utils.now()
is_started = self.start < utils.now()
if is_started:
time_part = " {}".format(time_now - self.start)
time_part = f" {time_now - self.start}"
else:
time_part = "-{}".format(self.start - time_now)
time_part = f"-{self.start - time_now}"
return (f"Battle {self.id} for {self.region_name[:16]:16} | "
f"{self.invader} : {self.defender} | Round time {time_part} | {'R'+str(self.zone_id):>3}")
@ -937,7 +939,7 @@ class EnergyToFight:
return self.energy
class TelegramBot:
class TelegramReporter:
__initialized: bool = False
__queue: List[str]
chat_id: int = 0
@ -962,10 +964,12 @@ class TelegramBot:
'last_time': self._last_time, 'next_time': self._next_time, 'queue': self.__queue,
'initialized': self.__initialized, 'has_threads': not self._threads}
def do_init(self, chat_id: int, token: str, player_name: str = ""):
def do_init(self, chat_id: int, token: str = None, player_name: str = None):
if token is None:
token = "864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o"
self.chat_id = chat_id
self.api_url = "https://api.telegram.org/bot{}/sendMessage".format(token)
self.player_name = player_name
self.api_url = f"https://api.telegram.org/bot{token}/sendMessage"
self.player_name = player_name or ""
self.__initialized = True
self._last_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-5))
self._last_full_energy_report = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-30))
@ -981,7 +985,7 @@ class TelegramBot:
self._threads = [t for t in self._threads if t.is_alive()]
self._next_time = utils.good_timedelta(utils.now(), datetime.timedelta(seconds=20))
if not self._threads:
name = "telegram_{}send".format(f"{self.player_name}_" if self.player_name else "")
name = f"telegram_{f'{self.player_name}_' if self.player_name else ''}send"
send_thread = threading.Thread(target=self.__send_messages, name=name)
send_thread.start()
self._threads.append(send_thread)
@ -1021,3 +1025,27 @@ class OfferItem(NamedTuple):
amount: int = 0
offer_id: int = 0
citizen_id: int = 0
class Inventory:
final: types.InvFinal
active: types.InvFinal
boosters: types.InvBooster
raw: types.InvRaw
market: types.InvRaw
used: int
total: int
def __init__(self):
self.active = {}
self.final = {}
self.boosters = {}
self.raw = {}
self.offers = {}
self.used = 0
self.total = 0
@property
def as_dict(self) -> Dict[str, Union[types.InvFinal, types.InvRaw, int]]:
return dict(active=self.active, final=self.final, boosters=self.boosters, raw=self.raw, offers=self.offers,
total=self.total, used=self.used)

7
erepublik/types.py Normal file
View File

@ -0,0 +1,7 @@
from datetime import datetime
from typing import Dict, Union, List
InvFinalItem = Dict[str, Union[str, int, List[Dict[str, Union[int, datetime]]]]]
InvBooster = Dict[str, Dict[int, Dict[int, InvFinalItem]]]
InvFinal = Dict[str, Dict[int, InvFinalItem]]
InvRaw = Dict[str, Dict[int, Dict[str, Union[str, int]]]]

View File

@ -10,7 +10,7 @@ import unicodedata
import warnings
from decimal import Decimal
from pathlib import Path
from typing import Any, List, Optional, Union, Dict
from typing import Any, Dict, List, Union
import requests
@ -21,10 +21,10 @@ try:
except ImportError:
import json
__all__ = ['VERSION', 'calculate_hit', 'caught_error', 'date_from_eday', 'eday_from_date',
__all__ = ['VERSION', 'calculate_hit', 'caught_error', 'date_from_eday', 'eday_from_date', 'deprecation',
'get_air_hit_dmg_value', 'get_file', 'get_ground_hit_dmg_value', 'get_sleep_seconds', 'good_timedelta',
'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now',
'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file',
'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file', 'write_request',
'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock']
if not sys.version_info >= (3, 6):
@ -64,7 +64,9 @@ def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.da
return constants.erep_tz.normalize(dt + td)
def eday_from_date(date: Union[datetime.date, datetime.datetime] = now()) -> int:
def eday_from_date(date: Union[datetime.date, datetime.datetime] = None) -> int:
if date is None:
date = now()
if isinstance(date, datetime.date):
date = datetime.datetime.combine(date, datetime.time(0, 0, 0))
return (date - datetime.datetime(2007, 11, 20, 0, 0, 0)).days
@ -93,7 +95,7 @@ def interactive_sleep(sleep_seconds: int):
# seconds = seconds % 30 if seconds % 30 else 30
else:
seconds = 1
sys.stdout.write("\rSleeping for {:4} more seconds".format(sleep_seconds))
sys.stdout.write(f"\rSleeping for {sleep_seconds:4} more seconds")
sys.stdout.flush()
time.sleep(seconds)
sleep_seconds -= seconds
@ -105,7 +107,7 @@ silent_sleep = time.sleep
def _write_log(msg, timestamp: bool = True, should_print: bool = False):
erep_time_now = now()
txt = "[{}] {}".format(erep_time_now.strftime('%F %T'), msg) if timestamp else msg
txt = f"[{erep_time_now.strftime('%F %T')}] {msg}" if timestamp else msg
txt = "\n".join(["\n".join(textwrap.wrap(line, 120)) for line in txt.splitlines()])
if not os.path.isdir('log'):
os.mkdir('log')
@ -158,6 +160,7 @@ def write_file(filename: str, content: str) -> int:
def write_request(response: requests.Response, is_error: bool = False):
from erepublik import Citizen
# Remove GET args from url name
url = response.url
last_index = url.index("?") if "?" in url else len(response.url)
@ -172,10 +175,10 @@ def write_request(response: requests.Response, is_error: bool = False):
ext = "html"
if not is_error:
filename = "debug/requests/{}_{}.{}".format(now().strftime('%F_%H-%M-%S'), name, ext)
filename = f"debug/requests/{now().strftime('%F_%H-%M-%S')}_{name}.{ext}"
write_file(filename, html)
else:
return {"name": "{}_{}.{}".format(now().strftime('%F_%H-%M-%S'), name, ext),
return {"name": f"{now().strftime('%F_%H-%M-%S')}_{name}.{ext}",
"content": html.encode('utf-8'),
"mimetype": "application/json" if ext == "json" else "text/html"}
@ -196,14 +199,14 @@ def send_email(name: str, content: List[Any], player=None, local_vars: Dict[str,
if promo:
resp = {"name": "%s.html" % name, "mimetype": "text/html",
"content": file_content_template.format(title="Promo", body="<br/>".join(content))}
subject = "[eBot][{}] Promos: {}".format(now().strftime('%F %T'), name)
subject = f"[eBot][{now().strftime('%F %T')}] Promos: {name}"
elif captcha:
resp = {"name": "%s.html" % name, "mimetype": "text/html",
"content": file_content_template.format(title="ReCaptcha", body="<br/>".join(content))}
subject = "[eBot][{}] RECAPTCHA: {}".format(now().strftime('%F %T'), name)
subject = f"[eBot][{now().strftime('%F %T')}] RECAPTCHA: {name}"
else:
subject = "[eBot][%s] Bug trace: %s" % (now().strftime('%F %T'), name)
subject = f"[eBot][{now().strftime('%F %T')}] Bug trace: {name}"
body = "".join(traceback.format_stack()) + \
"\n\n" + \
@ -252,7 +255,7 @@ def caught_error(e: Exception):
def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None,
interactive: Optional[bool] = None):
interactive: bool = None):
"""
Process error logging and email sending to developer
:param interactive: Should print interactively
@ -376,6 +379,7 @@ def get_final_hit_dmg(base_dmg: Union[Decimal, float, str], rang: int,
dmg = dmg * 11 / 10
return Decimal(dmg)
# def _clear_up_battle_memory(battle):
# del battle.invader._battle, battle.defender._battle
# for div_id, division in battle.div.items():
@ -396,7 +400,12 @@ def wait_for_lock(function):
return None
else:
instance.concurrency_available.clear()
ret = function(instance, *args, **kwargs)
try:
ret = function(instance, *args, **kwargs)
except Exception as e:
instance.concurrency_available.set()
raise e
instance.concurrency_available.set()
return ret
return wrapper

View File

@ -97,7 +97,7 @@ def main():
player.set_debug(CONFIG.get('debug', False))
player.login()
if CONFIG.get('battle_launcher'):
name = "{}-battle_launcher-{}".format(player.name, threading.active_count() - 1)
name = f"{player.name}-battle_launcher-{threading.active_count() - 1}"
state_thread = threading.Thread(target=_battle_launcher, args=(player,), name=name)
state_thread.start()

View File

@ -1,6 +1,6 @@
from datetime import timedelta
from erepublik import Citizen, utils, constants
from erepublik import Citizen, constants, utils
CONFIG = {
'email': 'player@email.com',
@ -94,15 +94,15 @@ def main():
closest_next_time = dt_max
next_tasks = []
for task, next_time in sorted(tasks.items(), key=lambda s: s[1]):
next_tasks.append("{}: {}".format(next_time.strftime('%F %T'), task))
next_tasks.append(f"{next_time.strftime('%F %T')}: {task}")
if next_time < closest_next_time:
closest_next_time = next_time
sleep_seconds = int(utils.get_sleep_seconds(closest_next_time))
if sleep_seconds <= 0:
player.write_log(f"Loop detected! Offending task: '{next_tasks[0]}'")
player.write_log("My next Tasks and there time:\n" + "\n".join(sorted(next_tasks)))
player.write_log("Sleeping until (eRep): {} (sleeping for {}s)".format(
closest_next_time.strftime("%F %T"), sleep_seconds))
player.write_log(f"Sleeping until (eRep): {closest_next_time.strftime('%F %T')}"
f" (sleeping for {sleep_seconds}s)")
seconds_to_sleep = sleep_seconds if sleep_seconds > 0 else 0
player.sleep(seconds_to_sleep)
except Exception as e:

View File

@ -1,19 +1,18 @@
bump2version==1.0.1
coverage==5.3
edx-sphinx-theme==1.5.0
coverage==5.3.1
edx-sphinx-theme==1.6.0
flake8==3.8.4
ipython>=7.19.0
isort==5.6.4
pip==20.3
isort==5.7.0
pip==20.3.3
PyInstaller==4.1
pytz==2020.4
pytest==6.1.2
pytz>=2020.0
pytest==6.2.1
responses==0.12.1
setuptools==50.3.2
Sphinx==3.3.1
requests==2.25.0
setuptools==51.1.1
Sphinx==3.4.2
requests>=2.24.0,<2.26.0
PySocks==1.7.1
tox==3.20.1
twine==3.2.0
watchdog==0.10.4
wheel==0.35.1
twine==3.3.0
wheel==0.36.2
pur==5.3.0

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.23.2.8
current_version = 0.23.4.1
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
@ -18,13 +18,13 @@ replace = __version__ = '{new_version}'
universal = 1
[flake8]
exclude = docs,.tox,.git,log,debug,venv
exclude = docs,.git,log,debug,venv
max-line-length = 120
ignore = D100,D101,D102,D103
[pycodestyle]
max-line-length = 120
exclude = .tox,.git,log,debug,venv, build
exclude = .git,log,debug,venv, build
[mypy]
python_version = 3.7

View File

@ -12,8 +12,8 @@ with open('HISTORY.rst') as history_file:
history = history_file.read()
requirements = [
'pytz==2020.4',
'requests==2.25.0',
'pytz>=2020.0',
'requests>=2.24.0,<2.26.0',
'PySocks==1.7.1'
]
@ -45,11 +45,11 @@ setup(
keywords='erepublik',
name='eRepublik',
packages=find_packages(include=['erepublik']),
python_requires='>=3.6, <4',
python_requires='>=3.7, <4',
setup_requires=setup_requirements,
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/',
version='0.23.2.8',
version='0.23.4.1',
zip_safe=False,
)

18
tox.ini
View File

@ -1,18 +0,0 @@
[tox]
envlist = py37, py38, flake8
[travis]
python =
3.7: py37
3.8: py38
[testenv:flake8]
basepython = python
deps = flake8
commands = flake8 erepublik_script
[testenv]
setenv =
PYTHONPATH = {toxinidir}
commands = python setup.py test