Compare commits

...

41 Commits

Author SHA1 Message Date
47f5142837 Bump version: 0.23.4.6 → 0.23.4.7 2021-01-08 11:09:39 +02:00
a32fd039dd Report details about fighting also on telegram 2021-01-08 11:08:50 +02:00
311e684c0c Add telegram fight reporting API, Fix message duplication on TelegramReporter initialization 2021-01-08 11:07:59 +02:00
52038a86d5 PackBooster icon update 2021-01-07 17:00:59 +02:00
c35a107641 Bump version: 0.23.4.5 → 0.23.4.6 2021-01-07 15:55:02 +02:00
b53b2f0fae Update loop 2021-01-07 15:54:56 +02:00
8da9b6221a Bump version: 0.23.4.4 → 0.23.4.5 2021-01-07 15:36:33 +02:00
caa41c85f6 minor tweaks 2021-01-07 15:36:27 +02:00
da3b5d5768 Update inventory on booster activation 2021-01-07 15:31:46 +02:00
f96d0233f9 CSRF bugfix 2021-01-07 15:31:10 +02:00
c693a5182e Merge branch 'master' of github.com:eeriks/erepublik 2021-01-07 15:14:17 +02:00
0dfba6a9ff Find battle and fight - reuse already calculated hit count 2021-01-07 15:14:11 +02:00
ee3eca9658 Bump version: 0.23.4.3 → 0.23.4.4 2021-01-06 23:37:05 +02:00
0e8680daca bugfix 2021-01-06 23:35:25 +02:00
8e3606f1a3 Doc update 2021-01-05 19:47:43 +02:00
bd0bcc9ac7 Bump version: 0.23.4.2 → 0.23.4.3 2021-01-05 19:35:00 +02:00
c6f2226e64 . 2021-01-05 19:34:43 +02:00
308807d800 isort, pre-commit 2021-01-05 19:29:20 +02:00
91565d840e Added endpoint for collect all WC rewards 2021-01-05 19:18:20 +02:00
b4a9dd88f8 config generator bugdix 2021-01-05 19:17:50 +02:00
8435aa23ba Bump version: 0.23.4.1 → 0.23.4.2 2021-01-05 16:30:49 +02:00
09cd275a69 type bugfix 2021-01-05 16:30:42 +02:00
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
23 changed files with 535 additions and 222 deletions

2
.gitignore vendored
View File

@ -37,7 +37,6 @@ pip-delete-this-directory.txt
# Unit test / coverage reports # Unit test / coverage reports
htmlcov/ htmlcov/
.tox/
.coverage .coverage
.coverage.* .coverage.*
.cache .cache
@ -103,5 +102,4 @@ ENV/
debug/ debug/
log/ log/
docs/
*dump.json *dump.json

13
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,13 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
default_language_version:
python: python3.7

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. Now you can make your changes locally.
5. When you're done making changes, check that your changes pass flake8 and the 5. When you're done making changes, check that your changes pass flake8, isort and the
tests, including testing other Python versions with tox:: tests::
$ flake8 erepublik tests $ flake8 erepublik tests
$ python setup.py test or py.test $ isort erepublik
$ tox $ 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:: 6. Commit your changes and push your branch to GitHub::

View File

@ -2,6 +2,25 @@
History 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) 0.23.2 (2020-12-01)
------------------- -------------------
* Added concurrency checks to guard against simultaneous fighting/wam'ing/traveling * Added concurrency checks to guard against simultaneous fighting/wam'ing/traveling
@ -17,7 +36,7 @@ History
0.23.0 (2020-11-26) 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 `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.get_article(article_id:int)` method to get article data
* Added `CitizenMedia.delete_article(article_id:int)` method to delete article * Added `CitizenMedia.delete_article(article_id:int)` method to delete article

View File

@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

View File

@ -47,7 +47,6 @@ clean-pyc: ## remove Python file artifacts
rm -rf log/ rm -rf log/
clean-test: ## remove test and coverage artifacts clean-test: ## remove test and coverage artifacts
rm -fr .tox/
rm -f .coverage rm -f .coverage
rm -fr htmlcov/ rm -fr htmlcov/
rm -fr .pytest_cache rm -fr .pytest_cache
@ -58,9 +57,6 @@ lint: ## check style with flake8
test: ## run tests quickly with the default Python test: ## run tests quickly with the default Python
python setup.py test 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: ## check code coverage quickly with the default Python
coverage run --source erepublik setup.py test coverage run --source erepublik setup.py test
coverage report -m coverage report -m

View File

@ -34,4 +34,3 @@ This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypack
.. _Cookiecutter: https://github.com/audreyr/cookiecutter .. _Cookiecutter: https://github.com/audreyr/cookiecutter
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage

61
docs/erepublik.rst Normal file
View File

@ -0,0 +1,61 @@
erepublik package
=================
Submodules
----------
erepublik.access\_points module
-------------------------------
.. automodule:: erepublik.access_points
:members:
:undoc-members:
:show-inheritance:
erepublik.citizen module
------------------------
.. automodule:: erepublik.citizen
:members:
:undoc-members:
:show-inheritance:
erepublik.classes module
------------------------
.. automodule:: erepublik.classes
:members:
:undoc-members:
:show-inheritance:
erepublik.constants module
--------------------------
.. automodule:: erepublik.constants
:members:
:undoc-members:
:show-inheritance:
erepublik.types module
----------------------
.. automodule:: erepublik.types
:members:
:undoc-members:
:show-inheritance:
erepublik.utils module
----------------------
.. automodule:: erepublik.utils
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: erepublik
:members:
:undoc-members:
:show-inheritance:

View File

