Compare commits

..

59 Commits

Author SHA1 Message Date
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
f88eaccf67 Bump version: 0.23.2.7 → 0.23.2.8 2020-12-07 14:01:29 +02:00
7be129a781 Quickfix for forbidding Dictatorship/Liberation wars 2020-12-07 14:01:22 +02:00
32546505b9 Bump version: 0.23.2.6 → 0.23.2.7 2020-12-04 13:13:17 +02:00
00ceabf8e3 cleanup 2020-12-04 13:13:10 +02:00
19113da8e6 cleanup 2020-12-04 13:11:11 +02:00
3ad7172925 cleanup 2020-12-04 13:08:42 +02:00
179f1a0892 Only init TelegramReporter if both chat id and token have been supplied 2020-12-04 13:08:16 +02:00
65adf707e2 Only init TelegramReporter if both chat id and token have been supplied 2020-12-04 13:08:04 +02:00
29f9ce5ccc Bump version: 0.23.2.5 → 0.23.2.6 2020-12-02 18:40:41 +02:00
fa3881bf10 Updated classes.OfferItem default price to be higher considerably higher than max price for cheapest most expensive items on sale (Q5 houses), Updated house renewal 2020-12-02 18:40:33 +02:00
c1e8e94cba Bump version: 0.23.2.4 → 0.23.2.5 2020-12-02 13:00:51 +02:00
8133461fd7 pep8 2020-12-02 13:00:33 +02:00
c87333411a Hotfix 2020-12-02 12:56:06 +02:00
d679006b34 small fix 2020-12-01 18:56:14 +02:00
fa308db074 Bump version: 0.23.2.3 → 0.23.2.4 2020-12-01 18:49:35 +02:00
a080163af9 Bugfix 2020-12-01 18:49:33 +02:00
8aa159edf7 Bump version: 0.23.2.2 → 0.23.2.3 2020-12-01 18:49:01 +02:00
1211a98227 Bugfix 2020-12-01 18:48:52 +02:00
734a5ef2b6 Bump version: 0.23.2.1 → 0.23.2.2 2020-12-01 18:11:09 +02:00
5c3b405ca8 Added utils.wait_for_lock decorator to wait for concurrency release 2020-12-01 18:11:00 +02:00
75de8fce96 File closing before return 2020-12-01 15:09:45 +02:00
01e5e44350 File closing before return 2020-12-01 15:07:17 +02:00
500409f74a Bump version: 0.23.2 → 0.23.2.1 2020-12-01 15:01:00 +02:00
9d79bb713c naming bugfix 2020-12-01 15:00:55 +02:00
684b2a6ba4 Bump version: 0.23.1.2 → 0.23.2 2020-12-01 14:22:40 +02:00
447aee8134 Concurrency flag to guard against 2020-12-01 14:22:26 +02:00
64249fc3d9 History 2020-12-01 13:11:45 +02:00
95a78b6e7e Bump version: 0.23.1.1 → 0.23.1.2 2020-12-01 13:02:29 +02:00
b338ea598a Seperated battle finding logic from CitizenMilitary.find_battle_and_fight method 2020-12-01 13:02:10 +02:00
17 changed files with 446 additions and 263 deletions

1
.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

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,8 +2,22 @@
History History
======= =======
0.23.2 (2020-12-01)
-------------------
* Added concurrency checks to guard against simultaneous fighting/wam'ing/traveling
* For concurrency checking use `utils.wait_for_lock` decorator
0.23.1 (2020-12-01)
-------------------
* Separated battle finding logic from CitizenMilitary.find_battle_and_fight method
* Base dmg calculations
* Get max hit value for divisions on current side
* Added method to get division stats
* Wheel of fortune updates
0.23.0 (2020-11-26) 0.23.0 (2020-11-26)
------------------- -------------------
* ***0.23 - last 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

