Compare commits

...

156 Commits

Author SHA1 Message Date
3fba1d6b3d Bump version: 0.23.4.10 → 0.23.4.11 2021-01-18 16:35:35 +02:00
88c8d5a9a0 Unified all event setting 2021-01-18 16:35:31 +02:00
382749a8d8 Bump version: 0.23.4.9 → 0.23.4.10 2021-01-18 16:12:31 +02:00
0c877e315b as_dict updates 2021-01-18 16:12:26 +02:00
c95f642fee Citizen.as_dict rewrite 2021-01-18 16:00:29 +02:00
da0276f9a6 Removed unused CitizenMilitary.boosters property
Explicitly updated all Citizen*.as_dict properties
2021-01-18 15:22:53 +02:00
f89f91e969 Concurrency checks placed directly in Citizen class
Added concurrency checks also for citizen updates

Sentry: EREPUBLIK-BOT-77
2021-01-18 15:11:40 +02:00
56c2ca1b6e House renewal price check bugfix 2021-01-12 08:05:57 +02:00
3cee2d1f0f Bump version: 0.23.4.8 → 0.23.4.9 2021-01-11 21:05:22 +02:00
0ed03877ce Damage booster's name change 2021-01-11 21:05:12 +02:00
491c9f5efe CitizenMilitary.countries were never used - all countries are available at constants.COUNTIRES, but allies and deployed allies are also available on every Battle 2021-01-11 16:27:23 +02:00
8445b556e7 Bump version: 0.23.4.7 → 0.23.4.8 2021-01-11 16:21:14 +02:00
d02c5c2969 dev requirement update 2021-01-11 16:20:58 +02:00
bff4183cb6 Added option to fight with energy bars, count small energy bars more precisely 2021-01-11 16:19:10 +02:00
47f5142837 Bump version: 0.23.4.6 → 0.23.4.7 2021-01-08 11:09:39 +02:00
a32fd039dd Report details about fighting also on telegram 2021-01-08 11:08:50 +02:00
311e684c0c Add telegram fight reporting API, Fix message duplication on TelegramReporter initialization 2021-01-08 11:07:59 +02:00
52038a86d5 PackBooster icon update 2021-01-07 17:00:59 +02:00
c35a107641 Bump version: 0.23.4.5 → 0.23.4.6 2021-01-07 15:55:02 +02:00
b53b2f0fae Update loop 2021-01-07 15:54:56 +02:00
8da9b6221a Bump version: 0.23.4.4 → 0.23.4.5 2021-01-07 15:36:33 +02:00
caa41c85f6 minor tweaks 2021-01-07 15:36:27 +02:00
da3b5d5768 Update inventory on booster activation 2021-01-07 15:31:46 +02:00
f96d0233f9 CSRF bugfix 2021-01-07 15:31:10 +02:00
c693a5182e Merge branch 'master' of github.com:eeriks/erepublik 2021-01-07 15:14:17 +02:00
0dfba6a9ff Find battle and fight - reuse already calculated hit count 2021-01-07 15:14:11 +02:00
ee3eca9658 Bump version: 0.23.4.3 → 0.23.4.4 2021-01-06 23:37:05 +02:00
0e8680daca bugfix 2021-01-06 23:35:25 +02:00
8e3606f1a3 Doc update 2021-01-05 19:47:43 +02:00
bd0bcc9ac7 Bump version: 0.23.4.2 → 0.23.4.3 2021-01-05 19:35:00 +02:00
c6f2226e64 . 2021-01-05 19:34:43 +02:00
308807d800 isort, pre-commit 2021-01-05 19:29:20 +02:00
91565d840e Added endpoint for collect all WC rewards 2021-01-05 19:18:20 +02:00
b4a9dd88f8 config generator bugdix 2021-01-05 19:17:50 +02:00
8435aa23ba Bump version: 0.23.4.1 → 0.23.4.2 2021-01-05 16:30:49 +02:00
09cd275a69 type bugfix 2021-01-05 16:30:42 +02:00
e23a67231e Bump version: 0.23.4 → 0.23.4.1 2021-01-05 16:19:43 +02:00
6a03d99abf type bugfix 2021-01-05 16:19:27 +02:00
2e344749a6 Bump version: 0.23.3.4 → 0.23.4 2021-01-05 15:50:55 +02:00
5aecefbd9d Protect those precious air boosters and 100% ground boosters 2021-01-05 15:50:38 +02:00
8b9ee5042d Bugfixes and inventory redone 2021-01-05 15:41:51 +02:00
1e93006c3d History updates 2020-12-17 18:18:46 +02:00
b13bfcdbf3 Bump version: 0.23.3.3 → 0.23.3.4 2020-12-17 18:09:43 +02:00
771dbdf826 With invalid bomb (bomb not in inventory) quit after first try 2020-12-17 18:03:04 +02:00
9646d112d2 If exception occures - set concurrency as available 2020-12-17 18:02:14 +02:00
a09c37a065 Bump version: 0.23.3.2 → 0.23.3.3 2020-12-17 15:23:39 +02:00
ba75e961fa Updated config generator 2020-12-17 15:20:21 +02:00
3b5780dbd6 Updated config generator 2020-12-17 15:18:22 +02:00
fccd0134b5 Merge branch 'remove-tox' 2020-12-17 14:07:46 +02:00
b9010fa856 Fixed MRO error 2020-12-17 14:07:29 +02:00
3e5410289e . 2020-12-17 14:03:45 +02:00
661a019b0a Broken MRO 2020-12-17 14:03:30 +02:00
23d682959d Party presidency and congress election bugfix if player is not part of a party 2020-12-17 12:13:44 +02:00
5806ccb6ca Buy food if unable to work/train because of food shortage 2020-12-14 13:51:16 +02:00
a9bc78b701 Sleep accepts floats and decimals, not only integers. Bomb deploy should use Citizen.sleep instead of utils.sleep 2020-12-11 17:22:45 +02:00
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
b88e7973e8 Bump version: 0.23.1 → 0.23.1.1 2020-11-30 18:54:44 +02:00
c671425531 Base dmg calculations 2020-11-30 18:54:35 +02:00
08937d06e4 Bump version: 0.23.0 → 0.23.1 2020-11-30 18:20:48 +02:00
cec4510831 Requirement update 2020-11-30 18:20:42 +02:00
cfb9501647 PEP8 2020-11-30 18:16:13 +02:00
d419211955 Get max hit value for divisions on current side 2020-11-30 18:12:36 +02:00
0ea144db17 Added method to get division stats 2020-11-30 17:44:23 +02:00
1418f580cd Spin wheel of fortune updates 2020-11-30 17:43:42 +02:00
49575bddf5 History update for v0.23.0 2020-11-26 18:39:25 +02:00
c874247335 Bump version: 0.22.3.3 → 0.23.0 2020-11-26 18:32:46 +02:00
6e9def4394 Added .get_article(int) and .delete_article(int) methods to CitizenMedia class 2020-11-26 18:32:31 +02:00
d6fbaa7945 Maverick fighting should be explicitly enabled through Citizen.config 2020-11-26 16:16:15 +02:00
eb740c60c7 Bump version: 0.22.3.2 → 0.22.3.3 2020-11-20 18:54:20 +02:00
240c409739 Travel to region bugfix 2020-11-20 18:54:12 +02:00
f348a315c5 Bump version: 0.22.3.1 → 0.22.3.2 2020-11-20 16:57:43 +02:00
7a09eea2b4 CitizenAnniversary.collect_map_quest_node() now accepts argument extra, to collect extra reward 2020-11-20 16:57:36 +02:00
81b1069cf0 Bump version: 0.22.3 → 0.22.3.1 2020-11-20 14:46:04 +02:00
429d43df15 CitizenTasks.work() bugfix: When employer out of money - resign 2020-11-20 14:45:58 +02:00
c81986d65e CitizenEconomy.post_market_offer() bugfix 2020-11-20 14:31:16 +02:00
7bb988f716 PEP8 2020-11-20 14:24:11 +02:00
16cae24712 Bump version: 0.22.2.1 → 0.22.3 2020-11-16 12:12:30 +02:00
3e051fe906 History update 2020-11-16 12:07:20 +02:00
aa9cda9314 eRepublik temp technical difficulties 2020-11-16 12:06:59 +02:00
fc66db8cab Config file generator update 2020-11-16 11:56:37 +02:00
6bbc4f8768 eRepublik has both airplaneRaw and aircraftRaw. Possible future bugs because of this! 2020-11-16 11:56:13 +02:00
160b32a914 Buy market offer directly 2020-11-16 11:52:55 +02:00
a51c3c620e Python round to even bugfix.
`{"stock":10.494533,"consume":10.5}` rounds to 0
2020-11-16 11:52:27 +02:00
a1c26468eb UA updates 2020-11-12 11:47:20 +02:00
4895ae3663 Bump version: 0.22.2 → 0.22.2.1 2020-11-10 15:39:15 +02:00
b8d7cc8d7c Example updates 2020-11-10 15:38:30 +02:00
1d0645b490 Bump version: 0.22.1.5 → 0.22.2 2020-11-09 16:34:52 +02:00
30cf6203b7 History update 2020-11-09 16:34:41 +02:00
a32e88218d CitizenEconomy.get_market_offers now accepts searching tickets and the new aircraft qualities 2020-11-09 16:28:21 +02:00
a031da0ee7 Bump version: 0.22.1.4 → 0.22.1.5 2020-11-04 17:22:44 +02:00
bdb13fa4ae History update, WAM actions are now more verbose 2020-11-04 17:22:35 +02:00
e1e3b33d46 requirement update 2020-11-03 18:10:43 +02:00
e09ca143b1 Bump version: 0.22.1.3 → 0.22.1.4 2020-11-03 14:31:43 +02:00
61d0599295 House renewal bugfix 2020-11-03 14:31:37 +02:00
1ef600492a House renewal bugfix 2020-11-03 14:30:20 +02:00
377eda6445 Inventory booster bugfix 2020-11-03 14:29:42 +02:00
fd13667ca8 Bump version: 0.22.1.2 → 0.22.1.3 2020-10-30 12:58:26 +02:00
0479afcabe Report if out of money to work as manager 2020-10-30 12:58:18 +02:00
486f022f35 Unified Item namig from constants.INDUSTRIES 2020-10-30 12:43:45 +02:00
638373e452 Bump version: 0.22.1.1 → 0.22.1.2 2020-10-27 17:04:40 +02:00
04f357cc70 Bugfix 2020-10-27 17:04:37 +02:00
ed4ffe5af6 Bump version: 0.22.1 → 0.22.1.1 2020-10-22 18:04:13 +03:00
8aa90a7dbf Lint 2020-10-22 18:04:06 +03:00
e798859105 Bump version: 0.22.0 → 0.22.1 2020-10-22 16:27:09 +03:00
241f1642ce Session dump/load, Citizen.inventory update, memory and network optimization, python3.6 support 2020-10-22 16:26:59 +03:00
a4128b5d89 Bump version: 0.21.5.8 → 0.22.0 2020-10-21 14:46:24 +03:00
22c2a0ffd2 New Features!
Dump session to file!
Load session from file!
2020-10-21 14:45:29 +03:00
38f0335354 Bump version: 0.21.5.7 → 0.21.5.8 2020-10-07 09:22:01 +03:00
889435b94e House renewal bugfix 2020-10-07 09:21:36 +03:00
bb16c27674 Bump version: 0.21.5.6 → 0.21.5.7 2020-10-06 12:35:19 +03:00
963d7ca11a bugfix 2020-10-06 12:35:14 +03:00
26 changed files with 1557 additions and 710 deletions