@ -8,15 +8,12 @@
<meta name="generator" content="Jekyll v4.0.1"> <meta name="generator" content="Jekyll v4.0.1">
<title>eBot configuration</title> <title>eBot configuration</title>
<!-- CSS only --> <!-- CSS only -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
crossorigin="anonymous">
<!-- JS, Popper.js, and jQuery --> <!-- 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://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" <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>
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>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous"></script>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
@ -37,14 +34,14 @@
<div class="col-12 col-sm-8 col-md-6"> <div class="col-12 col-sm-8 col-md-6">
<h3>Login data</h3> <h3>Login data</h3>
<div class="form-group"> <div class="form-group">
<input type="email" class="form-control form-control-sm" onchange="updateJson()" id="email" placeholder="E-mail..."> <label for="email" class="hidden"></label><input type="email" class="form-control" 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="password" class="hidden"></label><input type="password" class="form-control" onchange="updateJson()" id="password" disabled placeholder="Password..."
aria-describedby="passwordHelpBlock"> 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 <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> password!</small>
</div> </div>
</div> </div>
<div class="col-6 col-md-3"> <div class="col-6 col-sm-4 col-md-3">
<h3>Basic tasks</h3> <h3>Basic tasks</h3>
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="work" checked> <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> <label class="custom-control-label" for="ot">Work overtime</label>
</div> </div>
</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>
<div class="row"> <div class="row">
@ -200,6 +174,90 @@
<label class="custom-control-label" for="epic_hunt_ebs">Spend <small>[all]</small> EBs in epics</label> <label class="custom-control-label" for="epic_hunt_ebs">Spend <small>[all]</small> EBs in epics</label>
</div> </div>
</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> </div>
</form> </form>
</div> </div>
@ -212,7 +270,9 @@
function disable(element){ function disable(element){
element.checked = false; element.checked = false;
element.disabled = true; element.disabled = true;
element.value = null;
} }
function updateJson() { function updateJson() {
let config = {}; let config = {};
let email = document.getElementById('email'); // Generated let email = document.getElementById('email'); // Generated
@ -228,6 +288,14 @@
config.renew_houses = renew_houses.checked; config.renew_houses = renew_houses.checked;
let random_sleep = document.getElementById('random_sleep'); // Generated let random_sleep = document.getElementById('random_sleep'); // Generated
config.random_sleep = random_sleep.checked; 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 let buy_gold = document.getElementById('buy_gold'); // Generated
config.buy_gold = buy_gold.checked; config.buy_gold = buy_gold.checked;
let interactive = document.getElementById('interactive'); // Generated let interactive = document.getElementById('interactive'); // Generated
@ -297,7 +365,7 @@
let travel_to_fight = document.getElementById('travel_to_fight'); // Generated let travel_to_fight = document.getElementById('travel_to_fight'); // Generated
let epic_hunt = document.getElementById('epic_hunt'); // Generated let epic_hunt = document.getElementById('epic_hunt'); // Generated
let epic_hunt_ebs = document.getElementById('epic_hunt_ebs'); // Generated let epic_hunt_ebs = document.getElementById('epic_hunt_ebs'); // Generated
if (config.fight){ if (config.fight) {
air.disabled = false; air.disabled = false;
ground.disabled = false; ground.disabled = false;
boosters.disabled = false; boosters.disabled = false;
@ -325,7 +393,7 @@
} }
config.air = air.checked; config.air = air.checked;
config.ground = ground.cehcked; config.ground = ground.checked;
config.boosters = boosters.checked; config.boosters = boosters.checked;
config.continuous_fighting = continuous_fighting.checked; config.continuous_fighting = continuous_fighting.checked;
config.next_energy = next_energy.checked; config.next_energy = next_energy.checked;
@ -334,6 +402,59 @@
config.travel_to_fight = travel_to_fight.checked; config.travel_to_fight = travel_to_fight.checked;
config.epic_hunt = epic_hunt.checked; config.epic_hunt = epic_hunt.checked;
config.epic_hunt_ebs = config.epic_hunt ? epic_hunt_ebs.checked : config.epic_hunt; config.epic_hunt_ebs = config.epic_hunt ? epic_hunt_ebs.checked : config.epic_hunt;
config.maverick = false;
// 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'); let pre = document.getElementById('json-output');
pre.textContent = JSON.stringify(config, null, 2); pre.textContent = JSON.stringify(config, null, 2);
} }

7
docs/modules.rst Normal file
View File

@ -0,0 +1,7 @@
erepublik
=========
.. toctree::
:maxdepth: 4
erepublik

View File

@ -7,4 +7,3 @@ To use eRepublik script in a project::
from erepublik import Citizen from erepublik import Citizen
player = Citizen('email@domain.com', 'password') player = Citizen('email@domain.com', 'password')
player.update_all() player.update_all()

View File

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

View File