@ -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
@ -80,6 +76,7 @@ servedocs: docs ## compile the docs watching for changes
release: dist ## package and upload a release release: dist ## package and upload a release
twine upload dist/* twine upload dist/*
clean
dist: clean ## builds source and wheel package dist: clean ## builds source and wheel package
python setup.py sdist python setup.py sdist

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;
@ -334,6 +402,60 @@
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;
// 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);
} }

View File

@ -4,9 +4,9 @@
__author__ = """Eriks Karls""" __author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv' __email__ = 'eriks@72.lv'
__version__ = '0.23.1.1' __version__ = '0.23.3.4'
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

@ -15,10 +15,10 @@ class SlowRequests(Session):
timeout: datetime.timedelta = datetime.timedelta(milliseconds=500) timeout: datetime.timedelta = datetime.timedelta(milliseconds=500)
uas: List[str] = [ uas: List[str] = [
# Chrome # 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/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', # noqa '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', # noqa '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', # noqa '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/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/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', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
@ -29,14 +29,10 @@ class SlowRequests(Session):
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0',
] ]
debug: bool = False debug: bool = False
@ -85,11 +81,10 @@ class SlowRequests(Session):
if params: if params:
args.update({"params": params}) args.update({"params": params})
body = "[{dt}]\tURL: '{url}'\tMETHOD: {met}\tARGS: {args}\n".format(dt=utils.now().strftime("%F %T"), body = f"[{utils.now().strftime('%F %T')}]\tURL: '{url}'\tMETHOD: {method}\tARGS: {args}\n"
url=url, met=method, args=args)
utils.get_file(self.request_log_name)
with open(self.request_log_name, 'ab') as file: with open(self.request_log_name, 'ab') as file:
file.write(body.encode("UTF-8")) file.write(body.encode("UTF-8"))
pass
def _log_response(self, url, resp, redirect: bool = False): def _log_response(self, url, resp, redirect: bool = False):
from erepublik import Citizen from erepublik import Citizen
@ -99,22 +94,20 @@ class SlowRequests(Session):
self._log_request(hist_resp.request.url, "REDIRECT") self._log_request(hist_resp.request.url, "REDIRECT")
self._log_response(hist_resp.request.url, hist_resp, redirect=True) self._log_response(hist_resp.request.url, hist_resp, redirect=True)
file_data = { fd_path = 'debug/requests'
"path": 'debug/requests', fd_time = self.last_time.strftime('%Y/%m/%d/%H-%M-%S')
"time": self.last_time.strftime('%Y/%m/%d/%H-%M-%S'), fd_name = utils.slugify(url[len(Citizen.url):])
"name": utils.slugify(url[len(Citizen.url):]), fd_extra = "_REDIRECT" if redirect else ""
"extra": "_REDIRECT" if redirect else ""
}
try: try:
utils.json.loads(resp.text) utils.json.loads(resp.text)
file_data.update({"ext": "json"}) fd_ext = 'json'
except utils.json.JSONDecodeError: 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}'
with open(utils.get_file(filename), 'wb') as f: utils.write_file(filename, resp.text)
f.write(resp.text.encode('utf-8')) pass
class CitizenBaseAPI: class CitizenBaseAPI:
@ -292,7 +285,6 @@ class ErepublikCountryAPI(CitizenBaseAPI):
def _post_main_country_donate(self, country_id: int, action: str, value: Union[int, float], def _post_main_country_donate(self, country_id: int, action: str, value: Union[int, float],
quality: int = None) -> Response: quality: int = None) -> Response:
data = dict(countryId=country_id, action=action, _token=self.token, value=value, quality=quality) 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, return self.post(f"{self.url}/main/country-donate", data=data,
headers={"Referer": f"{self.url}/country/economy/Latvia"}) headers={"Referer": f"{self.url}/country/economy/Latvia"})
@ -367,16 +359,20 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
class ErepublikLeaderBoardAPI(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") 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}") 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") 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}") return self.get(f"{self.url}/main/leaderboards-kills-rankings/{country_id}/{weeks}/{mu_id}/{div}")
@ -442,8 +438,9 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
data.update(page=page) data.update(page=page)
return self.post(f"{self.url}/military/battle-console", data=data) return self.post(f"{self.url}/military/battle-console", data=data)
def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response: def _post_military_deploy_bomb(self, battle_id: int, division_id: int, side_id: int, bomb_id: int) -> Response:
data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token) 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) 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: def _post_military_fight_air(self, battle_id: int, side_id: int, zone_id: int) -> Response:
@ -487,7 +484,7 @@ class ErepublikPoliticsAPI(CitizenBaseAPI):
class ErepublikPresidentAPI(CitizenBaseAPI): class ErepublikPresidentAPI(CitizenBaseAPI):
def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response: 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} 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: 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, data = dict(requirments=1, _token=self.token, debate=debate,

View File

@ -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, utils
from .classes import OfferItem from .classes import OfferItem
@ -35,13 +35,15 @@ class BaseCitizen(access_points.CitizenAPI):
eday: int = 0 eday: int = 0
wheel_of_fortune: bool wheel_of_fortune: bool
debug: bool = False
config: classes.Config = None config: classes.Config = None
energy: classes.Energy = None energy: classes.Energy = None
details: classes.Details = None details: classes.Details = None
politics: classes.Politics = None politics: classes.Politics = None
reporter: classes.Reporter = None reporter: classes.Reporter = None
stop_threads: Event = None stop_threads: Event = None
telegram: classes.TelegramBot = None concurrency_available: Event = None
telegram: classes.TelegramReporter = None
r: Response = None r: Response = None
name: str = "Not logged in!" name: str = "Not logged in!"
@ -57,7 +59,9 @@ class BaseCitizen(access_points.CitizenAPI):
self.my_companies = classes.MyCompanies(self) self.my_companies = classes.MyCompanies(self)
self.reporter = classes.Reporter(self) self.reporter = classes.Reporter(self)
self.stop_threads = Event() self.stop_threads = Event()
self.telegram = classes.TelegramBot(stop_event=self.stop_threads) self.concurrency_available = Event()
self.concurrency_available.set()
self.telegram = classes.TelegramReporter(stop_event=self.stop_threads)
self.config.email = email self.config.email = email
self.config.password = password self.config.password = password
@ -413,7 +417,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:
@ -421,6 +425,10 @@ class BaseCitizen(access_points.CitizenAPI):
else: else:
sleep(seconds) sleep(seconds)
def set_debug(self, debug: bool):
self.debug = bool(debug)
self._req.debug = bool(debug)
def to_json(self, indent: bool = False) -> str: def to_json(self, indent: bool = False) -> str:
return utils.json.dumps(self, cls=classes.MyJSONEncoder, indent=4 if indent else None) return utils.json.dumps(self, cls=classes.MyJSONEncoder, indent=4 if indent else None)
@ -599,8 +607,8 @@ class BaseCitizen(access_points.CitizenAPI):
except AttributeError: except AttributeError:
continue continue
if data: if data:
msgs = ["{count} x {kind}, totaling {} {currency}\n" msgs = [f"{d['count']} x {d['kind']}, totaling {d['count'] * d['reward']} "
"{about}".format(d["count"] * d["reward"], **d) for d in data.values()] f"{d['currency']}" for d in data.values()]
msgs = "\n".join(msgs) msgs = "\n".join(msgs)
if self.config.telegram: if self.config.telegram:
@ -928,6 +936,7 @@ class CitizenCompanies(BaseCitizen):
def work_as_manager_in_holding(self, holding: classes.Holding) -> Optional[Dict[str, Any]]: def work_as_manager_in_holding(self, holding: classes.Holding) -> Optional[Dict[str, Any]]:
return self._work_as_manager(holding) return self._work_as_manager(holding)
@utils.wait_for_lock
def _work_as_manager(self, wam_holding: classes.Holding) -> Optional[Dict[str, Any]]: def _work_as_manager(self, wam_holding: classes.Holding) -> Optional[Dict[str, Any]]:
if self.restricted_ip: if self.restricted_ip:
return None return None
@ -1017,7 +1026,7 @@ class CitizenEconomy(CitizenTravel):
ret.update({house_quality: till}) ret.update({house_quality: till})
return ret return ret
def buy_and_activate_house(self, q: int) -> 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.get_inventory()
@ -1030,14 +1039,22 @@ class CitizenEconomy(CitizenTravel):
global_cheapest = self.get_market_offers("House", q)[f"q{q}"] global_cheapest = self.get_market_offers("House", q)[f"q{q}"]
if global_cheapest.price + 200 < local_cheapest.price: if global_cheapest.price + 200 < local_cheapest.price:
if global_cheapest.price + 2000 < self.details.cc:
if self.travel_to_country(global_cheapest.country): if self.travel_to_country(global_cheapest.country):
buy = self.buy_from_market(global_cheapest.offer_id, 1) buy = self.buy_market_offer(global_cheapest, 1)
else: else:
buy = {'error': True, 'message': 'Unable to travel!'} buy = dict(error=True, message='Unable to travel!')
else: else:
buy = self.buy_from_market(local_cheapest.offer_id, 1) buy = dict(error=True, message='Not enough money to buy house!')
if buy["error"]: else:
msg = f"Unable to buy q{q} house! \n{buy['message']}" 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"]}'
self.write_log(msg) self.write_log(msg)
else: else:
ok_to_activate = True ok_to_activate = True
@ -1058,7 +1075,9 @@ class CitizenEconomy(CitizenTravel):
house_durability = self.check_house_durability() house_durability = self.check_house_durability()
for q, active_till in house_durability.items(): for q, active_till in house_durability.items():
if utils.good_timedelta(active_till, - timedelta(hours=48)) <= self.now or forced: if utils.good_timedelta(active_till, - timedelta(hours=48)) <= self.now or forced:
house_durability = self.buy_and_activate_house(q) durability = self.buy_and_activate_house(q)
if durability:
house_durability = durability
return house_durability return house_durability
def activate_house(self, quality: int) -> bool: def activate_house(self, quality: int) -> bool:
@ -1139,11 +1158,13 @@ class CitizenEconomy(CitizenTravel):
return False return False
def post_market_offer(self, industry: int, quality: int, amount: int, price: float) -> bool: def post_market_offer(self, industry: int, quality: int, amount: int, price: float) -> bool:
if isinstance(industry, str):
industry = constants.INDUSTRIES[industry]
if not constants.INDUSTRIES[industry]: if not constants.INDUSTRIES[industry]:
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, 4, 23] else 'raw' _kind = 'final' if industry in [1, 2, 4, 23] else 'raw'
inventory = self.get_inventory() inventory = self.get_inventory()
items = inventory[_kind].get(constants.INDUSTRIES[industry], {_inv_qlt: {'amount': 0}}) items = inventory[_kind].get(constants.INDUSTRIES[industry], {_inv_qlt: {'amount': 0}})
if items[_inv_qlt]['amount'] < amount: if items[_inv_qlt]['amount'] < amount:
@ -1168,7 +1189,7 @@ class CitizenEconomy(CitizenTravel):
self._report_action("ECONOMY_SELL_PRODUCTS", message, kwargs=ret) self._report_action("ECONOMY_SELL_PRODUCTS", message, kwargs=ret)
return not bool(ret.get('error', True)) return not bool(ret.get('error', True))
def buy_from_market(self, offer: int, amount: int) -> dict: def buy_from_market(self, offer: int, amount: int) -> Dict[str, Any]:
ret = self._post_economy_marketplace_actions('buy', offer=offer, amount=amount) ret = self._post_economy_marketplace_actions('buy', offer=offer, amount=amount)
json_ret = ret.json() json_ret = ret.json()
if not json_ret.get('error', True): if not json_ret.get('error', True):
@ -1178,20 +1199,15 @@ class CitizenEconomy(CitizenTravel):
self._report_action("BOUGHT_PRODUCTS", json_ret.get('message'), kwargs=json_ret) self._report_action("BOUGHT_PRODUCTS", json_ret.get('message'), kwargs=json_ret)
return json_ret return json_ret
def buy_market_offer(self, offer: OfferItem, amount: int = None) -> dict: @utils.wait_for_lock
def buy_market_offer(self, offer: OfferItem, amount: int = None) -> Optional[Dict[str, Any]]:
if amount is None or amount > offer.amount: if amount is None or amount > offer.amount:
amount = offer.amount amount = offer.amount
traveled = False traveled = False
if not self.details.current_country == offer.country: if not self.details.current_country == offer.country:
traveled = True traveled = True
self.travel_to_country(offer.country) self.travel_to_country(offer.country)
ret = self._post_economy_marketplace_actions('buy', offer=offer.offer_id, amount=amount) json_ret = self.buy_from_market(offer.offer_id, amount)
json_ret = ret.json()
if not json_ret.get('error', True):
self.details.cc = ret.json()['currency']
self.details.gold = ret.json()['gold']
json_ret.pop("offerUpdate", None)
self._report_action("BOUGHT_PRODUCTS", json_ret.get('message'), kwargs=json_ret)
if traveled: if traveled:
self.travel_to_residence() self.travel_to_residence()
return json_ret return json_ret
@ -1455,9 +1471,8 @@ class CitizenMedia(BaseCitizen):
article_id = 0 article_id = 0
return article_id return article_id
else: else:
raise classes.ErepublikException("Article kind must be one of:\n{}\n'{}' is not supported".format( kinds = "\n".join([f"{k}: {v}" for k, v in kinds.items()])
"\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), kind 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]: def get_article(self, article_id: int) -> Dict[str, Any]:
return self._get_main_article_json(article_id).json() return self._get_main_article_json(article_id).json()
@ -1673,21 +1688,25 @@ class CitizenMilitary(CitizenTravel):
def has_battle_contribution(self): def has_battle_contribution(self):
return bool(self.__last_war_update_data.get("citizen_contribution", [])) return bool(self.__last_war_update_data.get("citizen_contribution", []))
def find_battle_and_fight(self): def find_battle_to_fight(self, silent: bool = False) -> Tuple[
if self.should_fight()[0]: classes.Battle, classes.BattleDivision, classes.BattleSide
self.write_log("Checking for battles to fight in...") ]:
self.update_war_info()
for battle in self.sorted_battles(self.config.sort_battles_time): for battle in self.sorted_battles(self.config.sort_battles_time):
if not isinstance(battle, classes.Battle): if not isinstance(battle, classes.Battle):
continue continue
if battle.is_dict_lib:
continue
battle_zone: Optional[classes.BattleDivision] = None battle_zone: Optional[classes.BattleDivision] = None
for div in battle.div.values(): for div in battle.div.values():
if div.terrain == 0: if div.terrain == 0:
if div.div_end: if div.div_end:
continue continue
maverick_ok = self.maverick and self.config.maverick
if self.config.air and div.is_air: if self.config.air and div.is_air:
battle_zone = div battle_zone = div
break break
elif self.config.ground and not div.is_air and (div.div == self.division or self.maverick): elif self.config.ground and not div.is_air and (div.div == self.division or maverick_ok):
battle_zone = div battle_zone = div
break break
else: else:
@ -1705,6 +1724,7 @@ class CitizenMilitary(CitizenTravel):
defender_side = self.details.current_country in battle.defender.allies + [battle.defender.country, ] defender_side = self.details.current_country in battle.defender.allies + [battle.defender.country, ]
side = battle.defender if defender_side else battle.invader side = battle.defender if defender_side else battle.invader
if not silent:
self.write_log(battle) self.write_log(battle)
travel = (self.config.travel_to_fight and self.should_travel_to_fight() or self.config.force_travel) \ travel = (self.config.travel_to_fight and self.should_travel_to_fight() or self.config.force_travel) \
@ -1712,6 +1732,17 @@ class CitizenMilitary(CitizenTravel):
if not travel: if not travel:
continue continue
yield battle, battle_zone, side
def find_battle_and_fight(self):
if self.should_fight()[0]:
self.write_log("Checking for battles to fight in...")
for battle, division, side in self.find_battle_to_fight():
allies = battle.invader.deployed + battle.defender.deployed + [battle.invader.country,
battle.defender.country]
travel_needed = self.details.current_country not in allies
if battle.start > self.now: if battle.start > self.now:
self.sleep(utils.get_sleep_seconds(battle.start)) self.sleep(utils.get_sleep_seconds(battle.start))
@ -1728,14 +1759,16 @@ class CitizenMilitary(CitizenTravel):
if not self.travel_to_battle(battle, countries_to_travel): if not self.travel_to_battle(battle, countries_to_travel):
break break
if self.change_division(battle, battle_zone):
self.set_default_weapon(battle, battle_zone) if self.change_division(battle, division):
self.fight(battle, battle_zone, side) self.set_default_weapon(battle, division)
self.fight(battle, division, side)
self.travel_to_residence() self.travel_to_residence()
break break
@utils.wait_for_lock
def fight(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None, def fight(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None,
count: int = None) -> int: count: int = None) -> Optional[int]:
"""Fight in a battle. """Fight in a battle.
Will auto activate booster and travel if allowed to do it. Will auto activate booster and travel if allowed to do it.
@ -1778,7 +1811,7 @@ class CitizenMilitary(CitizenTravel):
else: else:
self._eat('blue') self._eat('blue')
if self.energy.recovered < 50 or error_count >= 10 or count <= 0: 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 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.reporter.report_fighting(battle, not side.is_defender, division, total_damage, total_hits)
@ -1815,6 +1848,9 @@ class CitizenMilitary(CitizenTravel):
elif r_json.get("message") == "ZONE_INACTIVE": elif r_json.get("message") == "ZONE_INACTIVE":
self.write_log("Wrong division!!") self.write_log("Wrong division!!")
return 0, 10, 0 return 0, 10, 0
elif r_json.get("message") == "NON_BELLIGERENT":
self.write_log("Dictatorship/Liberation wars are not supported!")
return 0, 10, 0
elif r_json.get("message") in ["FIGHT_DISABLED", "DEPLOYMENT_MODE"]: elif r_json.get("message") in ["FIGHT_DISABLED", "DEPLOYMENT_MODE"]:
self._post_main_profile_update('options', self._post_main_profile_update('options',
params='{"optionName":"enable_web_deploy","optionValue":"off"}') params='{"optionName":"enable_web_deploy","optionValue":"off"}')
@ -1827,7 +1863,18 @@ class CitizenMilitary(CitizenTravel):
self.travel_to_battle(battle, countries) self.travel_to_battle(battle, countries)
err = True err = True
elif r_json.get("message") == "ENEMY_KILLED": elif r_json.get("message") == "ENEMY_KILLED":
# 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 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.energy.recovered = r_json["details"]["wellness"]
self.details.xp = int(r_json["details"]["points"]) self.details.xp = int(r_json["details"]["points"])
damage = r_json["user"]["givenDamage"] * (1.1 if r_json["oldEnemy"]["isNatural"] else 1) damage = r_json["user"]["givenDamage"] * (1.1 if r_json["oldEnemy"]["isNatural"] else 1)
@ -1836,17 +1883,24 @@ class CitizenMilitary(CitizenTravel):
return hits, err, damage return hits, err, damage
def deploy_bomb(self, battle: classes.Battle, bomb_id: int, inv_side: bool = None, count: int = 1) -> int: @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]:
"""Deploy bombs in a battle for given side. """Deploy bombs in a battle for given side.
:param battle: Battle :param battle: Battle
:type battle: Battle :type battle: classes.Battle
:param division: BattleDivision
:type division: classes.BattleDivision
:param bomb_id: int bomb id :param bomb_id: int bomb id
:param inv_side: should deploy on invader side, if None then will deploy in currently available side :type bomb_id: int
:param count: int how many bombs to deploy :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 :return: Deployed count
:rtype: int :rtype: int
""" """
if not isinstance(count, int) or count < 1: if not isinstance(count, int) or count < 1:
count = 1 count = 1
has_traveled = False has_traveled = False
@ -1857,22 +1911,22 @@ class CitizenMilitary(CitizenTravel):
good_countries = [battle.invader.country] + battle.invader.deployed good_countries = [battle.invader.country] + battle.invader.deployed
if self.details.current_country not in good_countries: if self.details.current_country not in good_countries:
has_traveled = self.travel_to_battle(battle, 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: else:
involved = [battle.invader.country, involved = [battle.invader.country,
battle.defender.country] + battle.invader.deployed + battle.defender.deployed battle.defender.country] + battle.invader.deployed + battle.defender.deployed
if self.details.current_country not in involved: if self.details.current_country not in involved:
count = 0 count = 0
side = battle.invader if inv_side else battle.defender
errors = deployed_count = 0 errors = deployed_count = 0
while (not deployed_count == count) and errors < 10: 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'): 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
@ -2122,13 +2176,21 @@ 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
@ -2252,7 +2314,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
@ -2270,6 +2332,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:
@ -2278,7 +2342,7 @@ class CitizenTasks(BaseCitizen):
self._eat("blue") self._eat("blue")
if self.energy.food_fights < 1: if self.energy.food_fights < 1:
seconds = (self.energy.reference_time - self.now).total_seconds() 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.sleep(seconds)
self._eat("blue") self._eat("blue")
self.work() self.work()
@ -2324,6 +2388,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")
@ -2380,10 +2446,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):
debug: bool = False
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
@ -2411,11 +2475,10 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self.update_citizen_info() self.update_citizen_info()
self.reporter.do_init() self.reporter.do_init()
if self.config.telegram: if self.config.telegram and self.config.telegram_chat_id:
# noinspection SpellCheckingInspection self.telegram.do_init(self.config.telegram_chat_id,
self.telegram.do_init(self.config.telegram_chat_id or 620981703, self.config.telegram_token,
self.config.telegram_token or "864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o", self.name)
"" if self.config.telegram_chat_id or self.config.telegram_token else self.name)
self.telegram.send_message(f"*Started* {utils.now():%F %T}") self.telegram.send_message(f"*Started* {utils.now():%F %T}")
self.update_all(True) self.update_all(True)
@ -2436,7 +2499,6 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
if self.details.gold >= 54: if self.details.gold >= 54:
self.buy_tg_contract() self.buy_tg_contract()
else: else:
self.write_log(f"Training ground contract active but " self.write_log(f"Training ground contract active but "
f"don't have enough gold ({self.details.gold}g {self.details.cc}cc)") f"don't have enough gold ({self.details.gold}g {self.details.cc}cc)")
if self.energy.is_energy_full and self.config.telegram: if self.energy.is_energy_full and self.config.telegram:
@ -2473,8 +2535,8 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
data[(title, reward)]['count'] += count data[(title, reward)]['count'] += count
self._post_main_global_alerts_close(medal.get('id')) self._post_main_global_alerts_close(medal.get('id'))
if data: if data:
msgs = ["{count} x {kind}," msgs = [f"{d['count']} x {d['kind']}, totaling {d['count'] * d['reward']} "
" totaling {} {currency}".format(d["count"] * d["reward"], **d) for d in data.values()] f"{d['currency']}" for d in data.values()]
msgs = "\n".join(msgs) msgs = "\n".join(msgs)
if self.config.telegram: if self.config.telegram:
@ -2483,10 +2545,6 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
for info in data.values(): for info in data.values():
self.reporter.report_action("NEW_MEDAL", info) self.reporter.report_action("NEW_MEDAL", info)
def set_debug(self, debug: bool):
self.debug = bool(debug)
self._req.debug = bool(debug)
def set_pin(self, pin: str): def set_pin(self, pin: str):
self.details.pin = str(pin[:4]) self.details.pin = str(pin[:4])
@ -2538,6 +2596,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
@ -2563,7 +2622,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
start_time = utils.good_timedelta(start_time, timedelta(minutes=30)) start_time = utils.good_timedelta(start_time, timedelta(minutes=30))
self.send_state_update() self.send_state_update()
self.send_inventory_update() self.send_inventory_update()
self.reporter.report_action('COMPANIES', json_val=self.my_companies.as_dict) 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)
except: # noqa except: # noqa
@ -2579,6 +2638,9 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
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.get_inventory(True))
def send_my_companies_update(self):
self.reporter.report_action('COMPANIES', json_val=self.my_companies.as_dict)
def eat(self): def eat(self):
""" """
Try to eat food Try to eat food
@ -2608,7 +2670,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
if not amount: if not amount:
inv_resp = self._get_economy_inventory_items().json() inv_resp = self._get_economy_inventory_items().json()
category = "rawMaterials" if kind.endswith("Raw") else "finalProducts" 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) amount = inv_resp.get("inventoryItems").get(category).get("items").get(item).get("amount", 0)
if amount >= 1: if amount >= 1:
@ -2637,7 +2699,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
elif kind.endswith("Raw"): elif kind.endswith("Raw"):
self.sell_produced_product(kind, 1) self.sell_produced_product(kind, 1)
else: 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")): 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")) raw_kind = re.search(r"not_enough_(\w+)_raw", response.get("message"))
if raw_kind: if raw_kind:
@ -2683,7 +2745,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self._report_action("WORK_AS_MANAGER", "Not enough money to work as manager!", kwargs=response) 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!") self.write_log("Not enough money to work as manager!")
else: 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._report_action("WORK_AS_MANAGER", f"Worked as manager failed: {msg}", kwargs=response)
self.write_log(msg) self.write_log(msg)
@ -2722,7 +2784,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
wam_count = self.my_companies.get_total_wam_count() wam_count = self.my_companies.get_total_wam_count()
if 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.my_companies.ff_lockdown = wam_count
self.travel_to_residence() self.travel_to_residence()
return bool(wam_count) return bool(wam_count)

View File

@ -3,14 +3,15 @@ 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, utils
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException', __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']
class ErepublikException(Exception): class ErepublikException(Exception):
@ -419,7 +420,7 @@ class Energy:
self._recovery_time = utils.now() self._recovery_time = utils.now()
def __repr__(self): 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): def set_reference_time(self, recovery_time: datetime.datetime):
self._recovery_time = recovery_time.replace(microsecond=0) self._recovery_time = recovery_time.replace(microsecond=0)
@ -598,10 +599,10 @@ class Reporter:
for unreported_data in self.__to_update: for unreported_data in self.__to_update:
unreported_data.update(player_id=self.citizen_id, key=self.key) unreported_data.update(player_id=self.citizen_id, key=self.key)
unreported_data = utils.json.loads(utils.json.dumps(unreported_data, cls=MyJSONEncoder)) 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() self.__to_update.clear()
data = utils.json.loads(utils.json.dumps(data, cls=MyJSONEncoder)) 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 return r
def register_account(self): def register_account(self):
@ -609,7 +610,7 @@ class Reporter:
try: try:
r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id)) r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id))
if not r.json().get("status"): if not r.json().get("status"):
self._req.post("{}/bot/register".format(self.url), json=dict(name=self.name, email=self.email, self._req.post(f"{self.url}/bot/register", json=dict(name=self.name, email=self.email,
player_id=self.citizen_id)) player_id=self.citizen_id))
finally: finally:
self.__registered = True self.__registered = True
@ -719,11 +720,11 @@ class BattleSide:
def __repr__(self): def __repr__(self):
side_text = "Defender" if self.is_defender else "Invader " side_text = "Defender" if self.is_defender else "Invader "
return f"<BattleSide: {side_text} {self.country.name}|{self.points:02d}p>" return f"<BattleSide: {side_text} {self.country.name}|{self.points:>2d}p>"
def __str__(self): def __str__(self):
side_text = "Defender" if self.is_defender else "Invader " side_text = "Defender" if self.is_defender else "Invader "
return f"{side_text} {self.country.name} - {self.points:02d} points" return f"{side_text} {self.country.name} - {self.points:>2d} points"
def __format__(self, format_spec): def __format__(self, format_spec):
return self.country.iso return self.country.iso
@ -789,7 +790,7 @@ class BattleDivision:
return constants.TERRAINS[self.terrain] return constants.TERRAINS[self.terrain]
def __str__(self): def __str__(self):
base_name = f"Div #{self.id} d{self.div}" base_name = f"D{self.div} #{self.id}"
if self.terrain: if self.terrain:
base_name += f" ({self.terrain_display})" base_name += f" ({self.terrain_display})"
if self.div_end: if self.div_end:
@ -895,12 +896,12 @@ class Battle:
time_now = utils.now() time_now = utils.now()
is_started = self.start < utils.now() is_started = self.start < utils.now()
if is_started: if is_started:
time_part = " {}".format(time_now - self.start) time_part = f" {time_now - self.start}"
else: 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]} | " return (f"Battle {self.id} for {self.region_name[:16]:16} | "
f"{self.invader} : {self.defender} | Round time {time_part}") f"{self.invader} : {self.defender} | Round time {time_part} | {'R'+str(self.zone_id):>3}")
def __repr__(self): def __repr__(self):
return f"<Battle #{self.id} {self.invader}:{self.defender} R{self.zone_id}>" return f"<Battle #{self.id} {self.invader}:{self.defender} R{self.zone_id}>"
@ -937,7 +938,7 @@ class EnergyToFight:
return self.energy return self.energy
class TelegramBot: class TelegramReporter:
__initialized: bool = False __initialized: bool = False
__queue: List[str] __queue: List[str]
chat_id: int = 0 chat_id: int = 0
@ -962,10 +963,12 @@ class TelegramBot:
'last_time': self._last_time, 'next_time': self._next_time, 'queue': self.__queue, 'last_time': self._last_time, 'next_time': self._next_time, 'queue': self.__queue,
'initialized': self.__initialized, 'has_threads': not self._threads} '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.chat_id = chat_id
self.api_url = "https://api.telegram.org/bot{}/sendMessage".format(token) self.api_url = f"https://api.telegram.org/bot{token}/sendMessage"
self.player_name = player_name self.player_name = player_name or ""
self.__initialized = True self.__initialized = True
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))
@ -981,7 +984,7 @@ class TelegramBot:
self._threads = [t for t in self._threads if t.is_alive()] self._threads = [t for t in self._threads if t.is_alive()]
self._next_time = utils.good_timedelta(utils.now(), datetime.timedelta(seconds=20)) self._next_time = utils.good_timedelta(utils.now(), datetime.timedelta(seconds=20))
if not self._threads: 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 = threading.Thread(target=self.__send_messages, name=name)
send_thread.start() send_thread.start()
self._threads.append(send_thread) self._threads.append(send_thread)
@ -1016,7 +1019,7 @@ class TelegramBot:
class OfferItem(NamedTuple): class OfferItem(NamedTuple):
price: float = 99_999. price: float = 999_999_999.
country: constants.Country = constants.Country(0, "", "", "") country: constants.Country = constants.Country(0, "", "", "")
amount: int = 0 amount: int = 0
offer_id: int = 0 offer_id: int = 0

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, Optional, Union
import requests import requests
@ -21,11 +21,11 @@ try:
except ImportError: except ImportError:
import json 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', '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', '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'] 'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock']
if not sys.version_info >= (3, 6): if not sys.version_info >= (3, 6):
raise AssertionError('This script requires Python version 3.6 and higher\n' raise AssertionError('This script requires Python version 3.6 and higher\n'
@ -93,7 +93,7 @@ def interactive_sleep(sleep_seconds: int):
# seconds = seconds % 30 if seconds % 30 else 30 # seconds = seconds % 30 if seconds % 30 else 30
else: else:
seconds = 1 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() sys.stdout.flush()
time.sleep(seconds) time.sleep(seconds)
sleep_seconds -= seconds sleep_seconds -= seconds
@ -105,7 +105,7 @@ silent_sleep = time.sleep
def _write_log(msg, timestamp: bool = True, should_print: bool = False): def _write_log(msg, timestamp: bool = True, should_print: bool = False):
erep_time_now = now() 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()]) txt = "\n".join(["\n".join(textwrap.wrap(line, 120)) for line in txt.splitlines()])
if not os.path.isdir('log'): if not os.path.isdir('log'):
os.mkdir('log') os.mkdir('log')
@ -152,11 +152,13 @@ def get_file(filepath: str) -> str:
def write_file(filename: str, content: str) -> int: def write_file(filename: str, content: str) -> int:
filename = get_file(filename) filename = get_file(filename)
with open(filename, 'ab') as f: with open(filename, 'ab') as f:
return f.write(content.encode("utf-8")) ret = f.write(content.encode("utf-8"))
return ret
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)
@ -171,10 +173,10 @@ def write_request(response: requests.Response, is_error: bool = False):
ext = "html" ext = "html"
if not is_error: 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) write_file(filename, html)
else: 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'), "content": html.encode('utf-8'),
"mimetype": "application/json" if ext == "json" else "text/html"} "mimetype": "application/json" if ext == "json" else "text/html"}
@ -195,14 +197,14 @@ def send_email(name: str, content: List[Any], player=None, local_vars: Dict[str,
if promo: if promo:
resp = {"name": "%s.html" % name, "mimetype": "text/html", resp = {"name": "%s.html" % name, "mimetype": "text/html",
"content": file_content_template.format(title="Promo", body="<br/>".join(content))} "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: elif captcha:
resp = {"name": "%s.html" % name, "mimetype": "text/html", resp = {"name": "%s.html" % name, "mimetype": "text/html",
"content": file_content_template.format(title="ReCaptcha", body="<br/>".join(content))} "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: 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()) + \ body = "".join(traceback.format_stack()) + \
"\n\n" + \ "\n\n" + \
@ -383,3 +385,23 @@ def get_final_hit_dmg(base_dmg: Union[Decimal, float, str], rang: int,
def deprecation(message): def deprecation(message):
warnings.warn(message, DeprecationWarning, stacklevel=2) warnings.warn(message, DeprecationWarning, stacklevel=2)
def wait_for_lock(function):
def wrapper(instance, *args, **kwargs):
if not instance.concurrency_available.wait(600):
e = 'Concurrency not freed in 10min!'
instance.write_log(e)
if instance.debug:
instance.report_error(e)
return None
else:
instance.concurrency_available.clear()
try:
ret = function(instance, *args, **kwargs)
except Exception as e:
instance.concurrency_available.set()
raise e
instance.concurrency_available.set()
return ret
return wrapper

View File

@ -97,7 +97,7 @@ def main():
player.set_debug(CONFIG.get('debug', False)) player.set_debug(CONFIG.get('debug', False))
player.login() player.login()
if CONFIG.get('battle_launcher'): 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 = threading.Thread(target=_battle_launcher, args=(player,), name=name)
state_thread.start() state_thread.start()

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

View File

@ -4,16 +4,15 @@ edx-sphinx-theme==1.5.0
flake8==3.8.4 flake8==3.8.4
ipython>=7.19.0 ipython>=7.19.0
isort==5.6.4 isort==5.6.4
pip==20.3 pip==20.3.3
PyInstaller==4.1 PyInstaller==4.1
pytz==2020.4 pytz==2020.4
pytest==6.1.2 pytest==6.2.1
responses==0.12.1 responses==0.12.1
setuptools==50.3.2 setuptools==51.0.0
Sphinx==3.3.1 Sphinx==3.3.1
requests==2.25.0 requests>=2.24.0,<2.26.0
PySocks==1.7.1 PySocks==1.7.1
tox==3.20.1
twine==3.2.0 twine==3.2.0
watchdog==0.10.4 wheel==0.36.2
wheel==0.35.1 pur==5.3.0

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.23.1.1 current_version = 0.23.3.4
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

View File

@ -13,7 +13,7 @@ with open('HISTORY.rst') as history_file:
requirements = [ requirements = [
'pytz==2020.4', 'pytz==2020.4',
'requests==2.25.0', 'requests>=2.24.0,<2.26.0',
'PySocks==1.7.1' 'PySocks==1.7.1'
] ]
@ -45,11 +45,11 @@ setup(
keywords='erepublik', keywords='erepublik',
name='eRepublik', name='eRepublik',
packages=find_packages(include=['erepublik']), packages=find_packages(include=['erepublik']),
python_requires='>=3.6, <4', python_requires='>=3.7, <4',
setup_requires=setup_requirements, setup_requires=setup_requirements,
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.1.1', version='0.23.3.4',
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