3
.gitignore vendored
View File

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

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

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

View File

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

View File

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

View File

@ -2,6 +2,76 @@
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
* 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 - 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
* Fixed `CitizenTravel.travel_to_region(region_id:int)` method
* Added `CitizenAnniversary.collect_map_quest_node(node_id:int, extra:bool=False)` to collect also extra rewards
* Fixed `CitizenTasks.work()` when employer out of money - resign and find a new job
* Fixed `CitizenEconomy.post_market_offer()`
0.22.3 (2020-11-16)
-------------------
* Fixed round to even bug when doing wam and not enough raw.
* Added meta industry airplaneRaw
* Added method `Citizen.buy_market_offer(OfferItem, amount=None)` to directly buy market offer with included travel to country and back.
0.22.2 (2020-11-09)
-------------------
* Allow querying market offers for q2-q5 aircrafts
* Added "Ticket" industry
0.22.1 (2020-11-04)
-------------------
* Requirement update
* Unified product naming in inventory and other places based on `erepublik.constants.INDUSTRIES` values
* `erepublik.Citizen` parameter `auto_login` now defaults to `False`
* Continued work on more verbose action and result logging
0.22.0 (2020-10-22)
-------------------
* Ability to dump session and restore from file
* Proxy support
* Inventory updates
* Remove market offers
* Memory and network optimizations
* Python 3.6 supported
0.20.0 (2020-06-15)
-------------------
* Massive restructuring