@ -305,11 +305,11 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
return self.post(f"{self.url}/main/buyGoldItems", data=data) return self.post(f"{self.url}/main/buyGoldItems", data=data)
def _post_economy_activate_booster(self, quality: int, duration: int, kind: str) -> Response: def _post_economy_activate_booster(self, quality: int, duration: int, kind: str) -> Response:
data = dict(type=kind, quality=quality, duration=duration, fromInventory=True) data = dict(type=kind, quality=quality, duration=duration, fromInventory=True, _token=self.token)
return self.post(f"{self.url}/economy/activateBooster", data=data) return self.post(f"{self.url}/economy/activateBooster", data=data)
def _post_economy_activate_house(self, quality: int) -> Response: def _post_economy_activate_house(self, quality: int) -> Response:
data = {"action": "activate", "quality": quality, "type": "house", "_token": self.token} data = dict(action="activate", quality=quality, type="house", _token=self.token)
return self.post(f"{self.url}/economy/activateHouse", data=data) return self.post(f"{self.url}/economy/activateHouse", data=data)
def _post_economy_donate_items_action(self, citizen_id: int, amount: int, industry: int, def _post_economy_donate_items_action(self, citizen_id: int, amount: int, industry: int,
@ -595,6 +595,10 @@ class ErepublikProfileAPI(CitizenBaseAPI):
data = dict(_token=self.token, rewardId=reward_id) data = dict(_token=self.token, rewardId=reward_id)
return self.post(f"{self.url}/main/weekly-challenge-collect-reward", data=data) return self.post(f"{self.url}/main/weekly-challenge-collect-reward", data=data)
def _post_main_weekly_challenge_collect_all(self, max_reward_id: int) -> Response:
data = dict(_token=self.token, maxRewardId=max_reward_id)
return self.post(f"{self.url}/main/weekly-challenge-collect-all", data=data)
def _post_main_profile_update(self, action: str, params: str): def _post_main_profile_update(self, action: str, params: str):
data = {"action": action, "params": params, "_token": self.token} data = {"action": action, "params": params, "_token": self.token}
return self.post(f"{self.url}/main/profile-update", data=data) return self.post(f"{self.url}/main/profile-update", data=data)

View File

@ -2,7 +2,7 @@ import re
import sys import sys
import warnings import warnings
import weakref import weakref
from datetime import datetime, timedelta from datetime import datetime, time, timedelta
from decimal import Decimal from decimal import Decimal
from itertools import product from itertools import product
from threading import Event 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 requests import HTTPError, RequestException, Response
from . import utils, classes, access_points, constants from . import access_points, classes, constants, types, utils
from .classes import OfferItem from .classes import OfferItem
@ -20,9 +20,7 @@ class BaseCitizen(access_points.CitizenAPI):
_last_inventory_update: datetime = constants.min_datetime _last_inventory_update: datetime = constants.min_datetime
promos: Dict[str, datetime] = None promos: Dict[str, datetime] = None
inventory: Dict[str, Dict[str, Dict[int, Dict[str, Union[str, int, float]]]]] _inventory: classes.Inventory
inventory_status: Dict[str, int]
boosters: Dict[int, Dict[int, int]] = {50: {}, 100: {}}
ot_points: int = 0 ot_points: int = 0
food: Dict[str, int] = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0} food: Dict[str, int] = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
@ -65,8 +63,7 @@ class BaseCitizen(access_points.CitizenAPI):
self.config.email = email self.config.email = email
self.config.password = password self.config.password = password
self.inventory = {} self._inventory = classes.Inventory()
self.inventory_status = dict(used=0, total=0)
self.wheel_of_fortune = False self.wheel_of_fortune = False
def get_csrf_token(self): def get_csrf_token(self):
@ -241,33 +238,42 @@ class BaseCitizen(access_points.CitizenAPI):
def update_inventory(self): def update_inventory(self):
""" """
Updates class properties and returns structured inventory. Updates citizen inventory
Return structure: {status: {used: int, total: int}, items: {active/final/raw: {item_token:{quality: data}}}
If item kind is damageBoosters or aircraftDamageBoosters then kind is renamed to kind+quality and duration is
used as quality.
""" """
self._update_inventory_data(self._get_economy_inventory_items().json()) self._update_inventory_data(self._get_economy_inventory_items().json())
def get_inventory(self, force: bool = False): @property
def inventory(self) -> classes.Inventory:
return self.get_inventory()
def get_inventory(self, force: bool = False) -> classes.Inventory:
if utils.good_timedelta(self._last_inventory_update, timedelta(minutes=2)) < self.now or force: if utils.good_timedelta(self._last_inventory_update, timedelta(minutes=2)) < self.now or force:
self.update_inventory() self.update_inventory()
return self.inventory return self._inventory
def _update_inventory_data(self, inv_data: Dict[str, Any]): def _update_inventory_data(self, inv_data: Dict[str, Any]):
if not isinstance(inv_data, dict): if not isinstance(inv_data, dict):
raise TypeError("Parameter `inv_data` must be dict not '{type(data)}'!") 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", {}) status = inv_data.get("inventoryStatus", {})
if status: if status:
self.inventory_status.clear() self._inventory.used = status.get("usedStorage")
self.inventory_status.update(used=status.get("usedStorage"), total=status.get("totalStorage")) self._inventory.total = status.get("totalStorage")
data = inv_data.get('inventoryItems', {}) data = inv_data.get('inventoryItems', {})
if not data: if not data:
return return
self._last_inventory_update = self.now self._last_inventory_update = self.now
self.food.update({"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0}) 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 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", {}): if data.get("activeEnhancements", {}).get("items", {}):
for item_data in data.get("activeEnhancements", {}).get("items", {}).values(): for item_data in data.get("activeEnhancements", {}).get("items", {}).values():
if item_data.get('token'): if item_data.get('token'):
@ -278,29 +284,35 @@ class BaseCitizen(access_points.CitizenAPI):
kind = constants.INDUSTRIES[constants.INDUSTRIES[kind]] kind = constants.INDUSTRIES[constants.INDUSTRIES[kind]]
if kind not in active_items: if kind not in active_items:
active_items[kind] = {} active_items[kind] = {}
icon = item_data['icon'] if item_data[ expiration_info = []
'icon'] else "//www.erepublik.net/images/modules/manager/tab_storage.png" if item_data.get('attributes').get('expirationInfo'):
item_data = dict(name=item_data.get("name"), time_left=item_data['active']['time_left'], icon=icon, expire_info = item_data.get('attributes').get('expirationInfo')
kind=kind, expiration_info = [_expire_value_to_python(v) for v in expire_info['value']]
quality=item_data.get("quality", 0)) if not item_data.get('icon') and item_data.get('isPackBooster'):
item_data['icon'] = f"//www.erepublik.com/images/icons/boosters/52px/{item_data.get('type')}.png"
icon = item_data['icon'] if item_data['icon'] else "//www.erepublik.net/images/modules/manager/tab_storage.png"
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'): if item_data.get('isPackBooster'):
active_items[kind].update({0: item_data}) active_items[kind].update({0: inv_item})
else: 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", {}): if data.get("finalProducts", {}).get("items", {}):
for item_data in data.get("finalProducts", {}).get("items", {}).values(): for item_data in data.get("finalProducts", {}).get("items", {}).values():
is_booster: bool = False
name = item_data['name'] name = item_data['name']
if item_data.get('type'): if item_data.get('type'):
if item_data.get('type') in ['damageBoosters', "aircraftDamageBoosters"]: # in ['damageBoosters', "aircraftDamageBoosters", 'prestigePointsBoosters']
kind = f"{item_data['type']}{item_data['quality']}" if item_data.get('isBooster'):
if item_data['quality'] == 5: is_booster = True
self.boosters[50].update({item_data['duration']: item_data['amount']}) kind = item_data['type']
elif item_data['quality'] == 10:
self.boosters[100].update({item_data['duration']: item_data['amount']})
delta = item_data['duration'] delta = item_data['duration']
if delta // 3600: if delta // 3600:
@ -337,6 +349,12 @@ class BaseCitizen(access_points.CitizenAPI):
if constants.INDUSTRIES[kind]: if constants.INDUSTRIES[kind]:
kind = constants.INDUSTRIES[constants.INDUSTRIES[kind]] kind = constants.INDUSTRIES[constants.INDUSTRIES[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: if kind not in final_items:
final_items[kind] = {} final_items[kind] = {}
@ -355,10 +373,23 @@ class BaseCitizen(access_points.CitizenAPI):
icon = "/images/modules/pvp/ghost_boosters/icon_booster_30_60.png" icon = "/images/modules/pvp/ghost_boosters/icon_booster_30_60.png"
else: else:
icon = "//www.erepublik.net/images/modules/manager/tab_storage.png" 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) expiration_info = []
if item_data.get('type') in ('damageBoosters', "aircraftDamageBoosters"): if item_data.get('attributes'):
_item_data = {_item_data['durability']: _item_data} 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: else:
if item_data.get('type') == 'bomb': if item_data.get('type') == 'bomb':
firepower = 0 firepower = 0
@ -367,11 +398,14 @@ class BaseCitizen(access_points.CitizenAPI):
except AttributeError: except AttributeError:
pass pass
finally: finally:
_item_data.update(fire_power=firepower) inv_item.update(fire_power=firepower)
_item_data = {_item_data['quality']: _item_data} _inv_item = {inv_item['quality']: inv_item}
final_items[kind].update(_item_data) 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", {}): if data.get("rawMaterials", {}).get("items", {}):
for item_data in data.get("rawMaterials", {}).get("items", {}).values(): for item_data in data.get("rawMaterials", {}).get("items", {}).values():
if item_data['isPartial']: if item_data['isPartial']:
@ -401,8 +435,11 @@ class BaseCitizen(access_points.CitizenAPI):
offers[kind] = {} offers[kind] = {}
offers[kind].update(offer_data) offers[kind].update(offer_data)
self.inventory.clear() self._inventory.active = active_items
self.inventory.update(active=active_items, final=final_items, raw=raw_materials, offers=offers) 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]) self.food["total"] = sum([self.food[q] * constants.FOOD_ENERGY[q] for q in constants.FOOD_ENERGY])
def write_log(self, *args, **kwargs): def write_log(self, *args, **kwargs):
@ -417,7 +454,7 @@ class BaseCitizen(access_points.CitizenAPI):
else: else:
utils.process_error(msg, self.name, sys.exc_info(), self, None, None) 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: if seconds < 0:
seconds = 0 seconds = 0
if self.config.interactive: if self.config.interactive:
@ -946,13 +983,13 @@ class CitizenCompanies(BaseCitizen):
raw_factories = wam_holding.get_wam_companies(raw_factory=True) raw_factories = wam_holding.get_wam_companies(raw_factory=True)
fin_factories = wam_holding.get_wam_companies(raw_factory=False) 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 = raw_factories + fin_factories
wam_list = wam_list[:self.energy.food_fights] wam_list = wam_list[:self.energy.food_fights]
if int(free_inventory * 0.75) < self.my_companies.get_needed_inventory_usage(wam_list): if int(free_inventory * 0.75) < self.my_companies.get_needed_inventory_usage(wam_list):
self.update_inventory() 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): while wam_list and free_inventory < self.my_companies.get_needed_inventory_usage(wam_list):
wam_list.pop(-1) wam_list.pop(-1)
@ -1020,8 +1057,8 @@ class CitizenEconomy(CitizenTravel):
def check_house_durability(self) -> Dict[int, datetime]: def check_house_durability(self) -> Dict[int, datetime]:
ret = {} ret = {}
inv = self.get_inventory() inv = self.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'])) till = utils.good_timedelta(self.now, timedelta(seconds=active_house['time_left']))
ret.update({house_quality: till}) ret.update({house_quality: till})
return ret return ret
@ -1029,8 +1066,8 @@ class CitizenEconomy(CitizenTravel):
def buy_and_activate_house(self, q: int) -> Optional[Dict[int, datetime]]: def buy_and_activate_house(self, q: int) -> Optional[Dict[int, datetime]]:
original_region = self.details.current_country, self.details.current_region original_region = self.details.current_country, self.details.current_region
ok_to_activate = False ok_to_activate = False
inv = self.get_inventory() inv = self.inventory
if not inv['final'].get('House', {}).get(q, {}): if not inv.final.get('House', {}).get(q, {}):
countries = [self.details.citizenship, ] countries = [self.details.citizenship, ]
if self.details.current_country != self.details.citizenship: if self.details.current_country != self.details.citizenship:
countries.append(self.details.current_country) countries.append(self.details.current_country)
@ -1084,7 +1121,7 @@ class CitizenEconomy(CitizenTravel):
r: Dict[str, Any] = self._post_economy_activate_house(quality).json() r: Dict[str, Any] = self._post_economy_activate_house(quality).json()
self._update_inventory_data(r) self._update_inventory_data(r)
if r.get("status") and not r.get("error"): if r.get("status") and not r.get("error"):
house: Dict[str, Union[int, str]] = self.get_inventory()['active']['House'][quality] house = self.inventory.active.get('House', {}).get(quality)
time_left = timedelta(seconds=house["time_left"]) time_left = timedelta(seconds=house["time_left"])
active_until = utils.good_timedelta(self.now, time_left) active_until = utils.good_timedelta(self.now, time_left)
self._report_action( self._report_action(
@ -1164,12 +1201,13 @@ class CitizenEconomy(CitizenTravel):
self.write_log(f"Trying to sell unsupported industry {industry}") self.write_log(f"Trying to sell unsupported industry {industry}")
_inv_qlt = quality if industry in [1, 2, 3, 4, 23] else 0 _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 = (self.inventory.final if final_kind else self.inventory.raw).get(constants.INDUSTRIES[industry],
items = inventory[_kind].get(constants.INDUSTRIES[industry], {_inv_qlt: {'amount': 0}}) {_inv_qlt: {'amount': 0}})
if items[_inv_qlt]['amount'] < amount: if items[_inv_qlt]['amount'] < amount:
inventory = self.get_inventory(True) self.update_inventory()
items = inventory[_kind].get(constants.INDUSTRIES[industry], {_inv_qlt: {'amount': 0}}) items = (self.inventory.final if final_kind else self.inventory.raw).get(constants.INDUSTRIES[industry],
{_inv_qlt: {'amount': 0}})
if items[_inv_qlt]['amount'] < amount: if items[_inv_qlt]['amount'] < amount:
self._report_action("ECONOMY_SELL_PRODUCTS", "Unable to sell! Not enough items in storage!", self._report_action("ECONOMY_SELL_PRODUCTS", "Unable to sell! Not enough items in storage!",
kwargs=dict(inventory=items[_inv_qlt], amount=amount)) kwargs=dict(inventory=items[_inv_qlt], amount=amount))
@ -1735,7 +1773,8 @@ class CitizenMilitary(CitizenTravel):
yield battle, battle_zone, side yield battle, battle_zone, side
def find_battle_and_fight(self): def find_battle_and_fight(self):
if self.should_fight()[0]: count = self.should_fight()[0]
if count:
self.write_log("Checking for battles to fight in...") self.write_log("Checking for battles to fight in...")
for battle, division, side in self.find_battle_to_fight(): for battle, division, side in self.find_battle_to_fight():
@ -1762,7 +1801,7 @@ class CitizenMilitary(CitizenTravel):
if self.change_division(battle, division): if self.change_division(battle, division):
self.set_default_weapon(battle, division) self.set_default_weapon(battle, division)
self.fight(battle, division, side) self.fight(battle, division, side, count)
self.travel_to_residence() self.travel_to_residence()
break break
@ -1788,7 +1827,7 @@ class CitizenMilitary(CitizenTravel):
self._report_action("IP_BLACKLISTED", "Fighting is not allowed from restricted IP!") self._report_action("IP_BLACKLISTED", "Fighting is not allowed from restricted IP!")
return 1 return 1
if not division.is_air and self.config.boosters: 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: if side is None:
side = battle.defender if self.details.citizenship in battle.defender.allies + [ side = battle.defender if self.details.citizenship in battle.defender.allies + [
battle.defender.country] else battle.invader battle.defender.country] else battle.invader
@ -1814,7 +1853,7 @@ class CitizenMilitary(CitizenTravel):
self.write_log(f"Hits: {total_hits:>4} | Damage: {total_damage}") self.write_log(f"Hits: {total_hits:>4} | Damage: {total_damage}")
ok_to_fight = False ok_to_fight = False
if total_damage: if total_damage:
self.reporter.report_fighting(battle, not side.is_defender, division, total_damage, total_hits) self.report_fighting(battle, not side.is_defender, division, total_damage, total_hits)
# self.reporter.report_action('FIGHT', dict(battle_id=battle.id, side=side, dmg=total_damage, # self.reporter.report_action('FIGHT', dict(battle_id=battle.id, side=side, dmg=total_damage,
# air=battle.has_air, hits=total_hits, # air=battle.has_air, hits=total_hits,
# round=battle.zone_id, # round=battle.zone_id,
@ -1873,7 +1912,7 @@ class CitizenMilitary(CitizenTravel):
else: else:
hits = r_json['hits'] hits = r_json['hits']
if r_json['user']['epicBattle']: if r_json['user']['epicBattle']:
hits /= 1+r_json['user']['epicBattle'] hits /= 1 + r_json['user']['epicBattle']
self.energy.recovered = r_json["details"]["wellness"] self.energy.recovered = r_json["details"]["wellness"]
self.details.xp = int(r_json["details"]["points"]) self.details.xp = int(r_json["details"]["points"])
@ -1884,7 +1923,8 @@ class CitizenMilitary(CitizenTravel):
return hits, err, damage return hits, err, damage
@utils.wait_for_lock @utils.wait_for_lock
def deploy_bomb(self, battle: classes.Battle, division: classes.BattleDivision, bomb_id: int, inv_side: bool, 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. """Deploy bombs in a battle for given side.
:param battle: Battle :param battle: Battle
@ -1922,8 +1962,11 @@ class CitizenMilitary(CitizenTravel):
r = self._post_military_deploy_bomb(battle.id, division.id, side.id, bomb_id).json() r = self._post_military_deploy_bomb(battle.id, division.id, side.id, bomb_id).json()
if not r.get('error'): if not r.get('error'):
deployed_count += 1 deployed_count += 1
self.sleep(0.5)
elif r.get('message') == 'LOCKED': elif r.get('message') == 'LOCKED':
sleep(0.5) self.sleep(0.5)
elif r.get('message') == 'INVALID_BOMB':
errors = 10
else: else:
errors += 1 errors += 1
@ -1976,34 +2019,49 @@ class CitizenMilitary(CitizenTravel):
return utils.calculate_hit(0, rang, True, elite, ne, 0, 20 if weapon else 0) return utils.calculate_hit(0, rang, True, elite, ne, 0, 20 if weapon else 0)
def activate_dmg_booster(self): def activate_damage_booster(self, ground: bool = True) -> int:
if self.config.boosters: kind = 'damageBoosters' if ground else 'aircraftDamageBoosters'
if not self.get_active_ground_damage_booster(): if self.config.boosters and not self.get_active_damage_booster(ground):
duration = 0 booster: Optional[types.InvFinalItem] = None
for length, amount in self.boosters[50].items(): for quality, data in sorted(self.inventory.boosters.get(kind, {}).items(), key=lambda x: x[0]):
if amount > 2: for _duration, _booster in sorted(data.items(), key=lambda y: y[0]):
duration = length critical_amount = 2 if quality < 10 and ground else 10
if _booster.get('amount') > critical_amount:
booster = _booster
break break
if duration: break
self._report_action("MILITARY_BOOSTER", f"Activated 50% {duration / 60}h damage booster") if booster:
self._post_economy_activate_booster(5, duration, "damage") kind = 'damage' if ground else 'air_damage'
self._report_action("MILITARY_BOOSTER", f"Activated {booster['name']}")
resp = self._post_economy_activate_booster(booster['quality'], booster['durability'], kind).json()
self._update_inventory_data(resp)
return self.get_active_damage_booster(ground)
def get_active_ground_damage_booster(self) -> int: def get_active_damage_booster(self, ground: bool = True) -> int:
inventory = self.get_inventory() kind = 'damageBoosters' if ground else 'aircraftDamageBoosters'
boosters = self.inventory.active.get(kind, {})
quality = 0 quality = 0
if inventory['active'].get('damageBoosters', {}).get(10): for q, boost in boosters.items():
quality = 100 if boost['quality'] * 10 > quality:
elif inventory['active'].get('damageBoosters', {}).get(5): quality = boost['quality'] * 10
quality = 50
return quality return quality
def activate_battle_effect(self, battle_id: int, kind: str) -> Response: def get_active_ground_damage_booster(self) -> int:
self._report_action('MILITARY_BOOSTER', f'Activated {kind} booster') return self.get_active_damage_booster(True)
return self._post_main_activate_battle_effect(battle_id, kind, self.details.citizen_id)
def activate_pp_booster(self, battle_id: int) -> Response: def get_active_air_damage_booster(self) -> int:
self._report_action('MILITARY_BOOSTER', 'Activated PrestigePoint booster') return self.get_active_damage_booster(False)
return self._post_military_fight_activate_booster(battle_id, 1, 180, "prestige_points")
def activate_battle_effect(self, battle_id: int, kind: str) -> bool:
self._report_action('MILITARY_BOOSTER', f'Activated {kind} booster')
resp = self._post_main_activate_battle_effect(battle_id, kind, self.details.citizen_id).json()
return not resp.get('error')
def activate_pp_booster(self, pp_item: types.InvFinalItem) -> bool:
self._report_action('MILITARY_BOOSTER', f'Activated {pp_item["name"]}')
resp = self._post_economy_activate_booster(pp_item['quality'], pp_item['durability'], 'prestige_points').json()
self._update_inventory_data(resp)
return pp_item.get('kind') in self.inventory.active
def _rw_choose_side(self, battle: classes.Battle, side: classes.BattleSide) -> Response: def _rw_choose_side(self, battle: classes.Battle, side: classes.BattleSide) -> Response:
return self._post_main_battlefield_travel(side.id, battle.id) return self._post_main_battlefield_travel(side.id, battle.id)
@ -2164,6 +2222,11 @@ class CitizenMilitary(CitizenTravel):
if division.wall['dom'] == 50 or division.wall['dom'] > 98: if division.wall['dom'] == 50 or division.wall['dom'] > 98:
yield division, division.wall['for'] == battle.invader.country.id yield division, division.wall['for'] == battle.invader.country.id
def report_fighting(self, battle: classes.Battle, invader: bool, division: classes.BattleDivision, damage: float, hits: int):
self.reporter.report_fighting(battle, invader, division, damage, hits)
if self.config.telegram:
self.telegram.report_fight(battle, invader, division, damage, hits)
class CitizenPolitics(BaseCitizen): class CitizenPolitics(BaseCitizen):
def get_country_parties(self, country: constants.Country = None) -> dict: def get_country_parties(self, country: constants.Country = None) -> dict:
@ -2173,13 +2236,22 @@ class CitizenPolitics(BaseCitizen):
ret.update({int(id_): name}) ret.update({int(id_): name})
return ret return ret
def candidate_for_congress(self, presentation: str = "") -> Response: def candidate_for_party_presidency(self) -> Optional[Response]:
self._report_action('POLITIC_CONGRESS', 'Applied for congress elections') if self.politics.is_party_member:
return self._post_candidate_for_congress(presentation)
def candidate_for_party_presidency(self) -> Response:
self._report_action('POLITIC_PARTY_PRESIDENT', 'Applied for party president elections') self._report_action('POLITIC_PARTY_PRESIDENT', 'Applied for party president elections')
return self._get_candidate_party(self.politics.party_slug) 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_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( def get_country_president_election_result(
self, country: constants.Country, year: int, month: int self, country: constants.Country, year: int, month: int
@ -2303,7 +2375,7 @@ class CitizenSocial(BaseCitizen):
return self._get_main_city_data_residents(city_id, params={"search": name}).json() return self._get_main_city_data_residents(city_id, params={"search": name}).json()
class CitizenTasks(BaseCitizen): class CitizenTasks(CitizenEconomy):
tg_contract: dict = {} tg_contract: dict = {}
ot_points: int = 0 ot_points: int = 0
next_ot_time: datetime = None next_ot_time: datetime = None
@ -2321,6 +2393,8 @@ class CitizenTasks(BaseCitizen):
if js.get('message') in ['employee', 'money']: if js.get('message') in ['employee', 'money']:
self.resign_from_employer() self.resign_from_employer()
self.find_new_job() self.find_new_job()
elif js.get('message') in ['not_enough_health_food']:
self.buy_food(120)
self.update_citizen_info() self.update_citizen_info()
self.work() self.work()
else: else:
@ -2375,6 +2449,8 @@ class CitizenTasks(BaseCitizen):
else: else:
if r.json().get('message') == 'employee': if r.json().get('message') == 'employee':
self.find_new_job() 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()) self.reporter.report_action("WORK_OT", r.json())
elif self.energy.food_fights < 1 and self.ot_points >= 24: elif self.energy.food_fights < 1 and self.ot_points >= 24:
self._eat("blue") self._eat("blue")
@ -2431,8 +2507,8 @@ class CitizenTasks(BaseCitizen):
self.ot_points = ot.get("points", 0) self.ot_points = ot.get("points", 0)
class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeaderBoard, class Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
CitizenMedia, CitizenMilitary, CitizenPolitics, CitizenSocial, CitizenTasks): CitizenMedia, CitizenPolitics, CitizenSocial, CitizenMilitary, CitizenTasks):
def __init__(self, email: str = "", password: str = "", auto_login: bool = False): def __init__(self, email: str = "", password: str = "", auto_login: bool = False):
super().__init__(email, password) super().__init__(email, password)
self._last_full_update = constants.min_datetime self._last_full_update = constants.min_datetime
@ -2550,17 +2626,21 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
data = self._get_main_weekly_challenge_data().json() data = self._get_main_weekly_challenge_data().json()
self.details.pp = data.get("player", {}).get("prestigePoints", 0) self.details.pp = data.get("player", {}).get("prestigePoints", 0)
self.details.next_pp.clear() self.details.next_pp.clear()
max_collectable_id = data.get('maxRewardId')
should_collect = False
for reward in data.get("rewards", {}).get("normal", {}): for reward in data.get("rewards", {}).get("normal", {}):
status = reward.get("status", "") status = reward.get("status", "")
if status == "rewarded": if status == "rewarded":
continue continue
elif status == "completed": elif status == "completed":
self._post_main_weekly_challenge_reward(reward.get("id", 0)) should_collect = True
elif reward.get("icon", "") == "energy_booster": elif reward.get("icon", "") == "energy_booster":
pps = re.search(r"Reach (\d+) Prestige Points to unlock the following reward: \+1 Energy", pps = re.search(r"Reach (\d+) Prestige Points to unlock the following reward: \+1 Energy",
reward.get("tooltip", "")) reward.get("tooltip", ""))
if pps: if pps:
self.details.next_pp.append(int(pps.group(1))) self.details.next_pp.append(int(pps.group(1)))
if should_collect:
self._post_main_weekly_challenge_collect_all(max_collectable_id)
def should_fight(self, silent: bool = True) -> Tuple[int, str, bool]: def should_fight(self, silent: bool = True) -> Tuple[int, str, bool]:
count, log_msg, force_fight = super().should_fight() count, log_msg, force_fight = super().should_fight()
@ -2581,6 +2661,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
f"(Recoverable until WC end {max_count}hp | want to do {count}hits)") f"(Recoverable until WC end {max_count}hp | want to do {count}hits)")
count = count if max_count > count else max_count count = count if max_count > count else max_count
if not silent:
self.write_log(log_msg, False) self.write_log(log_msg, False)
return count, log_msg, force_fight return count, log_msg, force_fight
@ -2602,10 +2683,12 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
else: else:
start_time = utils.good_timedelta(start_time.replace(minute=0), timedelta(hours=1)) start_time = utils.good_timedelta(start_time.replace(minute=0), timedelta(hours=1))
while not self.stop_threads.is_set(): while not self.stop_threads.is_set():
self.update_citizen_info()
start_time = utils.good_timedelta(start_time, timedelta(minutes=30)) start_time = utils.good_timedelta(start_time, timedelta(minutes=30))
self.update_citizen_info()
self.update_weekly_challenge()
self.send_state_update() self.send_state_update()
self.send_inventory_update() self.send_inventory_update()
self.update_companies()
self.send_my_companies_update() self.send_my_companies_update()
sleep_seconds = (start_time - self.now).total_seconds() sleep_seconds = (start_time - self.now).total_seconds()
self.stop_threads.wait(sleep_seconds if sleep_seconds > 0 else 0) self.stop_threads.wait(sleep_seconds if sleep_seconds > 0 else 0)
@ -2614,13 +2697,13 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
def send_state_update(self): def send_state_update(self):
data = dict(xp=self.details.xp, cc=self.details.cc, gold=self.details.gold, pp=self.details.pp, 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_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.available, food=self.food['total'], )
self.reporter.send_state_update(**data) self.reporter.send_state_update(**data)
def send_inventory_update(self): def send_inventory_update(self):
self.reporter.report_action("INVENTORY", json_val=self.get_inventory(True)) self.reporter.report_action("INVENTORY", json_val=self.inventory.as_dict)
def send_my_companies_update(self): def send_my_companies_update(self):
self.reporter.report_action('COMPANIES', json_val=self.my_companies.as_dict) self.reporter.report_action('COMPANIES', json_val=self.my_companies.as_dict)
@ -2758,7 +2841,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
for holding in regions.values(): for holding in regions.values():
raw_usage = holding.get_wam_raw_usage() 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: if (raw_usage['frm'] + raw_usage['wrm']) * 100 > free_storage:
self._report_action('WAM_UNAVAILABLE', 'Not enough storage!') self._report_action('WAM_UNAVAILABLE', 'Not enough storage!')
continue continue

View File

@ -3,15 +3,16 @@ import hashlib
import threading import threading
import weakref import weakref
from decimal import Decimal 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 requests import Response, Session, post
from . import utils, constants from . import constants, types, utils
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException', __all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
'ErepublikNetworkException', 'EnergyToFight', 'ErepublikNetworkException', 'EnergyToFight',
'Holding', 'MyCompanies', 'MyJSONEncoder', 'OfferItem', 'Politics', 'Reporter', 'TelegramReporter'] 'Holding', 'MyCompanies', 'MyJSONEncoder', 'OfferItem', 'Politics', 'Reporter', 'TelegramReporter',
'Inventory']
class ErepublikException(Exception): class ErepublikException(Exception):
@ -396,11 +397,11 @@ class Config:
self.spin_wheel_of_fortune = False self.spin_wheel_of_fortune = False
@property @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, 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, 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, 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, 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, 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, continuous_fighting=self.continuous_fighting, auto_buy_raw=self.auto_buy_raw,
@ -454,7 +455,7 @@ class Energy:
return self.recovered + self.recoverable return self.recovered + self.recoverable
@property @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, return dict(limit=self.limit, interval=self.interval, recoverable=self.recoverable, recovered=self.recovered,
reference_time=self.reference_time, food_fights=self.food_fights, 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_recoverable_full=self.is_recoverable_full, is_recovered_full=self.is_recovered_full,
@ -509,7 +510,7 @@ class Details:
return next_level_up - self.xp return next_level_up - self.xp
@property @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, 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, citizen_id=self.citizen_id, citizenship=self.citizenship, current_region=self.current_region,
current_country=self.current_country, residence_region=self.residence_region, current_country=self.current_country, residence_region=self.residence_region,
@ -528,7 +529,7 @@ class Politics:
is_country_president: bool = False is_country_president: bool = False
@property @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, 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_party_president=self.is_party_president, is_congressman=self.is_congressman,
is_country_president=self.is_country_president) is_country_president=self.is_country_president)
@ -566,7 +567,7 @@ class Reporter:
return self.citizen.details.citizen_id return self.citizen.details.citizen_id
@property @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, return dict(name=self.name, email=self.email, citizen_id=self.citizen_id, key=self.key, allowed=self.allowed,
queue=self.__to_update) queue=self.__to_update)
@ -730,7 +731,7 @@ class BattleSide:
return self.country.iso return self.country.iso
@property @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, return dict(points=self.points, country=self.country, is_defender=self.is_defender, allies=self.allies,
deployed=self.deployed) deployed=self.deployed)
@ -973,7 +974,7 @@ class TelegramReporter:
self._last_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-5)) 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)) self._last_full_energy_report = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-30))
if self.__queue: if self.__queue:
self.send_message("\n\n\n\n".join(self.__queue)) self.send_message("Telegram initialized")
def send_message(self, message: str) -> bool: def send_message(self, message: str) -> bool:
self.__queue.append(message) self.__queue.append(message)
@ -1001,13 +1002,19 @@ class TelegramReporter:
new_line = '\n' if multiple else '' new_line = '\n' if multiple else ''
self.send_message(f"New award: {new_line}*{msg}*") self.send_message(f"New award: {new_line}*{msg}*")
def report_fight(self, battle: "Battle", invader: bool, division: "BattleDivision", damage: float, hits: int):
side_txt = (battle.invader if invader else battle.defender).country.iso
self.send_message(f"*Fight report*:\n{int(damage):,d} dmg ({hits} hits) in"
f" [battle {battle.id} for {battle.region_name[:16]}]({battle.link}) in d{division.div} on "
f"{side_txt} side")
def __send_messages(self): def __send_messages(self):
while self._next_time > utils.now(): while self._next_time > utils.now():
if self.__thread_stopper.is_set(): if self.__thread_stopper.is_set():
break break
self.__thread_stopper.wait(utils.get_sleep_seconds(self._next_time)) self.__thread_stopper.wait(utils.get_sleep_seconds(self._next_time))
message = "\n\n\n\n".join(self.__queue) message = "\n\n\n".join(self.__queue)
if self.player_name: if self.player_name:
message = f"Player *{self.player_name}*\n" + message message = f"Player *{self.player_name}*\n" + message
response = post(self.api_url, json=dict(chat_id=self.chat_id, text=message, parse_mode="Markdown")) response = post(self.api_url, json=dict(chat_id=self.chat_id, text=message, parse_mode="Markdown"))
@ -1024,3 +1031,27 @@ class OfferItem(NamedTuple):
amount: int = 0 amount: int = 0
offer_id: int = 0 offer_id: int = 0
citizen_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, List, Union
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 import warnings
from decimal import Decimal from decimal import Decimal
from pathlib import Path from pathlib import Path
from typing import Any, List, Optional, Union, Dict from typing import Any, Dict, List, Union
import requests import requests
@ -64,7 +64,9 @@ def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.da
return constants.erep_tz.normalize(dt + td) 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): if isinstance(date, datetime.date):
date = datetime.datetime.combine(date, datetime.time(0, 0, 0)) date = datetime.datetime.combine(date, datetime.time(0, 0, 0))
return (date - datetime.datetime(2007, 11, 20, 0, 0, 0)).days return (date - datetime.datetime(2007, 11, 20, 0, 0, 0)).days
@ -158,6 +160,7 @@ def write_file(filename: str, content: str) -> int:
def write_request(response: requests.Response, is_error: bool = False): def write_request(response: requests.Response, is_error: bool = False):
from erepublik import Citizen from erepublik import Citizen
# Remove GET args from url name # Remove GET args from url name
url = response.url url = response.url
last_index = url.index("?") if "?" in url else len(response.url) last_index = url.index("?") if "?" in url else len(response.url)
@ -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, 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 Process error logging and email sending to developer
:param interactive: Should print interactively :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 dmg = dmg * 11 / 10
return Decimal(dmg) return Decimal(dmg)
# def _clear_up_battle_memory(battle): # def _clear_up_battle_memory(battle):
# del battle.invader._battle, battle.defender._battle # del battle.invader._battle, battle.defender._battle
# for div_id, division in battle.div.items(): # for div_id, division in battle.div.items():
@ -396,7 +400,12 @@ def wait_for_lock(function):
return None return None
else: else:
instance.concurrency_available.clear() instance.concurrency_available.clear()
try:
ret = function(instance, *args, **kwargs) ret = function(instance, *args, **kwargs)
except Exception as e:
instance.concurrency_available.set()
raise e
instance.concurrency_available.set() instance.concurrency_available.set()
return ret return ret
return wrapper return wrapper

View File

@ -1,6 +1,6 @@
from datetime import timedelta from datetime import timedelta
from erepublik import Citizen, utils, constants from erepublik import Citizen, constants, utils
CONFIG = { CONFIG = {
'email': 'player@email.com', 'email': 'player@email.com',

View File

@ -1,19 +1,19 @@
bump2version==1.0.1 bump2version==1.0.1
coverage==5.3 coverage==5.3.1
edx-sphinx-theme==1.5.0 edx-sphinx-theme==1.6.0
flake8==3.8.4 flake8==3.8.4
ipython>=7.19.0 ipython>=7.19.0
isort==5.6.4 isort==5.7.0
pip==20.3.1 pip==20.3.3
pre-commit==2.9.3
pur==5.3.0
PyInstaller==4.1 PyInstaller==4.1
pytz==2020.4
pytest==6.1.2
responses==0.12.1
setuptools==51.0.0
Sphinx==3.3.1
requests==2.25.0
PySocks==1.7.1 PySocks==1.7.1
tox==3.20.1 pytest==6.2.1
twine==3.2.0 pytz>=2020.5
watchdog==1.0.0 requests>=2.25.1
wheel==0.36.1 responses==0.12.1
setuptools==51.1.1
Sphinx==3.4.2
twine==3.3.0
wheel==0.36.2

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.23.3.2 current_version = 0.23.4.7
commit = True commit = True
tag = True tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)? parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
@ -18,13 +18,13 @@ replace = __version__ = '{new_version}'
universal = 1 universal = 1
[flake8] [flake8]
exclude = docs,.tox,.git,log,debug,venv exclude = docs,.git,log,debug,venv
max-line-length = 120 max-line-length = 120
ignore = D100,D101,D102,D103 ignore = D100,D101,D102,D103
[pycodestyle] [pycodestyle]
max-line-length = 120 max-line-length = 120
exclude = .tox,.git,log,debug,venv, build exclude = .git,log,debug,venv, build
[mypy] [mypy]
python_version = 3.7 python_version = 3.7
@ -37,4 +37,3 @@ warn_unused_configs = True
[isort] [isort]
multi_line_output = 2 multi_line_output = 2
line_length = 120 line_length = 120
not_skip = __init__.py

View File

@ -12,8 +12,8 @@ with open('HISTORY.rst') as history_file:
history = history_file.read() history = history_file.read()
requirements = [ requirements = [
'pytz==2020.4', 'pytz>=2020.0',
'requests==2.25.0', 'requests>=2.24.0,<2.26.0',
'PySocks==1.7.1' 'PySocks==1.7.1'
] ]
@ -50,6 +50,6 @@ setup(
test_suite='tests', test_suite='tests',
tests_require=test_requirements, tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/', url='https://github.com/eeriks/erepublik/',
version='0.23.3.2', version='0.23.4.7',
zip_safe=False, 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