Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
e23a67231e | |||
6a03d99abf | |||
2e344749a6 | |||
5aecefbd9d | |||
8b9ee5042d | |||
1e93006c3d | |||
b13bfcdbf3 | |||
771dbdf826 | |||
9646d112d2 | |||
a09c37a065 | |||
ba75e961fa | |||
3b5780dbd6 | |||
fccd0134b5 | |||
b9010fa856 | |||
3e5410289e | |||
661a019b0a | |||
23d682959d | |||
5806ccb6ca | |||
a9bc78b701 | |||
c458eb4b1c | |||
4af4d284c9 | |||
104c1a0b16 | |||
86f820771b | |||
2a7af0cb7d | |||
94de509026 | |||
82d913bc47 | |||
c462eac369 | |||
d522a4faeb | |||
084a47b07a | |||
5082855440 | |||
c9971f3570 | |||
c0122eccdf | |||
2524173da0 | |||
4a4b991faf | |||
e87c48124c | |||
f0f47566a0 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -37,7 +37,6 @@ pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
|
14
.travis.yml
14
.travis.yml
@ -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
|
||||
|
||||
|
@ -70,14 +70,14 @@ Ready to contribute? Here's how to set up `erepublik` for local development.
|
||||
|
||||
Now you can make your changes locally.
|
||||
|
||||
5. When you're done making changes, check that your changes pass flake8 and the
|
||||
tests, including testing other Python versions with tox::
|
||||
5. When you're done making changes, check that your changes pass flake8, isort and the
|
||||
tests::
|
||||
|
||||
$ flake8 erepublik tests
|
||||
$ python setup.py test or py.test
|
||||
$ tox
|
||||
$ isort erepublik
|
||||
$ python setup.py test
|
||||
|
||||
To get flake8 and tox, just pip install them into your virtualenv.
|
||||
To get flake8 and isort, just pip install them into your virtualenv.
|
||||
|
||||
6. Commit your changes and push your branch to GitHub::
|
||||
|
||||
|
21
HISTORY.rst
21
HISTORY.rst
@ -2,6 +2,25 @@
|
||||
History
|
||||
=======
|
||||
|
||||
0.23.4 (2021-01-05)
|
||||
-------------------
|
||||
* Added expiration data to inventory items
|
||||
* Inventory is now based on `classes.Inventory`
|
||||
* Requirement update to make them more flexible regarding versions required
|
||||
* Restructured inventory
|
||||
|
||||
0.23.3 (2020-12-17)
|
||||
-------------------
|
||||
* Fixed carpet bombing
|
||||
* Fixed hits done amount when fighting on ground
|
||||
* Minor requirement updates
|
||||
* Minor tweaks to method signatures
|
||||
* Fixed buy food if unable to work or train because not enough energy and not enough food
|
||||
* Fixed applications for party presidency and congress if not a party member
|
||||
* Removed tox
|
||||
* Updates to github.io config generator
|
||||
* Fixed `Citizen.concurrency_available` stuck in unset state if exception is being raised while doing concurrency task
|
||||
|
||||
0.23.2 (2020-12-01)
|
||||
-------------------
|
||||
* Added concurrency checks to guard against simultaneous fighting/wam'ing/traveling
|
||||
@ -17,7 +36,7 @@ History
|
||||
|
||||
0.23.0 (2020-11-26)
|
||||
-------------------
|
||||
* ***0.23 - last supported version for Python 3.7.***
|
||||
* ***0.23 - last officially supported version for Python 3.7.***
|
||||
* Added `Config.maverick` switch, to allow/deny automated fighting in non native divisions if the player has MaverickPack
|
||||
* Added `CitizenMedia.get_article(article_id:int)` method to get article data
|
||||
* Added `CitizenMedia.delete_article(article_id:int)` method to delete article
|
||||
|
5
Makefile
5
Makefile
@ -47,7 +47,6 @@ clean-pyc: ## remove Python file artifacts
|
||||
rm -rf log/
|
||||
|
||||
clean-test: ## remove test and coverage artifacts
|
||||
rm -fr .tox/
|
||||
rm -f .coverage
|
||||
rm -fr htmlcov/
|
||||
rm -fr .pytest_cache
|
||||
@ -58,9 +57,6 @@ lint: ## check style with flake8
|
||||
test: ## run tests quickly with the default Python
|
||||
python setup.py test
|
||||
|
||||
test-all: ## run tests on every Python version with tox
|
||||
tox
|
||||
|
||||
coverage: ## check code coverage quickly with the default Python
|
||||
coverage run --source erepublik setup.py test
|
||||
coverage report -m
|
||||
@ -80,6 +76,7 @@ servedocs: docs ## compile the docs watching for changes
|
||||
|
||||
release: dist ## package and upload a release
|
||||
twine upload dist/*
|
||||
clean
|
||||
|
||||
dist: clean ## builds source and wheel package
|
||||
python setup.py sdist
|
||||
|
188
docs/index.html
188
docs/index.html
@ -8,15 +8,12 @@
|
||||
<meta name="generator" content="Jekyll v4.0.1">
|
||||
<title>eBot configuration</title>
|
||||
<!-- CSS only -->
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
|
||||
crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
|
||||
|
||||
<!-- JS, Popper.js, and jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@ -37,14 +34,14 @@
|
||||
<div class="col-12 col-sm-8 col-md-6">
|
||||
<h3>Login data</h3>
|
||||
<div class="form-group">
|
||||
<input type="email" class="form-control form-control-sm" onchange="updateJson()" id="email" placeholder="E-mail...">
|
||||
<input type="password" class="form-control form-control-sm mt-3" onchange="updateJson()" id="password" disabled placeholder="Password..."
|
||||
<label for="email" class="hidden"></label><input type="email" class="form-control" onchange="updateJson()" id="email" placeholder="E-mail...">
|
||||
<label for="password" class="hidden"></label><input type="password" class="form-control" onchange="updateJson()" id="password" disabled placeholder="Password..."
|
||||
aria-describedby="passwordHelpBlock">
|
||||
<small id="passwordHelpBlock" class="form-text text-muted"><strong>NEVER</strong> enter Your passwords on 3rd party sites and <strong class="text-upper">DO NOT</strong> reuse Your
|
||||
password!</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="col-6 col-sm-4 col-md-3">
|
||||
<h3>Basic tasks</h3>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="work" checked>
|
||||
@ -59,29 +56,6 @@
|
||||
<label class="custom-control-label" for="ot">Work overtime</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<h3>Misc</h3>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="renew_houses" checked>
|
||||
<label class="custom-control-label" for="renew_houses">Auto renew houses</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="random_sleep" checked>
|
||||
<label class="custom-control-label" for="random_sleep">Random sleep</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="buy_gold">
|
||||
<label class="custom-control-label" for="buy_gold">Auto buy 10g</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="interactive" checked>
|
||||
<label class="custom-control-label" for="interactive">Interactive</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="debug">
|
||||
<label class="custom-control-label" for="debug">Debug</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@ -200,6 +174,90 @@
|
||||
<label class="custom-control-label" for="epic_hunt_ebs">Spend <small>[all]</small> EBs in epics</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6">
|
||||
<h3 class="mt-4">Misc</h3>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="renew_houses" checked>
|
||||
<label class="custom-control-label" for="renew_houses">Auto renew houses</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="random_sleep" checked>
|
||||
<label class="custom-control-label" for="random_sleep">Random sleep</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="buy_gold">
|
||||
<label class="custom-control-label" for="buy_gold">Auto buy 10g</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="interactive" checked>
|
||||
<label class="custom-control-label" for="interactive">Interactive</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="debug">
|
||||
<label class="custom-control-label" for="debug">Debug</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="spin_wheel_of_fortune">
|
||||
<label class="custom-control-label" for="spin_wheel_of_fortune">Auto spin 10% of cc in WheelOfFortune</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="congress">
|
||||
<label class="custom-control-label" for="congress">Auto candidate for congress</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="party_president">
|
||||
<label class="custom-control-label" for="party_president">Auto candidate for party presidency</label>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="contribute_cc">
|
||||
<label class="custom-control-label" for="contribute_cc">Contribute cc to country's account (weekly)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6">
|
||||
<h3 class="mt-4">Advanced</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="telegram">
|
||||
<label class="custom-control-label" for="telegram">Notify trough Telegram</label>
|
||||
</div>
|
||||
|
||||
<label for="telegram_chat_id">Telegram's chat ID</label>
|
||||
<input type="text" class="form-control" onchange="updateJson()" id="telegram_chat_id" placeholder="Chat ID">
|
||||
<label for="telegram_token">Telegram Bot token</label>
|
||||
<input type="text" class="form-control" onchange="updateJson()" id="telegram_token" placeholder="864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o">
|
||||
<small id="telegramTokenHelp" class="form-text text-muted">Only enter token if You want to use your own Telegram bot for notification sending</small>
|
||||
</div>
|
||||
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="proxy">
|
||||
<label class="custom-control-label" for="proxy">Use proxy</label>
|
||||
</div>
|
||||
<label for="proxy_kind">Proxy kind</label>
|
||||
<div class="form-group">
|
||||
<select class="form-control custom-select" id="proxy_kind">
|
||||
<option value="socks" selected>SOCKS5</option>
|
||||
<option value="http">HTTP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="proxy_host">Proxy hostname or IP address</label>
|
||||
<input type="text" class="form-control" onchange="updateJson()" id="proxy_host" placeholder="localhost">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="proxy_port">Proxy port</label>
|
||||
<input type="text" class="form-control" onchange="updateJson()" id="proxy_port" placeholder="8080">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="proxy_user">Proxy username (optional)</label>
|
||||
<input type="text" class="form-control" onchange="updateJson()" id="proxy_user" placeholder="user">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="proxy_password">Proxy password (optional)</label>
|
||||
<input type="password" class="form-control" onchange="updateJson()" id="proxy_password" placeholder="password" disabled>
|
||||
<small id="proxyHelpBlock" class="form-text text-muted"><strong>NEVER</strong> enter Your passwords on 3rd party sites and <strong class="text-upper">DO NOT</strong> reuse Your password!</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -212,7 +270,9 @@
|
||||
function disable(element){
|
||||
element.checked = false;
|
||||
element.disabled = true;
|
||||
element.value = null;
|
||||
}
|
||||
|
||||
function updateJson() {
|
||||
let config = {};
|
||||
let email = document.getElementById('email'); // Generated
|
||||
@ -228,6 +288,14 @@
|
||||
config.renew_houses = renew_houses.checked;
|
||||
let random_sleep = document.getElementById('random_sleep'); // Generated
|
||||
config.random_sleep = random_sleep.checked;
|
||||
let spin_wheel_of_fortune = document.getElementById('spin_wheel_of_fortune'); // Generated
|
||||
config.spin_wheel_of_fortune = spin_wheel_of_fortune.checked;
|
||||
let congress = document.getElementById('congress'); // Generated
|
||||
config.congress = congress.checked;
|
||||
let party_president = document.getElementById('party_president'); // Generated
|
||||
config.party_president = party_president.checked;
|
||||
let contribute_cc = document.getElementById('contribute_cc'); // Generated
|
||||
config.contribute_cc = contribute_cc.checked ? 10000 : false;
|
||||
let buy_gold = document.getElementById('buy_gold'); // Generated
|
||||
config.buy_gold = buy_gold.checked;
|
||||
let interactive = document.getElementById('interactive'); // Generated
|
||||
@ -297,7 +365,7 @@
|
||||
let travel_to_fight = document.getElementById('travel_to_fight'); // Generated
|
||||
let epic_hunt = document.getElementById('epic_hunt'); // Generated
|
||||
let epic_hunt_ebs = document.getElementById('epic_hunt_ebs'); // Generated
|
||||
if (config.fight){
|
||||
if (config.fight) {
|
||||
air.disabled = false;
|
||||
ground.disabled = false;
|
||||
boosters.disabled = false;
|
||||
@ -334,6 +402,60 @@
|
||||
config.travel_to_fight = travel_to_fight.checked;
|
||||
config.epic_hunt = epic_hunt.checked;
|
||||
config.epic_hunt_ebs = config.epic_hunt ? epic_hunt_ebs.checked : config.epic_hunt;
|
||||
|
||||
// Advanced
|
||||
|
||||
|
||||
let telegram = document.getElementById('telegram'); // Generated
|
||||
config.telegram = telegram.checked;
|
||||
let telegram_chat_id = document.getElementById('telegram_chat_id'); // Generated
|
||||
let telegram_token = document.getElementById('telegram_token'); // Generated
|
||||
if (config.telegram) {
|
||||
telegram_chat_id.disabled = false;
|
||||
telegram_token.disabled = false;
|
||||
} else {
|
||||
disable(telegram_chat_id);
|
||||
disable(telegram_token);
|
||||
}
|
||||
config.telegram_chat_id = telegram_chat_id.value;
|
||||
config.telegram_token = telegram_token.value;
|
||||
|
||||
let _proxy = {};
|
||||
let proxy = document.getElementById('proxy'); // Generated
|
||||
let proxy_kind = document.getElementById('proxy_kind'); // Generated
|
||||
let proxy_host = document.getElementById('proxy_host'); // Generated
|
||||
let proxy_port = document.getElementById('proxy_port'); // Generated
|
||||
let proxy_user = document.getElementById('proxy_user'); // Generated
|
||||
|
||||
if (proxy.checked) {
|
||||
proxy_kind.disabled = false;
|
||||
proxy_host.disabled = false;
|
||||
proxy_port.disabled = false;
|
||||
proxy_user.disabled = false;
|
||||
} else {
|
||||
disable(proxy_kind);
|
||||
disable(proxy_host);
|
||||
disable(proxy_port);
|
||||
disable(proxy_user);
|
||||
}
|
||||
_proxy.kind = proxy_kind.value;
|
||||
_proxy.host = proxy_host.value;
|
||||
_proxy.port = proxy_port.value;
|
||||
_proxy.username = proxy_user.value;
|
||||
_proxy.password = ""
|
||||
if (proxy.checked) {
|
||||
delete config._proxy;
|
||||
config.proxy = _proxy;
|
||||
} else {
|
||||
delete config.proxy;
|
||||
config._proxy = {
|
||||
'kind': 'socks or http',
|
||||
'host': 'localhost',
|
||||
'port': 8080,
|
||||
'username': 'optional',
|
||||
'password': 'optional'
|
||||
}
|
||||
}
|
||||
let pre = document.getElementById('json-output');
|
||||
pre.textContent = JSON.stringify(config, null, 2);
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
__author__ = """Eriks Karls"""
|
||||
__email__ = 'eriks@72.lv'
|
||||
__version__ = '0.23.2.8'
|
||||
__version__ = '0.23.4.1'
|
||||
|
||||
from erepublik import classes, utils, constants
|
||||
from erepublik import classes, constants, utils
|
||||
from erepublik.citizen import Citizen
|
||||
|
||||
__all__ = ["classes", "utils", "Citizen", 'constants']
|
||||
|
@ -15,10 +15,10 @@ class SlowRequests(Session):
|
||||
timeout: datetime.timedelta = datetime.timedelta(milliseconds=500)
|
||||
uas: List[str] = [
|
||||
# Chrome
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36', # noqa
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36', # noqa
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36', # noqa
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36', # noqa
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36',
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
|
||||
@ -81,8 +81,7 @@ class SlowRequests(Session):
|
||||
if params:
|
||||
args.update({"params": params})
|
||||
|
||||
body = "[{dt}]\tURL: '{url}'\tMETHOD: {met}\tARGS: {args}\n".format(dt=utils.now().strftime("%F %T"),
|
||||
url=url, met=method, args=args)
|
||||
body = f"[{utils.now().strftime('%F %T')}]\tURL: '{url}'\tMETHOD: {method}\tARGS: {args}\n"
|
||||
with open(self.request_log_name, 'ab') as file:
|
||||
file.write(body.encode("UTF-8"))
|
||||
pass
|
||||
@ -95,20 +94,18 @@ class SlowRequests(Session):
|
||||
self._log_request(hist_resp.request.url, "REDIRECT")
|
||||
self._log_response(hist_resp.request.url, hist_resp, redirect=True)
|
||||
|
||||
file_data = {
|
||||
"path": 'debug/requests',
|
||||
"time": self.last_time.strftime('%Y/%m/%d/%H-%M-%S'),
|
||||
"name": utils.slugify(url[len(Citizen.url):]),
|
||||
"extra": "_REDIRECT" if redirect else ""
|
||||
}
|
||||
fd_path = 'debug/requests'
|
||||
fd_time = self.last_time.strftime('%Y/%m/%d/%H-%M-%S')
|
||||
fd_name = utils.slugify(url[len(Citizen.url):])
|
||||
fd_extra = "_REDIRECT" if redirect else ""
|
||||
|
||||
try:
|
||||
utils.json.loads(resp.text)
|
||||
file_data.update({"ext": "json"})
|
||||
fd_ext = 'json'
|
||||
except utils.json.JSONDecodeError:
|
||||
file_data.update({"ext": "html"})
|
||||
fd_ext = 'html'
|
||||
|
||||
filename = 'debug/requests/{time}_{name}{extra}.{ext}'.format(**file_data)
|
||||
filename = f'{fd_path}/{fd_time}_{fd_name}{fd_extra}.{fd_ext}'
|
||||
utils.write_file(filename, resp.text)
|
||||
pass
|
||||
|
||||
@ -288,7 +285,6 @@ class ErepublikCountryAPI(CitizenBaseAPI):
|
||||
|
||||
def _post_main_country_donate(self, country_id: int, action: str, value: Union[int, float],
|
||||
quality: int = None) -> Response:
|
||||
|
||||
data = dict(countryId=country_id, action=action, _token=self.token, value=value, quality=quality)
|
||||
return self.post(f"{self.url}/main/country-donate", data=data,
|
||||
headers={"Referer": f"{self.url}/country/economy/Latvia"})
|
||||
@ -363,16 +359,20 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
|
||||
|
||||
|
||||
class ErepublikLeaderBoardAPI(CitizenBaseAPI):
|
||||
def _get_main_leaderboards_damage_aircraft_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0) -> Response: # noqa
|
||||
def _get_main_leaderboards_damage_aircraft_rankings(self, country_id: int, weeks: int = 0,
|
||||
mu_id: int = 0) -> Response: # noqa
|
||||
return self.get(f"{self.url}/main/leaderboards-damage-aircraft-rankings/{country_id}/{weeks}/{mu_id}/0")
|
||||
|
||||
def _get_main_leaderboards_damage_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0, div: int = 0) -> Response: # noqa
|
||||
def _get_main_leaderboards_damage_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0,
|
||||
div: int = 0) -> Response: # noqa
|
||||
return self.get(f"{self.url}/main/leaderboards-damage-rankings/{country_id}/{weeks}/{mu_id}/{div}")
|
||||
|
||||
def _get_main_leaderboards_kills_aircraft_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0) -> Response: # noqa
|
||||
def _get_main_leaderboards_kills_aircraft_rankings(self, country_id: int, weeks: int = 0,
|
||||
mu_id: int = 0) -> Response: # noqa
|
||||
return self.get(f"{self.url}/main/leaderboards-kills-aircraft-rankings/{country_id}/{weeks}/{mu_id}/0")
|
||||
|
||||
def _get_main_leaderboards_kills_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0, div: int = 0) -> Response: # noqa
|
||||
def _get_main_leaderboards_kills_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0,
|
||||
div: int = 0) -> Response: # noqa
|
||||
return self.get(f"{self.url}/main/leaderboards-kills-rankings/{country_id}/{weeks}/{mu_id}/{div}")
|
||||
|
||||
|
||||
@ -438,8 +438,9 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
|
||||
data.update(page=page)
|
||||
return self.post(f"{self.url}/military/battle-console", data=data)
|
||||
|
||||
def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response:
|
||||
data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token)
|
||||
def _post_military_deploy_bomb(self, battle_id: int, division_id: int, side_id: int, bomb_id: int) -> Response:
|
||||
data = dict(battleId=battle_id, battleZoneId=division_id, sideId=side_id, sideCountryId=side_id,
|
||||
bombId=bomb_id, _token=self.token)
|
||||
return self.post(f"{self.url}/military/deploy-bomb", data=data)
|
||||
|
||||
def _post_military_fight_air(self, battle_id: int, side_id: int, zone_id: int) -> Response:
|
||||
@ -483,7 +484,7 @@ class ErepublikPoliticsAPI(CitizenBaseAPI):
|
||||
class ErepublikPresidentAPI(CitizenBaseAPI):
|
||||
def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response:
|
||||
data = {'_token': self.token, 'warId': war_id, 'regionName': region_name, 'regionNameConfirm': region_name}
|
||||
return self.post('{}/wars/attack-region/{}/{}'.format(self.url, war_id, region_id), data=data)
|
||||
return self.post(f'{self.url}/wars/attack-region/{war_id}/{region_id}', data=data)
|
||||
|
||||
def _post_new_war(self, self_country_id: int, attack_country_id: int, debate: str = "") -> Response:
|
||||
data = dict(requirments=1, _token=self.token, debate=debate,
|
||||
|
@ -2,7 +2,7 @@ import re
|
||||
import sys
|
||||
import warnings
|
||||
import weakref
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, time
|
||||
from decimal import Decimal
|
||||
from itertools import product
|
||||
from threading import Event
|
||||
@ -11,7 +11,7 @@ from typing import Any, Dict, List, NoReturn, Optional, Set, Tuple, Union
|
||||
|
||||
from requests import HTTPError, RequestException, Response
|
||||
|
||||
from . import utils, classes, access_points, constants
|
||||
from . import access_points, classes, constants, utils, types
|
||||
from .classes import OfferItem
|
||||
|
||||
|
||||
@ -20,9 +20,7 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
_last_inventory_update: datetime = constants.min_datetime
|
||||
|
||||
promos: Dict[str, datetime] = None
|
||||
inventory: Dict[str, Dict[str, Dict[int, Dict[str, Union[str, int, float]]]]]
|
||||
inventory_status: Dict[str, int]
|
||||
boosters: Dict[int, Dict[int, int]] = {50: {}, 100: {}}
|
||||
inventory: classes.Inventory
|
||||
ot_points: int = 0
|
||||
|
||||
food: Dict[str, int] = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
|
||||
@ -43,7 +41,7 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
reporter: classes.Reporter = None
|
||||
stop_threads: Event = None
|
||||
concurrency_available: Event = None
|
||||
telegram: classes.TelegramBot = None
|
||||
telegram: classes.TelegramReporter = None
|
||||
|
||||
r: Response = None
|
||||
name: str = "Not logged in!"
|
||||
@ -61,12 +59,11 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
self.stop_threads = Event()
|
||||
self.concurrency_available = Event()
|
||||
self.concurrency_available.set()
|
||||
self.telegram = classes.TelegramBot(stop_event=self.stop_threads)
|
||||
self.telegram = classes.TelegramReporter(stop_event=self.stop_threads)
|
||||
|
||||
self.config.email = email
|
||||
self.config.password = password
|
||||
self.inventory = {}
|
||||
self.inventory_status = dict(used=0, total=0)
|
||||
self.inventory = classes.Inventory()
|
||||
self.wheel_of_fortune = False
|
||||
|
||||
def get_csrf_token(self):
|
||||
@ -248,7 +245,7 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
"""
|
||||
self._update_inventory_data(self._get_economy_inventory_items().json())
|
||||
|
||||
def get_inventory(self, force: bool = False):
|
||||
def get_inventory(self, force: bool = False) -> classes.Inventory:
|
||||
if utils.good_timedelta(self._last_inventory_update, timedelta(minutes=2)) < self.now or force:
|
||||
self.update_inventory()
|
||||
return self.inventory
|
||||
@ -257,17 +254,25 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
if not isinstance(inv_data, dict):
|
||||
raise TypeError("Parameter `inv_data` must be dict not '{type(data)}'!")
|
||||
|
||||
def _expire_value_to_python(_expire_value: str) -> Dict[str, Union[int, datetime]]:
|
||||
_data = re.search(
|
||||
r'((?P<amount>\d+) item\(s\) )?[eE]xpires? on Day (?P<eday>\d,\d{3}), (?P<time>\d\d:\d\d)',
|
||||
_expire_value).groupdict()
|
||||
eday = utils.date_from_eday(int(_data['eday'].replace(',', '')))
|
||||
dt = constants.erep_tz.localize(datetime.combine(eday, time(*[int(_) for _ in _data['time'].split(':')])))
|
||||
return {'amount': _data.get('amount'), 'expiration': dt}
|
||||
|
||||
status = inv_data.get("inventoryStatus", {})
|
||||
if status:
|
||||
self.inventory_status.clear()
|
||||
self.inventory_status.update(used=status.get("usedStorage"), total=status.get("totalStorage"))
|
||||
self.inventory.used = status.get("usedStorage")
|
||||
self.inventory.total = status.get("totalStorage")
|
||||
data = inv_data.get('inventoryItems', {})
|
||||
if not data:
|
||||
return
|
||||
self._last_inventory_update = self.now
|
||||
self.food.update({"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0})
|
||||
self.eb_small = self.eb_double = self.eb_normal = 0
|
||||
active_items: Dict[str, Dict[int, Dict[str, Union[str, int]]]] = {}
|
||||
active_items: types.InvFinal = {}
|
||||
if data.get("activeEnhancements", {}).get("items", {}):
|
||||
for item_data in data.get("activeEnhancements", {}).get("items", {}).values():
|
||||
if item_data.get('token'):
|
||||
@ -278,29 +283,34 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
kind = constants.INDUSTRIES[constants.INDUSTRIES[kind]]
|
||||
if kind not in active_items:
|
||||
active_items[kind] = {}
|
||||
expiration_info = []
|
||||
if item_data.get('attributes').get('expirationInfo'):
|
||||
expire_info = item_data.get('attributes').get('expirationInfo')
|
||||
expiration_info = [_expire_value_to_python(v) for v in expire_info['value']]
|
||||
icon = item_data['icon'] if item_data[
|
||||
'icon'] else "//www.erepublik.net/images/modules/manager/tab_storage.png"
|
||||
item_data = dict(name=item_data.get("name"), time_left=item_data['active']['time_left'], icon=icon,
|
||||
kind=kind,
|
||||
quality=item_data.get("quality", 0))
|
||||
inv_item: types.InvFinalItem = dict(
|
||||
name=item_data.get("name"), time_left=item_data['active']['time_left'], icon=icon,
|
||||
kind=kind, expiration=expiration_info, quality=item_data.get("quality", 0)
|
||||
)
|
||||
|
||||
if item_data.get('isPackBooster'):
|
||||
active_items[kind].update({0: item_data})
|
||||
active_items[kind].update({0: inv_item})
|
||||
else:
|
||||
active_items[kind].update({item_data.get("quality"): item_data})
|
||||
active_items[kind].update({inv_item.get("quality"): inv_item})
|
||||
|
||||
final_items: Dict[str, Dict[int, Dict[str, Union[str, int]]]] = {}
|
||||
final_items: types.InvFinal = {}
|
||||
boosters: types.InvBooster = {}
|
||||
if data.get("finalProducts", {}).get("items", {}):
|
||||
for item_data in data.get("finalProducts", {}).get("items", {}).values():
|
||||
is_booster: bool = False
|
||||
name = item_data['name']
|
||||
|
||||
if item_data.get('type'):
|
||||
if item_data.get('type') in ['damageBoosters', "aircraftDamageBoosters"]:
|
||||
kind = f"{item_data['type']}{item_data['quality']}"
|
||||
if item_data['quality'] == 5:
|
||||
self.boosters[50].update({item_data['duration']: item_data['amount']})
|
||||
elif item_data['quality'] == 10:
|
||||
self.boosters[100].update({item_data['duration']: item_data['amount']})
|
||||
# in ['damageBoosters', "aircraftDamageBoosters", 'prestigePointsBoosters']
|
||||
if item_data.get('type').endswith('Boosters'):
|
||||
is_booster = True
|
||||
kind = item_data['type']
|
||||
|
||||
delta = item_data['duration']
|
||||
if delta // 3600:
|
||||
@ -337,8 +347,14 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
if constants.INDUSTRIES[kind]:
|
||||
kind = constants.INDUSTRIES[constants.INDUSTRIES[kind]]
|
||||
|
||||
if kind not in final_items:
|
||||
final_items[kind] = {}
|
||||
if is_booster:
|
||||
if kind not in boosters:
|
||||
boosters[kind] = {}
|
||||
if item_data.get('quality', 0) not in boosters[kind]:
|
||||
boosters[kind][item_data['quality']] = {}
|
||||
else:
|
||||
if kind not in final_items:
|
||||
final_items[kind] = {}
|
||||
|
||||
if item_data['icon']:
|
||||
icon = item_data['icon']
|
||||
@ -355,10 +371,23 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
icon = "/images/modules/pvp/ghost_boosters/icon_booster_30_60.png"
|
||||
else:
|
||||
icon = "//www.erepublik.net/images/modules/manager/tab_storage.png"
|
||||
_item_data = dict(kind=kind, quality=item_data.get('quality', 0), amount=item_data.get('amount', 0),
|
||||
durability=item_data.get('duration', 0), icon=icon, name=name)
|
||||
if item_data.get('type') in ('damageBoosters', "aircraftDamageBoosters"):
|
||||
_item_data = {_item_data['durability']: _item_data}
|
||||
|
||||
expiration_info = []
|
||||
if item_data.get('attributes'):
|
||||
if item_data.get('attributes').get('expirationInfo'):
|
||||
expire_info = item_data.get('attributes').get('expirationInfo')
|
||||
expiration_info = [_expire_value_to_python(v) for v in expire_info['value']]
|
||||
elif item_data.get('attributes').get('expiration'):
|
||||
_exp = item_data.get('attributes').get('expiration')
|
||||
exp_dt = (utils.date_from_eday(int(_exp['value'].replace(',', ''))))
|
||||
expiration_info = [{'amount': item_data.get('amount'), 'expiration': exp_dt}]
|
||||
_inv_item: Dict[int, types.InvFinalItem]
|
||||
inv_item: types.InvFinalItem = dict(
|
||||
kind=kind, quality=item_data.get('quality', 0), icon=icon, expiration=expiration_info,
|
||||
amount=item_data.get('amount'), durability=item_data.get('duration', 0), name=name
|
||||
)
|
||||
if is_booster:
|
||||
_inv_item = {inv_item['durability']: inv_item}
|
||||
else:
|
||||
if item_data.get('type') == 'bomb':
|
||||
firepower = 0
|
||||
@ -367,11 +396,14 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
except AttributeError:
|
||||
pass
|
||||
finally:
|
||||
_item_data.update(fire_power=firepower)
|
||||
_item_data = {_item_data['quality']: _item_data}
|
||||
final_items[kind].update(_item_data)
|
||||
inv_item.update(fire_power=firepower)
|
||||
_inv_item = {inv_item['quality']: inv_item}
|
||||
if is_booster:
|
||||
boosters[kind][inv_item['quality']].update(_inv_item)
|
||||
else:
|
||||
final_items[kind].update(_inv_item)
|
||||
|
||||
raw_materials: Dict[str, Dict[int, Dict[str, Union[str, int]]]] = {}
|
||||
raw_materials: types.InvRaw = {}
|
||||
if data.get("rawMaterials", {}).get("items", {}):
|
||||
for item_data in data.get("rawMaterials", {}).get("items", {}).values():
|
||||
if item_data['isPartial']:
|
||||
@ -401,8 +433,11 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
offers[kind] = {}
|
||||
|
||||
offers[kind].update(offer_data)
|
||||
self.inventory.clear()
|
||||
self.inventory.update(active=active_items, final=final_items, raw=raw_materials, offers=offers)
|
||||
self.inventory.active = active_items
|
||||
self.inventory.final = final_items
|
||||
self.inventory.boosters = boosters
|
||||
self.inventory.raw = raw_materials
|
||||
self.inventory.offers = offers
|
||||
self.food["total"] = sum([self.food[q] * constants.FOOD_ENERGY[q] for q in constants.FOOD_ENERGY])
|
||||
|
||||
def write_log(self, *args, **kwargs):
|
||||
@ -417,7 +452,7 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
else:
|
||||
utils.process_error(msg, self.name, sys.exc_info(), self, None, None)
|
||||
|
||||
def sleep(self, seconds: int):
|
||||
def sleep(self, seconds: Union[int, float, Decimal]):
|
||||
if seconds < 0:
|
||||
seconds = 0
|
||||
if self.config.interactive:
|
||||
@ -607,8 +642,8 @@ class BaseCitizen(access_points.CitizenAPI):
|
||||
except AttributeError:
|
||||
continue
|
||||
if data:
|
||||
msgs = ["{count} x {kind}, totaling {} {currency}\n"
|
||||
"{about}".format(d["count"] * d["reward"], **d) for d in data.values()]
|
||||
msgs = [f"{d['count']} x {d['kind']}, totaling {d['count'] * d['reward']} "
|
||||
f"{d['currency']}" for d in data.values()]
|
||||
|
||||
msgs = "\n".join(msgs)
|
||||
if self.config.telegram:
|
||||
@ -946,13 +981,13 @@ class CitizenCompanies(BaseCitizen):
|
||||
raw_factories = wam_holding.get_wam_companies(raw_factory=True)
|
||||
fin_factories = wam_holding.get_wam_companies(raw_factory=False)
|
||||
|
||||
free_inventory = self.inventory_status["total"] - self.inventory_status["used"]
|
||||
free_inventory = self.inventory.total - self.inventory.used
|
||||
wam_list = raw_factories + fin_factories
|
||||
wam_list = wam_list[:self.energy.food_fights]
|
||||
|
||||
if int(free_inventory * 0.75) < self.my_companies.get_needed_inventory_usage(wam_list):
|
||||
self.update_inventory()
|
||||
free_inventory = self.inventory_status["total"] - self.inventory_status["used"]
|
||||
free_inventory = self.inventory.total - self.inventory.used
|
||||
|
||||
while wam_list and free_inventory < self.my_companies.get_needed_inventory_usage(wam_list):
|
||||
wam_list.pop(-1)
|
||||
@ -1021,7 +1056,7 @@ class CitizenEconomy(CitizenTravel):
|
||||
def check_house_durability(self) -> Dict[int, datetime]:
|
||||
ret = {}
|
||||
inv = self.get_inventory()
|
||||
for house_quality, active_house in inv['active'].get('House', {}).items():
|
||||
for house_quality, active_house in inv.active.get('House', {}).items():
|
||||
till = utils.good_timedelta(self.now, timedelta(seconds=active_house['time_left']))
|
||||
ret.update({house_quality: till})
|
||||
return ret
|
||||
@ -1030,7 +1065,7 @@ class CitizenEconomy(CitizenTravel):
|
||||
original_region = self.details.current_country, self.details.current_region
|
||||
ok_to_activate = False
|
||||
inv = self.get_inventory()
|
||||
if not inv['final'].get('House', {}).get(q, {}):
|
||||
if not inv.final.get('House', {}).get(q, {}):
|
||||
countries = [self.details.citizenship, ]
|
||||
if self.details.current_country != self.details.citizenship:
|
||||
countries.append(self.details.current_country)
|
||||
@ -1039,16 +1074,22 @@ class CitizenEconomy(CitizenTravel):
|
||||
|
||||
global_cheapest = self.get_market_offers("House", q)[f"q{q}"]
|
||||
if global_cheapest.price + 200 < local_cheapest.price:
|
||||
if self.travel_to_country(global_cheapest.country):
|
||||
buy = self.buy_market_offer(global_cheapest, 1)
|
||||
if global_cheapest.price + 2000 < self.details.cc:
|
||||
if self.travel_to_country(global_cheapest.country):
|
||||
buy = self.buy_market_offer(global_cheapest, 1)
|
||||
else:
|
||||
buy = dict(error=True, message='Unable to travel!')
|
||||
else:
|
||||
buy = {'error': True, 'message': 'Unable to travel!'}
|
||||
buy = dict(error=True, message='Not enough money to buy house!')
|
||||
else:
|
||||
buy = self.buy_market_offer(local_cheapest, 1)
|
||||
if local_cheapest.price < self.details.cc:
|
||||
buy = self.buy_market_offer(local_cheapest, 1)
|
||||
else:
|
||||
buy = dict(error=True, message='Not enough money to buy house!')
|
||||
if buy is None:
|
||||
pass
|
||||
elif buy["error"]:
|
||||
msg = f"Unable to buy q{q} house! \n{buy['message']}"
|
||||
msg = f'Unable to buy q{q} house! \n{buy["message"]}'
|
||||
self.write_log(msg)
|
||||
else:
|
||||
ok_to_activate = True
|
||||
@ -1078,7 +1119,8 @@ class CitizenEconomy(CitizenTravel):
|
||||
r: Dict[str, Any] = self._post_economy_activate_house(quality).json()
|
||||
self._update_inventory_data(r)
|
||||
if r.get("status") and not r.get("error"):
|
||||
house: Dict[str, Union[int, str]] = self.get_inventory()['active']['House'][quality]
|
||||
inventory = self.get_inventory()
|
||||
house = inventory.active.get('House', {}).get(quality)
|
||||
time_left = timedelta(seconds=house["time_left"])
|
||||
active_until = utils.good_timedelta(self.now, time_left)
|
||||
self._report_action(
|
||||
@ -1158,12 +1200,14 @@ class CitizenEconomy(CitizenTravel):
|
||||
self.write_log(f"Trying to sell unsupported industry {industry}")
|
||||
|
||||
_inv_qlt = quality if industry in [1, 2, 3, 4, 23] else 0
|
||||
_kind = 'final' if industry in [1, 2, 4, 23] else 'raw'
|
||||
final_kind = industry in [1, 2, 4, 23]
|
||||
inventory = self.get_inventory()
|
||||
items = inventory[_kind].get(constants.INDUSTRIES[industry], {_inv_qlt: {'amount': 0}})
|
||||
items = (inventory.final if final_kind else inventory.raw).get(constants.INDUSTRIES[industry],
|
||||
{_inv_qlt: {'amount': 0}})
|
||||
if items[_inv_qlt]['amount'] < amount:
|
||||
inventory = self.get_inventory(True)
|
||||
items = inventory[_kind].get(constants.INDUSTRIES[industry], {_inv_qlt: {'amount': 0}})
|
||||
items = (inventory.final if final_kind else inventory.raw).get(constants.INDUSTRIES[industry],
|
||||
{_inv_qlt: {'amount': 0}})
|
||||
if items[_inv_qlt]['amount'] < amount:
|
||||
self._report_action("ECONOMY_SELL_PRODUCTS", "Unable to sell! Not enough items in storage!",
|
||||
kwargs=dict(inventory=items[_inv_qlt], amount=amount))
|
||||
@ -1465,9 +1509,8 @@ class CitizenMedia(BaseCitizen):
|
||||
article_id = 0
|
||||
return article_id
|
||||
else:
|
||||
raise classes.ErepublikException("Article kind must be one of:\n{}\n'{}' is not supported".format(
|
||||
"\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), kind
|
||||
))
|
||||
kinds = "\n".join([f"{k}: {v}" for k, v in kinds.items()])
|
||||
raise classes.ErepublikException(f"Article kind must be one of:\n{kinds}\n'{kind}' is not supported")
|
||||
|
||||
def get_article(self, article_id: int) -> Dict[str, Any]:
|
||||
return self._get_main_article_json(article_id).json()
|
||||
@ -1783,7 +1826,7 @@ class CitizenMilitary(CitizenTravel):
|
||||
self._report_action("IP_BLACKLISTED", "Fighting is not allowed from restricted IP!")
|
||||
return 1
|
||||
if not division.is_air and self.config.boosters:
|
||||
self.activate_dmg_booster()
|
||||
self.activate_damage_booster(not division.is_air)
|
||||
if side is None:
|
||||
side = battle.defender if self.details.citizenship in battle.defender.allies + [
|
||||
battle.defender.country] else battle.invader
|
||||
@ -1806,7 +1849,7 @@ class CitizenMilitary(CitizenTravel):
|
||||
else:
|
||||
self._eat('blue')
|
||||
if self.energy.recovered < 50 or error_count >= 10 or count <= 0:
|
||||
self.write_log("Hits: {:>4} | Damage: {}".format(total_hits, total_damage))
|
||||
self.write_log(f"Hits: {total_hits:>4} | Damage: {total_damage}")
|
||||
ok_to_fight = False
|
||||
if total_damage:
|
||||
self.reporter.report_fighting(battle, not side.is_defender, division, total_damage, total_hits)
|
||||
@ -1858,7 +1901,18 @@ class CitizenMilitary(CitizenTravel):
|
||||
self.travel_to_battle(battle, countries)
|
||||
err = True
|
||||
elif r_json.get("message") == "ENEMY_KILLED":
|
||||
hits = (self.energy.recovered - r_json["details"]["wellness"]) // 10
|
||||
# Non-InfantryKit players
|
||||
if r_json['user']['earnedXp']:
|
||||
hits = r_json['user']['earnedXp']
|
||||
# InfantryKit player
|
||||
# The almost always safe way (breaks on levelup hit)
|
||||
elif self.energy.recovered >= r_json["details"]["wellness"]: # Haven't reached levelup
|
||||
hits = (self.energy.recovered - r_json["details"]["wellness"]) // 10
|
||||
else:
|
||||
hits = r_json['hits']
|
||||
if r_json['user']['epicBattle']:
|
||||
hits /= 1 + r_json['user']['epicBattle']
|
||||
|
||||
self.energy.recovered = r_json["details"]["wellness"]
|
||||
self.details.xp = int(r_json["details"]["points"])
|
||||
damage = r_json["user"]["givenDamage"] * (1.1 if r_json["oldEnemy"]["isNatural"] else 1)
|
||||
@ -1868,14 +1922,20 @@ class CitizenMilitary(CitizenTravel):
|
||||
return hits, err, damage
|
||||
|
||||
@utils.wait_for_lock
|
||||
def deploy_bomb(self, battle: classes.Battle, bomb_id: int, inv_side: bool = None, count: int = 1) -> Optional[int]:
|
||||
def deploy_bomb(self, battle: classes.Battle, division: classes.BattleDivision, bomb_id: int, inv_side: bool,
|
||||
count: int = 1) -> Optional[int]:
|
||||
"""Deploy bombs in a battle for given side.
|
||||
|
||||
:param battle: Battle
|
||||
:type battle: Battle
|
||||
:type battle: classes.Battle
|
||||
:param division: BattleDivision
|
||||
:type division: classes.BattleDivision
|
||||
:param bomb_id: int bomb id
|
||||
:param inv_side: should deploy on invader side, if None then will deploy in currently available side
|
||||
:param count: int how many bombs to deploy
|
||||
:type bomb_id: int
|
||||
:param inv_side: should deploy on invader side
|
||||
:type inv_side: bool
|
||||
:param count: how many bombs to deploy
|
||||
:type count: int
|
||||
:return: Deployed count
|
||||
:rtype: int
|
||||
"""
|
||||
@ -1890,22 +1950,22 @@ class CitizenMilitary(CitizenTravel):
|
||||
good_countries = [battle.invader.country] + battle.invader.deployed
|
||||
if self.details.current_country not in good_countries:
|
||||
has_traveled = self.travel_to_battle(battle, good_countries)
|
||||
elif inv_side is not None:
|
||||
good_countries = [battle.defender.country] + battle.defender.deployed
|
||||
if self.details.current_country not in good_countries:
|
||||
has_traveled = self.travel_to_battle(battle, good_countries)
|
||||
else:
|
||||
involved = [battle.invader.country,
|
||||
battle.defender.country] + battle.invader.deployed + battle.defender.deployed
|
||||
if self.details.current_country not in involved:
|
||||
count = 0
|
||||
side = battle.invader if inv_side else battle.defender
|
||||
errors = deployed_count = 0
|
||||
while (not deployed_count == count) and errors < 10:
|
||||
r = self._post_military_deploy_bomb(battle.id, bomb_id).json()
|
||||
r = self._post_military_deploy_bomb(battle.id, division.id, side.id, bomb_id).json()
|
||||
if not r.get('error'):
|
||||
deployed_count += 1
|
||||
self.sleep(0.5)
|
||||
elif r.get('message') == 'LOCKED':
|
||||
sleep(0.5)
|
||||
self.sleep(0.5)
|
||||
elif r.get('message') == 'INVALID_BOMB':
|
||||
errors = 10
|
||||
else:
|
||||
errors += 1
|
||||
|
||||
@ -1958,34 +2018,45 @@ class CitizenMilitary(CitizenTravel):
|
||||
|
||||
return utils.calculate_hit(0, rang, True, elite, ne, 0, 20 if weapon else 0)
|
||||
|
||||
def activate_dmg_booster(self):
|
||||
if self.config.boosters:
|
||||
if not self.get_active_ground_damage_booster():
|
||||
duration = 0
|
||||
for length, amount in self.boosters[50].items():
|
||||
if amount > 2:
|
||||
duration = length
|
||||
def activate_damage_booster(self, ground: bool = True):
|
||||
kind = 'damageBoosters' if ground else 'aircraftDamageBoosters'
|
||||
if self.config.boosters and not self.get_active_damage_booster(ground):
|
||||
booster: Optional[types.InvFinalItem] = None
|
||||
for quality, data in sorted(self.inventory.boosters.get(kind, {}).items(), key=lambda x: x[0]):
|
||||
for _duration, _booster in sorted(data.items(), key=lambda y: y[0]):
|
||||
critical_amount = 2 if quality < 10 and ground else 10
|
||||
if _booster.get('amount') > critical_amount:
|
||||
booster = _booster
|
||||
break
|
||||
if duration:
|
||||
self._report_action("MILITARY_BOOSTER", f"Activated 50% {duration / 60}h damage booster")
|
||||
self._post_economy_activate_booster(5, duration, "damage")
|
||||
break
|
||||
if booster:
|
||||
self._report_action("MILITARY_BOOSTER", f"Activated {booster['name']}")
|
||||
self._post_economy_activate_booster(booster['quality'], booster['durability'],
|
||||
'damage' if ground else 'air_damage')
|
||||
|
||||
def get_active_damage_booster(self, ground: bool = True) -> int:
|
||||
kind = 'damageBoosters' if ground else 'aircraftDamageBoosters'
|
||||
inventory = self.get_inventory()
|
||||
boosters = inventory.active.get(kind, {})
|
||||
quality = 0
|
||||
for q, boost in boosters.items():
|
||||
if boost['quality'] * 10 > quality:
|
||||
quality = boost['quality'] * 10
|
||||
return quality
|
||||
|
||||
def get_active_ground_damage_booster(self) -> int:
|
||||
inventory = self.get_inventory()
|
||||
quality = 0
|
||||
if inventory['active'].get('damageBoosters', {}).get(10):
|
||||
quality = 100
|
||||
elif inventory['active'].get('damageBoosters', {}).get(5):
|
||||
quality = 50
|
||||
return quality
|
||||
return self.get_active_damage_booster(True)
|
||||
|
||||
def get_active_air_damage_booster(self) -> int:
|
||||
return self.get_active_damage_booster(False)
|
||||
|
||||
def activate_battle_effect(self, battle_id: int, kind: str) -> Response:
|
||||
self._report_action('MILITARY_BOOSTER', f'Activated {kind} booster')
|
||||
return self._post_main_activate_battle_effect(battle_id, kind, self.details.citizen_id)
|
||||
|
||||
def activate_pp_booster(self, battle_id: int) -> Response:
|
||||
self._report_action('MILITARY_BOOSTER', 'Activated PrestigePoint booster')
|
||||
return self._post_military_fight_activate_booster(battle_id, 1, 180, "prestige_points")
|
||||
def activate_pp_booster(self, pp_item: types.InvFinalItem) -> Response:
|
||||
self._report_action('MILITARY_BOOSTER', f'Activated {pp_item["name"]}')
|
||||
return self._post_economy_activate_booster(pp_item['quality'], pp_item['durability'], 'prestige_points')
|
||||
|
||||
def _rw_choose_side(self, battle: classes.Battle, side: classes.BattleSide) -> Response:
|
||||
return self._post_main_battlefield_travel(side.id, battle.id)
|
||||
@ -2155,13 +2226,22 @@ class CitizenPolitics(BaseCitizen):
|
||||
ret.update({int(id_): name})
|
||||
return ret
|
||||
|
||||
def candidate_for_congress(self, presentation: str = "") -> Response:
|
||||
self._report_action('POLITIC_CONGRESS', 'Applied for congress elections')
|
||||
return self._post_candidate_for_congress(presentation)
|
||||
def candidate_for_party_presidency(self) -> Optional[Response]:
|
||||
if self.politics.is_party_member:
|
||||
self._report_action('POLITIC_PARTY_PRESIDENT', 'Applied for party president elections')
|
||||
return self._get_candidate_party(self.politics.party_slug)
|
||||
else:
|
||||
self._report_action('POLITIC_CONGRESS',
|
||||
'Unable to apply for party president elections - not a party member')
|
||||
return None
|
||||
|
||||
def candidate_for_party_presidency(self) -> Response:
|
||||
self._report_action('POLITIC_PARTY_PRESIDENT', 'Applied for party president elections')
|
||||
return self._get_candidate_party(self.politics.party_slug)
|
||||
def candidate_for_congress(self, presentation: str = "") -> Optional[Response]:
|
||||
if self.politics.is_party_member:
|
||||
self._report_action('POLITIC_CONGRESS', 'Applied for congress elections')
|
||||
return self._post_candidate_for_congress(presentation)
|
||||
else:
|
||||
self._report_action('POLITIC_CONGRESS', 'Unable to apply for congress elections - not a party member')
|
||||
return None
|
||||
|
||||
def get_country_president_election_result(
|
||||
self, country: constants.Country, year: int, month: int
|
||||
@ -2285,7 +2365,7 @@ class CitizenSocial(BaseCitizen):
|
||||
return self._get_main_city_data_residents(city_id, params={"search": name}).json()
|
||||
|
||||
|
||||
class CitizenTasks(BaseCitizen):
|
||||
class CitizenTasks(CitizenEconomy):
|
||||
tg_contract: dict = {}
|
||||
ot_points: int = 0
|
||||
next_ot_time: datetime = None
|
||||
@ -2303,6 +2383,8 @@ class CitizenTasks(BaseCitizen):
|
||||
if js.get('message') in ['employee', 'money']:
|
||||
self.resign_from_employer()
|
||||
self.find_new_job()
|
||||
elif js.get('message') in ['not_enough_health_food']:
|
||||
self.buy_food(120)
|
||||
self.update_citizen_info()
|
||||
self.work()
|
||||
else:
|
||||
@ -2311,7 +2393,7 @@ class CitizenTasks(BaseCitizen):
|
||||
self._eat("blue")
|
||||
if self.energy.food_fights < 1:
|
||||
seconds = (self.energy.reference_time - self.now).total_seconds()
|
||||
self.write_log("I don't have energy to work. Will sleep for {}s".format(seconds))
|
||||
self.write_log(f"I don't have energy to work. Will sleep for {seconds}s")
|
||||
self.sleep(seconds)
|
||||
self._eat("blue")
|
||||
self.work()
|
||||
@ -2357,6 +2439,8 @@ class CitizenTasks(BaseCitizen):
|
||||
else:
|
||||
if r.json().get('message') == 'employee':
|
||||
self.find_new_job()
|
||||
elif r.json().get('message') == 'not_enough_health_food':
|
||||
self.buy_food(120)
|
||||
self.reporter.report_action("WORK_OT", r.json())
|
||||
elif self.energy.food_fights < 1 and self.ot_points >= 24:
|
||||
self._eat("blue")
|
||||
@ -2413,8 +2497,8 @@ class CitizenTasks(BaseCitizen):
|
||||
self.ot_points = ot.get("points", 0)
|
||||
|
||||
|
||||
class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeaderBoard,
|
||||
CitizenMedia, CitizenMilitary, CitizenPolitics, CitizenSocial, CitizenTasks):
|
||||
class Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
|
||||
CitizenMedia, CitizenPolitics, CitizenSocial, CitizenMilitary, CitizenTasks):
|
||||
def __init__(self, email: str = "", password: str = "", auto_login: bool = False):
|
||||
super().__init__(email, password)
|
||||
self._last_full_update = constants.min_datetime
|
||||
@ -2442,9 +2526,10 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
|
||||
|
||||
self.update_citizen_info()
|
||||
self.reporter.do_init()
|
||||
if self.config.telegram:
|
||||
if self.config.telegram_chat_id and self.config.telegram_token:
|
||||
self.telegram.do_init(self.config.telegram_chat_id, self.config.telegram_token, self.name)
|
||||
if self.config.telegram and self.config.telegram_chat_id:
|
||||
self.telegram.do_init(self.config.telegram_chat_id,
|
||||
self.config.telegram_token,
|
||||
self.name)
|
||||
self.telegram.send_message(f"*Started* {utils.now():%F %T}")
|
||||
|
||||
self.update_all(True)
|
||||
@ -2501,8 +2586,8 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
|
||||
data[(title, reward)]['count'] += count
|
||||
self._post_main_global_alerts_close(medal.get('id'))
|
||||
if data:
|
||||
msgs = ["{count} x {kind},"
|
||||
" totaling {} {currency}".format(d["count"] * d["reward"], **d) for d in data.values()]
|
||||
msgs = [f"{d['count']} x {d['kind']}, totaling {d['count'] * d['reward']} "
|
||||
f"{d['currency']}" for d in data.values()]
|
||||
|
||||
msgs = "\n".join(msgs)
|
||||
if self.config.telegram:
|
||||
@ -2562,7 +2647,8 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
|
||||
f"(Recoverable until WC end {max_count}hp | want to do {count}hits)")
|
||||
count = count if max_count > count else max_count
|
||||
|
||||
self.write_log(log_msg, False)
|
||||
if not silent:
|
||||
self.write_log(log_msg, False)
|
||||
|
||||
return count, log_msg, force_fight
|
||||
|
||||
@ -2595,13 +2681,13 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
|
||||
|
||||
def send_state_update(self):
|
||||
data = dict(xp=self.details.xp, cc=self.details.cc, gold=self.details.gold, pp=self.details.pp,
|
||||
inv_total=self.inventory_status['total'], inv=self.inventory_status['used'],
|
||||
inv_total=self.inventory.total, inv=self.inventory.used,
|
||||
hp_limit=self.energy.limit,
|
||||
hp_interval=self.energy.interval, hp_available=self.energy.available, food=self.food['total'], )
|
||||
self.reporter.send_state_update(**data)
|
||||
|
||||
def send_inventory_update(self):
|
||||
self.reporter.report_action("INVENTORY", json_val=self.get_inventory(True))
|
||||
self.reporter.report_action("INVENTORY", json_val=self.get_inventory(True).as_dict)
|
||||
|
||||
def send_my_companies_update(self):
|
||||
self.reporter.report_action('COMPANIES', json_val=self.my_companies.as_dict)
|
||||
@ -2635,7 +2721,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
|
||||
if not amount:
|
||||
inv_resp = self._get_economy_inventory_items().json()
|
||||
category = "rawMaterials" if kind.endswith("Raw") else "finalProducts"
|
||||
item = "{}_{}".format(constants.INDUSTRIES[kind], quality)
|
||||
item = f"{constants.INDUSTRIES[kind]}_{quality}"
|
||||
amount = inv_resp.get("inventoryItems").get(category).get("items").get(item).get("amount", 0)
|
||||
|
||||
if amount >= 1:
|
||||
@ -2664,7 +2750,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
|
||||
elif kind.endswith("Raw"):
|
||||
self.sell_produced_product(kind, 1)
|
||||
else:
|
||||
raise classes.ErepublikException("Unknown kind produced '{kind}'".format(kind=kind))
|
||||
raise classes.ErepublikException(f"Unknown kind produced '{kind}'")
|
||||
elif self.config.auto_buy_raw and re.search(r"not_enough_[^_]*_raw", response.get("message")):
|
||||
raw_kind = re.search(r"not_enough_(\w+)_raw", response.get("message"))
|
||||
if raw_kind:
|
||||
@ -2710,7 +2796,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
|
||||
self._report_action("WORK_AS_MANAGER", "Not enough money to work as manager!", kwargs=response)
|
||||
self.write_log("Not enough money to work as manager!")
|
||||
else:
|
||||
msg = "I was not able to wam and or employ because:\n{}".format(response)
|
||||
msg = f"I was not able to wam and or employ because:\n{response}"
|
||||
self._report_action("WORK_AS_MANAGER", f"Worked as manager failed: {msg}", kwargs=response)
|
||||
self.write_log(msg)
|
||||
|
||||
@ -2739,7 +2825,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
|
||||
|
||||
for holding in regions.values():
|
||||
raw_usage = holding.get_wam_raw_usage()
|
||||
free_storage = self.inventory_status['total'] - self.inventory_status['used']
|
||||
free_storage = self.inventory.total - self.inventory.used
|
||||
if (raw_usage['frm'] + raw_usage['wrm']) * 100 > free_storage:
|
||||
self._report_action('WAM_UNAVAILABLE', 'Not enough storage!')
|
||||
continue
|
||||
@ -2749,7 +2835,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
|
||||
|
||||
wam_count = self.my_companies.get_total_wam_count()
|
||||
if wam_count:
|
||||
self.write_log("Wam ff lockdown is now {}, was {}".format(wam_count, self.my_companies.ff_lockdown))
|
||||
self.write_log(f"Wam ff lockdown is now {wam_count}, was {self.my_companies.ff_lockdown}")
|
||||
self.my_companies.ff_lockdown = wam_count
|
||||
self.travel_to_residence()
|
||||
return bool(wam_count)
|
||||
|
@ -3,14 +3,16 @@ import hashlib
|
||||
import threading
|
||||
import weakref
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List, NamedTuple, Tuple, Union, NoReturn, Generator, Iterable
|
||||
from typing import Any, Dict, Generator, Iterable, List, NamedTuple, NoReturn, Tuple, Union
|
||||
|
||||
from requests import Response, Session, post
|
||||
|
||||
from . import utils, constants
|
||||
from . import constants, utils, types
|
||||
|
||||
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
|
||||
'Holding', 'MyCompanies', 'MyJSONEncoder', 'OfferItem', 'Politics', 'Reporter', 'TelegramBot']
|
||||
'ErepublikNetworkException', 'EnergyToFight',
|
||||
'Holding', 'MyCompanies', 'MyJSONEncoder', 'OfferItem', 'Politics', 'Reporter', 'TelegramReporter',
|
||||
'Inventory']
|
||||
|
||||
|
||||
class ErepublikException(Exception):
|
||||
@ -395,11 +397,11 @@ class Config:
|
||||
self.spin_wheel_of_fortune = False
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
def as_dict(self) -> Dict[str, Union[bool, int, str, List[str]]]:
|
||||
return dict(email=self.email, work=self.work, train=self.train, wam=self.wam, ot=self.ot,
|
||||
auto_sell=self.auto_sell, auto_sell_all=self.auto_sell_all, employees=self.employees,
|
||||
fight=self.fight, air=self.air, ground=self.ground, all_in=self.all_in,
|
||||
next_energy=self.next_energy, boosters=self.boosters, travel_to_fight=self.travel_to_fight,
|
||||
next_energy=self.next_energy, travel_to_fight=self.travel_to_fight,
|
||||
always_travel=self.always_travel, epic_hunt=self.epic_hunt, epic_hunt_ebs=self.epic_hunt_ebs,
|
||||
rw_def_side=self.rw_def_side, interactive=self.interactive, maverick=self.maverick,
|
||||
continuous_fighting=self.continuous_fighting, auto_buy_raw=self.auto_buy_raw,
|
||||
@ -419,7 +421,7 @@ class Energy:
|
||||
self._recovery_time = utils.now()
|
||||
|
||||
def __repr__(self):
|
||||
return "{:4}/{:4} + {:4}, {:3}hp/6min".format(self.recovered, self.limit, self.recoverable, self.interval)
|
||||
return f"{self.recovered:4}/{self.limit:4} + {self.recoverable:4}, {self.interval:3}hp/6min"
|
||||
|
||||
def set_reference_time(self, recovery_time: datetime.datetime):
|
||||
self._recovery_time = recovery_time.replace(microsecond=0)
|
||||
@ -453,7 +455,7 @@ class Energy:
|
||||
return self.recovered + self.recoverable
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
def as_dict(self) -> Dict[str, Union[int, datetime.datetime, bool]]:
|
||||
return dict(limit=self.limit, interval=self.interval, recoverable=self.recoverable, recovered=self.recovered,
|
||||
reference_time=self.reference_time, food_fights=self.food_fights,
|
||||
is_recoverable_full=self.is_recoverable_full, is_recovered_full=self.is_recovered_full,
|
||||
@ -508,7 +510,7 @@ class Details:
|
||||
return next_level_up - self.xp
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
def as_dict(self) -> Dict[str, Union[int, float, str, constants.Country, bool]]:
|
||||
return dict(xp=self.xp, cc=self.cc, pp=self.pp, pin=self.pin, gold=self.gold, next_pp=self.next_pp,
|
||||
citizen_id=self.citizen_id, citizenship=self.citizenship, current_region=self.current_region,
|
||||
current_country=self.current_country, residence_region=self.residence_region,
|
||||
@ -527,7 +529,7 @@ class Politics:
|
||||
is_country_president: bool = False
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
def as_dict(self) -> Dict[str, Union[bool, int, str]]:
|
||||
return dict(is_party_member=self.is_party_member, party_id=self.party_id, party_slug=self.party_slug,
|
||||
is_party_president=self.is_party_president, is_congressman=self.is_congressman,
|
||||
is_country_president=self.is_country_president)
|
||||
@ -565,7 +567,7 @@ class Reporter:
|
||||
return self.citizen.details.citizen_id
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
def as_dict(self) -> Dict[str, Union[bool, int, str, List[Dict[Any, Any]]]]:
|
||||
return dict(name=self.name, email=self.email, citizen_id=self.citizen_id, key=self.key, allowed=self.allowed,
|
||||
queue=self.__to_update)
|
||||
|
||||
@ -598,10 +600,10 @@ class Reporter:
|
||||
for unreported_data in self.__to_update:
|
||||
unreported_data.update(player_id=self.citizen_id, key=self.key)
|
||||
unreported_data = utils.json.loads(utils.json.dumps(unreported_data, cls=MyJSONEncoder))
|
||||
self._req.post("{}/bot/update".format(self.url), json=unreported_data)
|
||||
self._req.post(f"{self.url}/bot/update", json=unreported_data)
|
||||
self.__to_update.clear()
|
||||
data = utils.json.loads(utils.json.dumps(data, cls=MyJSONEncoder))
|
||||
r = self._req.post("{}/bot/update".format(self.url), json=data)
|
||||
r = self._req.post(f"{self.url}/bot/update", json=data)
|
||||
return r
|
||||
|
||||
def register_account(self):
|
||||
@ -609,8 +611,8 @@ class Reporter:
|
||||
try:
|
||||
r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id))
|
||||
if not r.json().get("status"):
|
||||
self._req.post("{}/bot/register".format(self.url), json=dict(name=self.name, email=self.email,
|
||||
player_id=self.citizen_id))
|
||||
self._req.post(f"{self.url}/bot/register", json=dict(name=self.name, email=self.email,
|
||||
player_id=self.citizen_id))
|
||||
finally:
|
||||
self.__registered = True
|
||||
self.allowed = True
|
||||
@ -729,7 +731,7 @@ class BattleSide:
|
||||
return self.country.iso
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
def as_dict(self) -> Dict[str, Union[int, constants.Country, bool, List[constants.Country]]]:
|
||||
return dict(points=self.points, country=self.country, is_defender=self.is_defender, allies=self.allies,
|
||||
deployed=self.deployed)
|
||||
|
||||
@ -895,9 +897,9 @@ class Battle:
|
||||
time_now = utils.now()
|
||||
is_started = self.start < utils.now()
|
||||
if is_started:
|
||||
time_part = " {}".format(time_now - self.start)
|
||||
time_part = f" {time_now - self.start}"
|
||||
else:
|
||||
time_part = "-{}".format(self.start - time_now)
|
||||
time_part = f"-{self.start - time_now}"
|
||||
|
||||
return (f"Battle {self.id} for {self.region_name[:16]:16} | "
|
||||
f"{self.invader} : {self.defender} | Round time {time_part} | {'R'+str(self.zone_id):>3}")
|
||||
@ -937,7 +939,7 @@ class EnergyToFight:
|
||||
return self.energy
|
||||
|
||||
|
||||
class TelegramBot:
|
||||
class TelegramReporter:
|
||||
__initialized: bool = False
|
||||
__queue: List[str]
|
||||
chat_id: int = 0
|
||||
@ -962,10 +964,12 @@ class TelegramBot:
|
||||
'last_time': self._last_time, 'next_time': self._next_time, 'queue': self.__queue,
|
||||
'initialized': self.__initialized, 'has_threads': not self._threads}
|
||||
|
||||
def do_init(self, chat_id: int, token: str, player_name: str = ""):
|
||||
def do_init(self, chat_id: int, token: str = None, player_name: str = None):
|
||||
if token is None:
|
||||
token = "864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o"
|
||||
self.chat_id = chat_id
|
||||
self.api_url = "https://api.telegram.org/bot{}/sendMessage".format(token)
|
||||
self.player_name = player_name
|
||||
self.api_url = f"https://api.telegram.org/bot{token}/sendMessage"
|
||||
self.player_name = player_name or ""
|
||||
self.__initialized = True
|
||||
self._last_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-5))
|
||||
self._last_full_energy_report = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-30))
|
||||
@ -981,7 +985,7 @@ class TelegramBot:
|
||||
self._threads = [t for t in self._threads if t.is_alive()]
|
||||
self._next_time = utils.good_timedelta(utils.now(), datetime.timedelta(seconds=20))
|
||||
if not self._threads:
|
||||
name = "telegram_{}send".format(f"{self.player_name}_" if self.player_name else "")
|
||||
name = f"telegram_{f'{self.player_name}_' if self.player_name else ''}send"
|
||||
send_thread = threading.Thread(target=self.__send_messages, name=name)
|
||||
send_thread.start()
|
||||
self._threads.append(send_thread)
|
||||
@ -1021,3 +1025,27 @@ class OfferItem(NamedTuple):
|
||||
amount: int = 0
|
||||
offer_id: int = 0
|
||||
citizen_id: int = 0
|
||||
|
||||
|
||||
class Inventory:
|
||||
final: types.InvFinal
|
||||
active: types.InvFinal
|
||||
boosters: types.InvBooster
|
||||
raw: types.InvRaw
|
||||
market: types.InvRaw
|
||||
used: int
|
||||
total: int
|
||||
|
||||
def __init__(self):
|
||||
self.active = {}
|
||||
self.final = {}
|
||||
self.boosters = {}
|
||||
self.raw = {}
|
||||
self.offers = {}
|
||||
self.used = 0
|
||||
self.total = 0
|
||||
|
||||
@property
|
||||
def as_dict(self) -> Dict[str, Union[types.InvFinal, types.InvRaw, int]]:
|
||||
return dict(active=self.active, final=self.final, boosters=self.boosters, raw=self.raw, offers=self.offers,
|
||||
total=self.total, used=self.used)
|
||||
|
7
erepublik/types.py
Normal file
7
erepublik/types.py
Normal file
@ -0,0 +1,7 @@
|
||||
from datetime import datetime
|
||||
from typing import Dict, Union, List
|
||||
|
||||
InvFinalItem = Dict[str, Union[str, int, List[Dict[str, Union[int, datetime]]]]]
|
||||
InvBooster = Dict[str, Dict[int, Dict[int, InvFinalItem]]]
|
||||
InvFinal = Dict[str, Dict[int, InvFinalItem]]
|
||||
InvRaw = Dict[str, Dict[int, Dict[str, Union[str, int]]]]
|
@ -10,7 +10,7 @@ import unicodedata
|
||||
import warnings
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Optional, Union, Dict
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
import requests
|
||||
|
||||
@ -21,10 +21,10 @@ try:
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
__all__ = ['VERSION', 'calculate_hit', 'caught_error', 'date_from_eday', 'eday_from_date',
|
||||
__all__ = ['VERSION', 'calculate_hit', 'caught_error', 'date_from_eday', 'eday_from_date', 'deprecation',
|
||||
'get_air_hit_dmg_value', 'get_file', 'get_ground_hit_dmg_value', 'get_sleep_seconds', 'good_timedelta',
|
||||
'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now',
|
||||
'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file',
|
||||
'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file', 'write_request',
|
||||
'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock']
|
||||
|
||||
if not sys.version_info >= (3, 6):
|
||||
@ -64,7 +64,9 @@ def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.da
|
||||
return constants.erep_tz.normalize(dt + td)
|
||||
|
||||
|
||||
def eday_from_date(date: Union[datetime.date, datetime.datetime] = now()) -> int:
|
||||
def eday_from_date(date: Union[datetime.date, datetime.datetime] = None) -> int:
|
||||
if date is None:
|
||||
date = now()
|
||||
if isinstance(date, datetime.date):
|
||||
date = datetime.datetime.combine(date, datetime.time(0, 0, 0))
|
||||
return (date - datetime.datetime(2007, 11, 20, 0, 0, 0)).days
|
||||
@ -93,7 +95,7 @@ def interactive_sleep(sleep_seconds: int):
|
||||
# seconds = seconds % 30 if seconds % 30 else 30
|
||||
else:
|
||||
seconds = 1
|
||||
sys.stdout.write("\rSleeping for {:4} more seconds".format(sleep_seconds))
|
||||
sys.stdout.write(f"\rSleeping for {sleep_seconds:4} more seconds")
|
||||
sys.stdout.flush()
|
||||
time.sleep(seconds)
|
||||
sleep_seconds -= seconds
|
||||
@ -105,7 +107,7 @@ silent_sleep = time.sleep
|
||||
|
||||
def _write_log(msg, timestamp: bool = True, should_print: bool = False):
|
||||
erep_time_now = now()
|
||||
txt = "[{}] {}".format(erep_time_now.strftime('%F %T'), msg) if timestamp else msg
|
||||
txt = f"[{erep_time_now.strftime('%F %T')}] {msg}" if timestamp else msg
|
||||
txt = "\n".join(["\n".join(textwrap.wrap(line, 120)) for line in txt.splitlines()])
|
||||
if not os.path.isdir('log'):
|
||||
os.mkdir('log')
|
||||
@ -158,6 +160,7 @@ def write_file(filename: str, content: str) -> int:
|
||||
|
||||
def write_request(response: requests.Response, is_error: bool = False):
|
||||
from erepublik import Citizen
|
||||
|
||||
# Remove GET args from url name
|
||||
url = response.url
|
||||
last_index = url.index("?") if "?" in url else len(response.url)
|
||||
@ -172,10 +175,10 @@ def write_request(response: requests.Response, is_error: bool = False):
|
||||
ext = "html"
|
||||
|
||||
if not is_error:
|
||||
filename = "debug/requests/{}_{}.{}".format(now().strftime('%F_%H-%M-%S'), name, ext)
|
||||
filename = f"debug/requests/{now().strftime('%F_%H-%M-%S')}_{name}.{ext}"
|
||||
write_file(filename, html)
|
||||
else:
|
||||
return {"name": "{}_{}.{}".format(now().strftime('%F_%H-%M-%S'), name, ext),
|
||||
return {"name": f"{now().strftime('%F_%H-%M-%S')}_{name}.{ext}",
|
||||
"content": html.encode('utf-8'),
|
||||
"mimetype": "application/json" if ext == "json" else "text/html"}
|
||||
|
||||
@ -196,14 +199,14 @@ def send_email(name: str, content: List[Any], player=None, local_vars: Dict[str,
|
||||
if promo:
|
||||
resp = {"name": "%s.html" % name, "mimetype": "text/html",
|
||||
"content": file_content_template.format(title="Promo", body="<br/>".join(content))}
|
||||
subject = "[eBot][{}] Promos: {}".format(now().strftime('%F %T'), name)
|
||||
subject = f"[eBot][{now().strftime('%F %T')}] Promos: {name}"
|
||||
|
||||
elif captcha:
|
||||
resp = {"name": "%s.html" % name, "mimetype": "text/html",
|
||||
"content": file_content_template.format(title="ReCaptcha", body="<br/>".join(content))}
|
||||
subject = "[eBot][{}] RECAPTCHA: {}".format(now().strftime('%F %T'), name)
|
||||
subject = f"[eBot][{now().strftime('%F %T')}] RECAPTCHA: {name}"
|
||||
else:
|
||||
subject = "[eBot][%s] Bug trace: %s" % (now().strftime('%F %T'), name)
|
||||
subject = f"[eBot][{now().strftime('%F %T')}] Bug trace: {name}"
|
||||
|
||||
body = "".join(traceback.format_stack()) + \
|
||||
"\n\n" + \
|
||||
@ -252,7 +255,7 @@ def caught_error(e: Exception):
|
||||
|
||||
|
||||
def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None,
|
||||
interactive: Optional[bool] = None):
|
||||
interactive: bool = None):
|
||||
"""
|
||||
Process error logging and email sending to developer
|
||||
:param interactive: Should print interactively
|
||||
@ -376,6 +379,7 @@ def get_final_hit_dmg(base_dmg: Union[Decimal, float, str], rang: int,
|
||||
dmg = dmg * 11 / 10
|
||||
return Decimal(dmg)
|
||||
|
||||
|
||||
# def _clear_up_battle_memory(battle):
|
||||
# del battle.invader._battle, battle.defender._battle
|
||||
# for div_id, division in battle.div.items():
|
||||
@ -396,7 +400,12 @@ def wait_for_lock(function):
|
||||
return None
|
||||
else:
|
||||
instance.concurrency_available.clear()
|
||||
ret = function(instance, *args, **kwargs)
|
||||
try:
|
||||
ret = function(instance, *args, **kwargs)
|
||||
except Exception as e:
|
||||
instance.concurrency_available.set()
|
||||
raise e
|
||||
instance.concurrency_available.set()
|
||||
return ret
|
||||
|
||||
return wrapper
|
||||
|
@ -97,7 +97,7 @@ def main():
|
||||
player.set_debug(CONFIG.get('debug', False))
|
||||
player.login()
|
||||
if CONFIG.get('battle_launcher'):
|
||||
name = "{}-battle_launcher-{}".format(player.name, threading.active_count() - 1)
|
||||
name = f"{player.name}-battle_launcher-{threading.active_count() - 1}"
|
||||
state_thread = threading.Thread(target=_battle_launcher, args=(player,), name=name)
|
||||
state_thread.start()
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from erepublik import Citizen, utils, constants
|
||||
from erepublik import Citizen, constants, utils
|
||||
|
||||
CONFIG = {
|
||||
'email': 'player@email.com',
|
||||
@ -94,15 +94,15 @@ def main():
|
||||
closest_next_time = dt_max
|
||||
next_tasks = []
|
||||
for task, next_time in sorted(tasks.items(), key=lambda s: s[1]):
|
||||
next_tasks.append("{}: {}".format(next_time.strftime('%F %T'), task))
|
||||
next_tasks.append(f"{next_time.strftime('%F %T')}: {task}")
|
||||
if next_time < closest_next_time:
|
||||
closest_next_time = next_time
|
||||
sleep_seconds = int(utils.get_sleep_seconds(closest_next_time))
|
||||
if sleep_seconds <= 0:
|
||||
player.write_log(f"Loop detected! Offending task: '{next_tasks[0]}'")
|
||||
player.write_log("My next Tasks and there time:\n" + "\n".join(sorted(next_tasks)))
|
||||
player.write_log("Sleeping until (eRep): {} (sleeping for {}s)".format(
|
||||
closest_next_time.strftime("%F %T"), sleep_seconds))
|
||||
player.write_log(f"Sleeping until (eRep): {closest_next_time.strftime('%F %T')}"
|
||||
f" (sleeping for {sleep_seconds}s)")
|
||||
seconds_to_sleep = sleep_seconds if sleep_seconds > 0 else 0
|
||||
player.sleep(seconds_to_sleep)
|
||||
except Exception as e:
|
||||
|
@ -1,19 +1,18 @@
|
||||
bump2version==1.0.1
|
||||
coverage==5.3
|
||||
edx-sphinx-theme==1.5.0
|
||||
coverage==5.3.1
|
||||
edx-sphinx-theme==1.6.0
|
||||
flake8==3.8.4
|
||||
ipython>=7.19.0
|
||||
isort==5.6.4
|
||||
pip==20.3
|
||||
isort==5.7.0
|
||||
pip==20.3.3
|
||||
PyInstaller==4.1
|
||||
pytz==2020.4
|
||||
pytest==6.1.2
|
||||
pytz>=2020.0
|
||||
pytest==6.2.1
|
||||
responses==0.12.1
|
||||
setuptools==50.3.2
|
||||
Sphinx==3.3.1
|
||||
requests==2.25.0
|
||||
setuptools==51.1.1
|
||||
Sphinx==3.4.2
|
||||
requests>=2.24.0,<2.26.0
|
||||
PySocks==1.7.1
|
||||
tox==3.20.1
|
||||
twine==3.2.0
|
||||
watchdog==0.10.4
|
||||
wheel==0.35.1
|
||||
twine==3.3.0
|
||||
wheel==0.36.2
|
||||
pur==5.3.0
|
||||
|
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.23.2.8
|
||||
current_version = 0.23.4.1
|
||||
commit = True
|
||||
tag = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
|
||||
@ -18,13 +18,13 @@ replace = __version__ = '{new_version}'
|
||||
universal = 1
|
||||
|
||||
[flake8]
|
||||
exclude = docs,.tox,.git,log,debug,venv
|
||||
exclude = docs,.git,log,debug,venv
|
||||
max-line-length = 120
|
||||
ignore = D100,D101,D102,D103
|
||||
|
||||
[pycodestyle]
|
||||
max-line-length = 120
|
||||
exclude = .tox,.git,log,debug,venv, build
|
||||
exclude = .git,log,debug,venv, build
|
||||
|
||||
[mypy]
|
||||
python_version = 3.7
|
||||
|
8
setup.py
8
setup.py
@ -12,8 +12,8 @@ with open('HISTORY.rst') as history_file:
|
||||
history = history_file.read()
|
||||
|
||||
requirements = [
|
||||
'pytz==2020.4',
|
||||
'requests==2.25.0',
|
||||
'pytz>=2020.0',
|
||||
'requests>=2.24.0,<2.26.0',
|
||||
'PySocks==1.7.1'
|
||||
]
|
||||
|
||||
@ -45,11 +45,11 @@ setup(
|
||||
keywords='erepublik',
|
||||
name='eRepublik',
|
||||
packages=find_packages(include=['erepublik']),
|
||||
python_requires='>=3.6, <4',
|
||||
python_requires='>=3.7, <4',
|
||||
setup_requires=setup_requirements,
|
||||
test_suite='tests',
|
||||
tests_require=test_requirements,
|
||||
url='https://github.com/eeriks/erepublik/',
|
||||
version='0.23.2.8',
|
||||
version='0.23.4.1',
|
||||
zip_safe=False,
|
||||
)
|
||||
|
Reference in New Issue
Block a user