View File

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

View File

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

View File

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

61
docs/erepublik.rst Normal file
View File

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

View File

@ -8,15 +8,12 @@
<meta name="generator" content="Jekyll v4.0.1">
<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">
@ -93,8 +67,8 @@
<label class="custom-control-label" for="wam">Work as manager</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="employ">
<label class="custom-control-label" for="employ">Employ employees</label>
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="employees">
<label class="custom-control-label" for="employees">Employ employees</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_buy_raw" checked>
@ -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
@ -236,8 +304,8 @@
config.debug = debug.checked;
let wam = document.getElementById('wam'); // Generated
config.wam = wam.checked;
let employ = document.getElementById('employ'); // Generated
config.employ = employ.checked;
let employees = document.getElementById('employees'); // Generated
config.employees = employees.checked;
let auto_buy_raw = document.getElementById('auto_buy_raw'); // Generated
let auto_sell_all = document.getElementById('auto_sell_all'); // Generated
@ -249,7 +317,7 @@
let auto_sell_house = document.getElementById('auto_sell_house'); // Generated
let auto_sell_arm = document.getElementById('auto_sell_arm'); // Generated
let auto_sell_air = document.getElementById('auto_sell_air'); // Generated
if (config.wam || config.employ) {
if (config.wam || config.employees) {
auto_buy_raw.disabled = false;
auto_sell_all.disabled = false;
auto_sell_frm.disabled = false;
@ -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;
@ -325,7 +393,7 @@
}
config.air = air.checked;
config.ground = ground.cehcked;
config.ground = ground.checked;
config.boosters = boosters.checked;
config.continuous_fighting = continuous_fighting.checked;
config.next_energy = next_energy.checked;
@ -334,6 +402,59 @@
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;
config.maverick = false;
// Advanced
let telegram = document.getElementById('telegram'); // Generated
config.telegram = telegram.checked;
let telegram_chat_id = document.getElementById('telegram_chat_id'); // Generated
let telegram_token = document.getElementById('telegram_token'); // Generated
if (config.telegram) {
telegram_chat_id.disabled = false;
telegram_token.disabled = false;
} else {
disable(telegram_chat_id);
disable(telegram_token);
}
config.telegram_chat_id = telegram_chat_id.value;
config.telegram_token = telegram_token.value;
let _proxy = {};
let proxy = document.getElementById('proxy'); // Generated
let proxy_kind = document.getElementById('proxy_kind'); // Generated
let proxy_host = document.getElementById('proxy_host'); // Generated
let proxy_port = document.getElementById('proxy_port'); // Generated
let proxy_user = document.getElementById('proxy_user'); // Generated
if (proxy.checked) {
proxy_kind.disabled = false;
proxy_host.disabled = false;
proxy_port.disabled = false;
proxy_user.disabled = false;
} else {
disable(proxy_kind);
disable(proxy_host);
disable(proxy_port);
disable(proxy_user);
}
_proxy.kind = proxy_kind.value;
_proxy.host = proxy_host.value;
_proxy.port = proxy_port.value;
_proxy.username = proxy_user.value;
_proxy.password = ""
if (proxy.checked) {
delete config._proxy;
config.proxy = _proxy;
} else {
delete config.proxy;
config._proxy = {
'kind': 'socks or http',
'host': 'localhost',
'port': 8080,
'username': 'optional',
'password': 'optional'
}
}
let pre = document.getElementById('json-output');
pre.textContent = JSON.stringify(config, null, 2);
}
@ -359,7 +480,7 @@
"interactive": true,
"debug": true,
"wam": true,
"employ": true,
"employees": true,
"auto_buy_raw": true,
"auto_sell_all": true,
"auto_sell": [

7
docs/modules.rst Normal file
View File

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

View File

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

View File

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

View File

@ -15,9 +15,11 @@ 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/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',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36',
@ -27,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: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: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: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: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
@ -83,11 +81,10 @@ 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)
utils.get_file(self.request_log_name)
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
def _log_response(self, url, resp, redirect: bool = False):
from erepublik import Citizen
@ -97,22 +94,20 @@ 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)
with open(utils.get_file(filename), 'wb') as f:
f.write(resp.text.encode('utf-8'))
filename = f'{fd_path}/{fd_time}_{fd_name}{fd_extra}.{fd_ext}'
utils.write_file(filename, resp.text)
pass
class CitizenBaseAPI:
@ -125,6 +120,10 @@ class CitizenBaseAPI:
self._req = SlowRequests()
self.token = ""
@property
def as_dict(self):
return dict(url=self.url, request=self._req.as_dict, token=self.token)
def post(self, url: str, data=None, json=None, **kwargs) -> Response:
return self._req.post(url, data, json, **kwargs)
@ -159,8 +158,10 @@ class ErepublikAnniversaryAPI(CitizenBaseAPI):
data = {'nodeId': node_id, '_token': self.token, "currencyCost": currency_amount}
return self.post(f"{self.url}/main/map-rewards-speedup", data=data)
def _post_map_rewards_claim(self, node_id: int) -> Response:
def _post_map_rewards_claim(self, node_id: int, extra: bool = False) -> Response:
data = {'nodeId': node_id, '_token': self.token}
if extra:
data['claimExtra'] = 1
return self.post(f"{self.url}/main/map-rewards-claim", data=data)
def _post_main_wheel_of_fortune_spin(self, cost) -> Response:
@ -174,6 +175,9 @@ class ErepublikArticleAPI(CitizenBaseAPI):
def _get_main_article_json(self, article_id: int) -> Response:
return self.get(f"{self.url}/main/articleJson/{article_id}")
def _get_main_delete_article(self, article_id: int) -> Response:
return self.get(f"{self.url}/main/delete-article/{article_id}/1")
def _post_main_article_comments(self, article_id: int, page: int = 1) -> Response:
data = dict(_token=self.token, articleId=article_id, page=page)
if page:
@ -285,7 +289,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"})
@ -306,11 +309,11 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
return self.post(f"{self.url}/main/buyGoldItems", data=data)
def _post_economy_activate_booster(self, quality: int, duration: int, kind: str) -> Response:
data = dict(type=kind, quality=quality, duration=duration, fromInventory=True)
data = dict(type=kind, quality=quality, duration=duration, fromInventory=True, _token=self.token)
return self.post(f"{self.url}/economy/activateBooster", data=data)
def _post_economy_activate_house(self, quality: int) -> Response:
data = {"action": "activate", "quality": quality, "type": "house", "_token": self.token}
data = dict(action="activate", quality=quality, type="house", _token=self.token)
return self.post(f"{self.url}/economy/activateHouse", data=data)
def _post_economy_donate_items_action(self, citizen_id: int, amount: int, industry: int,
@ -344,27 +347,36 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
orderBy="price_asc" if order_asc else "price_desc", _token=self.token)
return self.post(f"{self.url}/economy/marketplaceAjax", data=data)
def _post_economy_marketplace_actions(self, amount: int, buy: bool = False, **kwargs) -> Response:
if buy:
data = dict(_token=self.token, offerId=kwargs['offer'], amount=amount, orderBy="price_asc", currentPage=1,
buyAction=1)
else:
def _post_economy_marketplace_actions(self, action: str, **kwargs) -> Response:
if action == 'buy':
data = dict(_token=self.token, offerId=kwargs['offer'], amount=kwargs['amount'],
orderBy="price_asc", currentPage=1, buyAction=1)
elif action == 'sell':
data = dict(_token=self.token, countryId=kwargs["country_id"], price=kwargs["price"],
industryId=kwargs["industry"], quality=kwargs["quality"], amount=amount, sellAction='postOffer')
industryId=kwargs["industry"], quality=kwargs["quality"], amount=kwargs['amount'],
sellAction='postOffer')
elif action == 'delete':
data = dict(_token=self.token, offerId=kwargs["offer_id"], sellAction='deleteOffer')
else:
raise ValueError(f"Action '{action}' is not supported! Only 'buy/sell/delete' actions are available")
return self.post(f"{self.url}/economy/marketplaceActions", data=data)
class ErepublikLeaderBoardAPI(CitizenBaseAPI):
def _get_main_leaderboards_damage_aircraft_rankings(self, country_id: int, weeks: int = 0, mu_id: int = 0) -> Response:
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:
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:
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:
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}")
@ -376,6 +388,9 @@ class ErepublikLocationAPI(CitizenBaseAPI):
class ErepublikMilitaryAPI(CitizenBaseAPI):
def _get_military_battle_stats(self, battle_id: int, division: int, division_id: int):
return self.get(f"{self.url}/military/battle-stats/{battle_id}/{division}/{division_id}")
def _get_military_battlefield_choose_side(self, battle_id: int, side_id: int) -> Response:
return self.get(f"{self.url}/military/battlefield-choose-side/{battle_id}/{side_id}")
@ -427,8 +442,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:
@ -472,7 +488,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,
@ -583,6 +599,10 @@ class ErepublikProfileAPI(CitizenBaseAPI):
data = dict(_token=self.token, rewardId=reward_id)
return self.post(f"{self.url}/main/weekly-challenge-collect-reward", data=data)
def _post_main_weekly_challenge_collect_all(self, max_reward_id: int) -> Response:
data = dict(_token=self.token, maxRewardId=max_reward_id)
return self.post(f"{self.url}/main/weekly-challenge-collect-all", data=data)
def _post_main_profile_update(self, action: str, params: str):
data = {"action": action, "params": params, "_token": self.token}
return self.post(f"{self.url}/main/profile-update", data=data)

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,16 @@ import hashlib
import threading
import weakref
from decimal import Decimal
from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union, NoReturn
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, types, utils
__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):
@ -39,8 +41,9 @@ class Holding:
if name:
self.name = name
else:
name = f"Holding (#{self.id}) with {len(self.companies)} "
if len(self.companies) == 1:
comp_sum = len(self.companies)
name = f"Holding (#{self.id}) with {comp_sum} "
if comp_sum == 1:
name += "company"
else:
name += "companies"
@ -48,14 +51,14 @@ class Holding:
@property
def wam_count(self) -> int:
return sum([company.wam_enabled and not company.already_worked for company in self.companies])
return len([1 for company in self.companies if company.wam_enabled and not company.already_worked])
@property
def wam_companies(self) -> List["Company"]:
def wam_companies(self) -> Iterable["Company"]:
return [company for company in self.companies if company.wam_enabled]
@property
def employable_companies(self) -> List["Company"]:
def employable_companies(self) -> Iterable["Company"]:
return [company for company in self.companies if company.preset_works]
def add_company(self, company: "Company") -> NoReturn:
@ -72,7 +75,7 @@ class Holding:
wrm += company.raw_usage
return dict(frm=frm, wrm=wrm)
def get_wam_companies(self, raw_factory: bool = None) -> Optional[List["Company"]]:
def get_wam_companies(self, raw_factory: bool = None) -> List["Company"]:
raw = []
factory = []
for company in self.wam_companies:
@ -81,18 +84,15 @@ class Holding:
raw.append(company)
else:
factory.append(company)
if raw_factory is not None and not raw_factory:
return factory
elif raw_factory is not None and raw_factory:
return raw
elif raw_factory is None:
if raw_factory is None:
return raw + factory
else:
raise ErepublikException("raw_factory should be True/False/None")
return raw if raw_factory else factory
def __str__(self) -> str:
name = f"Holding (#{self.id}) with {len(self.companies)} "
if len(self.companies) % 10 == 1:
comp = len(self.companies)
name = f"Holding (#{self.id}) with {comp} "
if comp == 1:
name += "company"
else:
name += "companies"
@ -181,7 +181,7 @@ class Company:
@property
def _sort_keys(self):
return not self.is_raw, self._internal_industry, -self.quality, self.id
return not self.is_raw, self._internal_industry, self.quality, self.id
def __hash__(self):
return hash(self._sort_keys)
@ -240,13 +240,14 @@ class MyCompanies:
ff_lockdown: int = 0
holdings: Dict[int, Holding]
companies: weakref.WeakSet
_companies: weakref.WeakSet
_citizen: weakref.ReferenceType
companies: Generator[Company, None, None]
def __init__(self, citizen):
self._citizen = weakref.ref(citizen)
self.holdings: Dict[int, Holding] = dict()
self.companies: weakref.WeakSet = weakref.WeakSet()
self.holdings = dict()
self._companies = weakref.WeakSet()
self.next_ot_time = utils.now()
def prepare_holdings(self, holdings: Dict[str, Dict[str, Any]]):
@ -281,7 +282,7 @@ class MyCompanies:
company_dict.get('can_work_as_manager'), company_dict.get('cannot_work_as_manager_reason'),
company_dict.get('industry_id'), company_dict.get('already_worked'), company_dict.get('preset_works')
)
self.companies.add(company)
self._companies.add(company)
holding.add_company(company)
def get_employable_factories(self) -> Dict[int, int]:
@ -291,14 +292,18 @@ class MyCompanies:
return sum([holding.wam_count for holding in self.holdings.values()])
@staticmethod
def get_needed_inventory_usage(companies: Union[Company, List[Company]]) -> Decimal:
def get_needed_inventory_usage(companies: Union[Company, Iterable[Company]]) -> Decimal:
if isinstance(companies, list):
return sum([company.products_made * 100 if company.is_raw else 1 for company in companies])
return sum(company.products_made * 100 if company.is_raw else 1 for company in companies)
else:
return companies.products_made
@property
def companies(self) -> Generator[Company, None, None]:
return (c for c in self._companies)
def __str__(self):
return f"MyCompanies: {len(self.companies)} companies in {len(self.holdings)} holdings"
return f"MyCompanies: {sum(1 for _ in self.companies)} companies in {len(self.holdings)} holdings"
def __repr__(self):
return str(self)
@ -308,14 +313,16 @@ class MyCompanies:
for company in holding.companies: # noqa
del company
holding.companies.clear()
self.companies.clear()
self._companies.clear()
@property
def as_dict(self) -> Dict[str, Union[str, int, datetime.datetime, Dict[str, Dict[str, Union[str, int, List[Dict[str, Union[str, int, bool, float, Decimal]]]]]]]]:
def as_dict(self) -> Dict[str, Union[str, int, datetime.datetime, Dict[str, Dict[str, Union[
str, int, List[Dict[str, Union[str, int, bool, float, Decimal]]]]
]]]]:
return dict(name=str(self), work_units=self.work_units, next_ot_time=self.next_ot_time,
ff_lockdown=self.ff_lockdown,
holdings={str(hi): h.as_dict for hi, h in self.holdings.items()},
company_count=len(self.companies))
company_count=sum(1 for _ in self.companies))
@property
def citizen(self):
@ -352,6 +359,8 @@ class Config:
telegram = True
telegram_chat_id = 0
telegram_token = ""
maverick = False
spin_wheel_of_fortune = False
def __init__(self):
self.auto_sell = []
@ -384,18 +393,21 @@ class Config:
self.telegram = True
self.telegram_chat_id = 0
self.telegram_token = ""
self.maverick = False
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,
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,
force_wam=self.force_wam, sort_battles_time=self.sort_battles_time, force_travel=self.force_travel,
telegram=self.telegram, telegram_chat_id=self.telegram_chat_id, telegram_token=self.telegram_token)
telegram=self.telegram, telegram_chat_id=self.telegram_chat_id, telegram_token=self.telegram_token,
spin_wheel_of_fortune=self.spin_wheel_of_fortune)
class Energy:
@ -409,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)
@ -443,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,
@ -498,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,
@ -517,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)
@ -555,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)
@ -588,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):
@ -599,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
@ -638,6 +650,16 @@ class Reporter:
air=battle.has_air, hits=hits,
round=battle.zone_id, extra=dict(battle=battle, side=side, division=division)))
def report_money_donation(self, citizen_id: int, amount: float, is_currency: bool = True):
cur = 'cc' if is_currency else 'gold'
self.report_action('DONATE_MONEY', dict(citizen_id=citizen_id, amount=amount, currency=cur),
f"Successfully donated {amount}{cur} to citizen with id {citizen_id}!")
def report_item_donation(self, citizen_id: int, amount: float, quality: int, industry: str):
self.report_action('DONATE_ITEMS',
dict(citizen_id=citizen_id, amount=amount, quality=quality, industry=industry),
f"Successfully donated {amount} x {industry} q{quality} to citizen with id {citizen_id}!")
def report_promo(self, kind: str, time_until: datetime.datetime):
self._req.post(f"{self.url}/promos/add/", data=dict(kind=kind, time_untill=time_until))
@ -709,17 +731,17 @@ class BattleSide:
def __repr__(self):
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):
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):
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)
@ -779,7 +801,7 @@ class BattleDivision:
return constants.TERRAINS[self.terrain]
def __str__(self):
base_name = f"Div #{self.id} d{self.div}"
base_name = f"D{self.div} #{self.id}"
if self.terrain:
base_name += f" ({self.terrain_display})"
if self.div_end:
@ -854,13 +876,15 @@ class Battle:
self.invader = BattleSide(
self, constants.COUNTRIES[battle.get('inv', {}).get('id')], battle.get('inv', {}).get('points'),
[constants.COUNTRIES[row.get('id')] for row in battle.get('inv', {}).get('ally_list')],
[constants.COUNTRIES[row.get('id')] for row in battle.get('inv', {}).get('ally_list') if row['deployed']], False
[constants.COUNTRIES[row.get('id')] for row in battle.get('inv', {}).get('ally_list') if row['deployed']],
False
)
self.defender = BattleSide(
self, constants.COUNTRIES[battle.get('def', {}).get('id')], battle.get('def', {}).get('points'),
[constants.COUNTRIES[row.get('id')] for row in battle.get('def', {}).get('ally_list')],
[constants.COUNTRIES[row.get('id')] for row in battle.get('def', {}).get('ally_list') if row['deployed']], True
[constants.COUNTRIES[row.get('id')] for row in battle.get('def', {}).get('ally_list') if row['deployed']],
True
)
self.div = {}
@ -869,7 +893,7 @@ class Battle:
if data.get('end'):
end = datetime.datetime.fromtimestamp(data.get('end'), tz=constants.erep_tz)
else:
end = utils.localize_dt(datetime.datetime.max - datetime.timedelta(days=1))
end = constants.max_datetime
battle_div = BattleDivision(self, div_id=data.get('id'), div=data.get('div'), end=end,
epic=data.get('epic_type') in [1, 5],
@ -883,11 +907,12 @@ 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]} | {self.invader} : {self.defender} | Round time {time_part}"
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}")
def __repr__(self):
return f"<Battle #{self.id} {self.invader}:{self.defender} R{self.zone_id}>"
@ -924,7 +949,7 @@ class EnergyToFight:
return self.energy
class TelegramBot:
class TelegramReporter:
__initialized: bool = False
__queue: List[str]
chat_id: int = 0
@ -949,15 +974,17 @@ 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))
if self.__queue:
self.send_message("\n\n\n\n".join(self.__queue))
self.send_message("Telegram initialized")
def send_message(self, message: str) -> bool:
self.__queue.append(message)
@ -968,7 +995,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)
@ -985,15 +1012,29 @@ class TelegramBot:
new_line = '\n' if multiple else ''
self.send_message(f"New award: {new_line}*{msg}*")
def report_fight(self, battle: "Battle", invader: bool, division: "BattleDivision", damage: float, hits: int):
side_txt = (battle.invader if invader else battle.defender).country.iso
self.send_message(f"*Fight report*:\n{int(damage):,d} dmg ({hits} hits) in"
f" [battle {battle.id} for {battle.region_name[:16]}]({battle.link}) in d{division.div} on "
f"{side_txt} side")
def report_item_donation(self, citizen_id: int, amount: float, product: str):
self.send_message(f"*Donation*: {amount} x {product} to citizen "
f"[{citizen_id}](https://www.erepublik.com/en/citizen/profile/{citizen_id})")
def report_money_donation(self, citizen_id: int, amount: float, is_currency: bool = True):
self.send_message(f"*Donation*: {amount}{'cc' if is_currency else 'gold'} to citizen "
f"[{citizen_id}](https://www.erepublik.com/en/citizen/profile/{citizen_id})")
def __send_messages(self):
while self._next_time > utils.now():
if self.__thread_stopper.is_set():
break
self.__thread_stopper.wait(utils.get_sleep_seconds(self._next_time))
message = "\n\n\n\n".join(self.__queue)
message = "\n\n".join(self.__queue)
if self.player_name:
message = f"Player *{self.player_name}*\n" + message
message = f"Player *{self.player_name}*\n\n" + message
response = post(self.api_url, json=dict(chat_id=self.chat_id, text=message, parse_mode="Markdown"))
self._last_time = utils.now()
if response.json().get('ok'):
@ -1003,8 +1044,32 @@ class TelegramBot:
class OfferItem(NamedTuple):
price: float = 99_999.
price: float = 999_999_999.
country: constants.Country = constants.Country(0, "", "", "")
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)

View File

@ -1,10 +1,14 @@
import datetime
from typing import Dict, Optional, Union
import pytz
__all__ = ["erep_tz", "Country", "AIR_RANKS", "COUNTRIES", "FOOD_ENERGY", "GROUND_RANKS", "GROUND_RANK_POINTS", "INDUSTRIES", "TERRAINS"]
__all__ = ["erep_tz", 'min_datetime', "max_datetime", "Country", "AIR_RANKS", "COUNTRIES", "FOOD_ENERGY",
"GROUND_RANKS", "GROUND_RANK_POINTS", "INDUSTRIES", "TERRAINS"]
erep_tz = pytz.timezone('US/Pacific')
min_datetime = erep_tz.localize(datetime.datetime(2007, 11, 20))
max_datetime = erep_tz.localize(datetime.datetime(2281, 9, 4))
class Country:
@ -49,17 +53,17 @@ class Country:
class Industries:
__by_name = {'food': 1, 'weapon': 2, 'house': 4, 'aircraft': 23,
'foodraw': 7, 'weaponraw': 12, 'houseraw': 18, 'aircraftraw': 24,
__by_name = {'food': 1, 'weapon': 2, 'ticket': 3, 'house': 4, 'aircraft': 23,
'foodraw': 7, 'weaponraw': 12, 'houseraw': 18, 'aircraftraw': 24, 'airplaneraw': 24,
'frm': 7, 'wrm': 12, 'hrm': 18, 'arm': 24,
'frm q1': 7, 'frm q2': 8, 'frm q3': 9, 'frm q4': 10, 'frm q5': 11,
'wrm q1': 12, 'wrm q2': 13, 'wrm q3': 14, 'wrm q4': 15, 'wrm q5': 16,
'hrm q1': 18, 'hrm q2': 19, 'hrm q3': 20, 'hrm q4': 21, 'hrm q5': 22,
'arm q1': 24, 'arm q2': 25, 'arm q3': 26, 'arm q4': 27, 'arm q5': 28}
__by_id = {1: "Food", 2: "Weapon", 4: "House", 23: "Aircraft",
__by_id = {1: "Food", 2: "Weapon", 3: "Ticket", 4: "House", 23: "Aircraft",
7: "foodRaw", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
12: "weaponRaw", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
18: "houseRaw", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
17: "houseRaw", 18: "houseRaw", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
24: "aircraftRaw", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5"}
def __getitem__(self, item) -> Optional[Union[int, str]]:

7
erepublik/types.py Normal file
View File

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

View File

@ -7,9 +7,10 @@ import textwrap
import time
import traceback
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
@ -20,14 +21,14 @@ 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',
'write_interactive_log', 'write_silent_log']
'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, 7):
raise AssertionError('This script requires Python version 3.7 and higher\n'
if not sys.version_info >= (3, 6):
raise AssertionError('This script requires Python version 3.6 and higher\n'
'But Your version is v{}.{}.{}'.format(*sys.version_info))
VERSION: str = __version__
@ -47,7 +48,7 @@ def localize_dt(dt: Union[datetime.date, datetime.datetime]) -> datetime.datetim
elif isinstance(dt, datetime.date):
return constants.erep_tz.localize(datetime.datetime.combine(dt, datetime.time(0, 0, 0)))
else:
return dt.astimezone(constants.erep_tz)
raise TypeError(f"Argument dt must be and instance of datetime.datetime or datetime.date not {type(dt)}")
def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.datetime:
@ -63,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
@ -92,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
@ -104,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')
@ -151,11 +154,13 @@ def get_file(filepath: str) -> str:
def write_file(filename: str, content: str) -> int:
filename = get_file(filename)
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):
from erepublik import Citizen
# Remove GET args from url name
url = response.url
last_index = url.index("?") if "?" in url else len(response.url)
@ -170,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"}
@ -194,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" + \
@ -250,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
@ -337,17 +342,8 @@ def calculate_hit(strength: float, rang: int, tp: bool, elite: bool, ne: bool, b
base_wpn = (1 + Decimal(str(weapon / 100)))
dmg = 10 * base_str * base_rnk * base_wpn
if elite:
dmg = dmg * 11 / 10
if tp and rang >= 70:
dmg = dmg * (1 + Decimal((rang - 69) / 10))
dmg = dmg * (100 + booster) / 100
if ne:
dmg = dmg * 11 / 10
return round(dmg, dec)
dmg = get_final_hit_dmg(dmg, rang, tp=tp, elite=elite, ne=ne, booster=booster)
return Decimal(round(dmg, dec))
def get_ground_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False,
@ -370,7 +366,46 @@ def get_air_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_pat
return calculate_hit(0, rang, true_patriot, elite, natural_enemy, booster, weapon_power)
def _clear_up_battle_memory(battle):
del battle.invader._battle, battle.defender._battle
for div_id, division in battle.div.items():
del division._battle
def get_final_hit_dmg(base_dmg: Union[Decimal, float, str], rang: int,
tp: bool = False, elite: bool = False, ne: bool = False, booster: int = 0) -> Decimal:
dmg = Decimal(str(base_dmg))
if elite:
dmg = dmg * 11 / 10
if tp and rang >= 70:
dmg = dmg * (1 + Decimal((rang - 69) / 10))
dmg = dmg * (100 + booster) / 100
if ne:
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():
# del division._battle
def deprecation(message):
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

@ -29,7 +29,7 @@ def _battle_launcher(player: Citizen):
"""
global CONFIG
finished_war_ids = {*[]}
war_data = CONFIG.get('start_battles', {})
war_data = CONFIG.get('battle_launcher', {})
war_ids = {int(war_id) for war_id in war_data.keys()}
next_attack_time = player.now
next_attack_time = next_attack_time.replace(minute=next_attack_time.minute // 5 * 5, second=0)
@ -45,7 +45,7 @@ def _battle_launcher(player: Citizen):
status = player.get_war_status(war_id)
if status.get('ended', False):
CONFIG['start_battles'].pop(war_id, None)
CONFIG['battle_launcher'].pop(war_id, None)
finished_war_ids.add(war_id)
continue
elif not status.get('can_attack'):
@ -64,7 +64,15 @@ def _battle_launcher(player: Citizen):
player.update_war_info()
battle_id = player.get_war_status(war_id).get("battle_id")
if battle_id is not None and battle_id in player.all_battles:
player.fight(battle_id, player.details.citizenship, hits)
battle = player.all_battles.get(battle_id)
for division in battle.div.values():
if division.div == player.division:
div = division
break
else:
player.report_error("Players division not found in the first round!")
break
player.fight(battle, div, battle.invader, hits)
break
player.sleep(1)
if attacked:
@ -89,7 +97,7 @@ def main():
player.set_debug(CONFIG.get('debug', False))
player.login()
if CONFIG.get('battle_launcher'):
name = "{}-battle_launcher-{}".format(player.name, threading.active_count() - 1)
name = f"{player.name}-battle_launcher-{threading.active_count() - 1}"
state_thread = threading.Thread(target=_battle_launcher, args=(player,), name=name)
state_thread.start()

View File

@ -1,12 +1,16 @@
from datetime import timedelta
from erepublik import Citizen, utils
from erepublik import Citizen, constants, utils
CONFIG = {
'email': 'player@email.com',
'password': 'Pa$5w0rd!',
'interactive': True,
'debug': True
'debug': True,
'work': True,
'ot': True, # Work OverTime
'wam': True, # WorkAsManager
'train': True
}
@ -14,11 +18,14 @@ CONFIG = {
def main():
player = Citizen(email=CONFIG['email'], password=CONFIG['password'], auto_login=False)
player.config.interactive = CONFIG['interactive']
player.config.fight = CONFIG['fight']
player.config.work = CONFIG['work']
player.config.train = CONFIG['train']
player.config.ot = CONFIG['ot']
player.config.wam = CONFIG['wam']
player.set_debug(CONFIG.get('debug', False))
player.login()
now = player.now.replace(second=0, microsecond=0)
dt_max = now.replace(year=9999)
dt_max = constants.max_datetime
tasks = {
'eat': now,
}
@ -76,7 +83,7 @@ def main():
tasks.update({'eat': next_time})
if tasks.get('ot', dt_max) <= now:
player.write_log("Doing task: ot")
player.write_log("Doing task: work overtime")
if now > player.my_companies.next_ot_time:
player.work_ot()
next_time = now + timedelta(minutes=60)
@ -87,15 +94,15 @@ def main():
closest_next_time = dt_max
next_tasks = []
for task, next_time in sorted(tasks.items(), key=lambda s: s[1]):
next_tasks.append("{}: {}".format(next_time.strftime('%F %T'), task))
next_tasks.append(f"{next_time.strftime('%F %T')}: {task}")
if next_time < closest_next_time:
closest_next_time = next_time
sleep_seconds = int(utils.get_sleep_seconds(closest_next_time))
if sleep_seconds <= 0:
player.write_log(f"Loop detected! Offending task: '{next_tasks[0]}'")
player.write_log("My next Tasks and there time:\n" + "\n".join(sorted(next_tasks)))
player.write_log("Sleeping until (eRep): {} (sleeping for {}s)".format(
closest_next_time.strftime("%F %T"), sleep_seconds))
player.write_log(f"Sleeping until (eRep): {closest_next_time.strftime('%F %T')}"
f" (sleeping for {sleep_seconds}s)")
seconds_to_sleep = sleep_seconds if sleep_seconds > 0 else 0
player.sleep(seconds_to_sleep)
except Exception as e:

View File

@ -1,19 +1,19 @@
bump2version==1.0.0
coverage==5.3
edx-sphinx-theme==1.5.0
flake8==3.8.3
ipython==7.18.1
isort==5.5.3
pip==20.2.3
PyInstaller==4.0
pytz==2020.1
pytest==6.1.0
responses==0.12.0
setuptools==50.3.0
Sphinx==3.2.1
requests==2.24.0
bump2version==1.0.1
coverage==5.3.1
edx-sphinx-theme==1.6.0
flake8==3.8.4
ipython>=7.19.0
isort==5.7.0
pip==20.3.3
pre-commit==2.9.3
pur==5.3.0
PyInstaller==4.1
PySocks==1.7.1
tox==3.20.0
twine==3.2.0
watchdog==0.10.3
wheel==0.35.1
pytest==6.2.1
pytz>=2020.5
requests>=2.25.1
responses==0.12.1
setuptools==51.1.2
Sphinx==3.4.3
twine==3.3.0
wheel==0.36.2

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.21.5.6
current_version = 0.23.4.11
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
@ -37,4 +37,3 @@ warn_unused_configs = True
[isort]
multi_line_output = 2
line_length = 120
not_skip = __init__.py

View File

@ -11,11 +11,18 @@ with open('README.rst') as readme_file:
with open('HISTORY.rst') as history_file:
history = history_file.read()
requirements = ['pytz==2020.1', 'requests==2.24.0', 'PySocks==1.7.1']
requirements = [
'pytz>=2020.0',
'requests>=2.24.0,<2.26.0',
'PySocks==1.7.1'
]
setup_requirements = []
test_requirements = []
test_requirements = [
"pytest==6.1.2",
"responses==0.12.1"
]
setup(
author="Eriks Karls",
@ -43,6 +50,6 @@ setup(
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/',
version='0.21.5.6',
version='0.23.4.11',
zip_safe=False,
)

View File

@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
"""Tests for `erepublik` package."""
from typing import Callable
from erepublik import Citizen
@ -66,7 +65,8 @@ class TestErepublik(unittest.TestCase):
self.assertEqual(self.citizen.next_reachable_energy, 0)
def test_should_fight(self):
is_wc_close: Callable[[], bool] = lambda: self.citizen.max_time_till_full_ff > self.citizen.time_till_week_change
def is_wc_close():
return self.citizen.max_time_till_full_ff > self.citizen.time_till_week_change
self.citizen.config.fight = False
self.assertEqual(self.citizen.should_fight(), (0, "Fighting not allowed!", False))

18
tox.ini
View File

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