Compare commits

...

685 Commits

Author SHA1 Message Date
72b12a5ca2 Lint 2022-03-15 10:36:39 +02:00
1831ddfb49 Bump version: 0.29.2.2 → 0.29.2.3 2022-03-11 12:01:40 +02:00
f9d2ad85c2 bugfix 2022-03-11 12:01:30 +02:00
66a05bb202 Bump version: 0.29.2.1 → 0.29.2.2 2022-03-11 11:44:54 +02:00
273e98c8d1 Update 2022-03-11 11:44:29 +02:00
03c435bf7e Bump version: 0.29.2 → 0.29.2.1 2022-03-06 21:22:36 +02:00
112150cce6 bugfix
version update
2022-03-06 21:22:26 +02:00
eb9231b0e7 Bump version: 0.29.1 → 0.29.2 2022-03-06 16:36:49 +02:00
763fbaaf9c Update wam logic 2022-03-06 16:36:35 +02:00
e9f3441678 style setting update 2022-03-06 16:35:49 +02:00
Eriks K
f59b47d131 Bump version: 0.29.0.6 → 0.29.1 2022-02-04 00:41:40 +02:00
Eriks K
6ca6d416b3 Merge branch 'master' of github.com:eeriks/erepublik 2022-02-04 00:41:20 +02:00
Eriks K
903d596064 Fixup 2022-02-04 00:41:13 +02:00
8a2a1ab393 autosell bugfix 2022-02-04 00:34:25 +02:00
Eriks K
3ee9811b8f Bump version: 0.29.0.5 → 0.29.0.6 2022-02-01 23:01:51 +02:00
Eriks K
f9b136faba Bugfix 2022-02-01 23:01:44 +02:00
Eriks K
1fb186961f Bump version: 0.29.0.4 → 0.29.0.5 2022-02-01 22:57:18 +02:00
Eriks K
2c1cf2fc66 Bugfix 2022-02-01 22:57:07 +02:00
Eriks K
75996779e1 Bump version: 0.28.3 → 0.29.0 2022-02-01 22:21:44 +02:00
Eriks K
d5ce52162a Updated WAM logic for cases when there are too many RAW factories 2022-02-01 22:21:29 +02:00
7e5312df18 Bump version: 0.28.2 → 0.28.3 2022-01-26 14:03:21 +02:00
8474920b72 Bugfix requests 2.27.0 contains bug which prohibits using auth with socks proxy 2022-01-26 14:03:11 +02:00
3426f60749 Bump version: 0.28.1 → 0.28.2 2022-01-26 13:10:23 +02:00
c703201ae7 Bugfix 2022-01-26 13:10:07 +02:00
0119ebe44b Bump version: 0.28.0.4 → 0.28.1 2022-01-06 10:11:50 +02:00
247d87ac5e Update UA browser versions; Remove Android as system platform
lint
2022-01-06 10:11:35 +02:00
e978c04228 Initialize headers after login 2021-11-26 09:53:00 +02:00
e925ab455d RTD yaml 2021-11-24 17:02:19 +02:00
1447b21676 Small fixes 2021-11-24 16:54:36 +02:00
575bb60f05 GitHub worklflow update 2021-11-24 14:36:15 +02:00
81c1469752 Bump version: 0.28.0.3 → 0.28.0.4 2021-11-19 08:50:31 +02:00
490809045d Merge branch 'master' of github.com:eeriks/erepublik 2021-11-19 08:49:20 +02:00
f425faea5b Bump version: 0.28.0.2 → 0.28.0.3 2021-11-19 08:41:44 +02:00
b7fe834091 Bump version: 0.28.0.1 → 0.28.0.2 2021-11-19 08:39:45 +02:00
bc09133f61 Fight removal bugfix 2021-11-19 08:39:30 +02:00
8b264e2b9c Bump version: 0.28.0.1 → 0.28.0.2 2021-11-17 00:31:08 +02:00
711a61783c Bugfix 2021-11-17 00:30:48 +02:00
c9942cd565 Bump version: 0.28.0 → 0.28.0.1 2021-11-16 23:24:52 +02:00
0f6529262e Moved Fight code to backup file 2021-11-16 23:24:36 +02:00
8243664c24 Bump version: 0.27.2 → 0.28.0 2021-11-16 22:47:32 +02:00
c8834e0f9e Fixed medal amounts, removed military related stuff 2021-11-16 22:47:02 +02:00
954e9d576b Bump version: 0.27.1 → 0.27.2 2021-11-02 18:44:50 +02:00
47ec6c03df Updated requirements 2021-11-02 18:44:40 +02:00
44360bd143 erep.lv url update 2021-11-02 18:32:13 +02:00
Eriks K
3e0caae041 Doc update 2021-10-23 16:55:26 +03:00
Eriks K
5ce4c62f22 Bump version: 0.27.0 → 0.27.1 2021-10-23 16:32:22 +03:00
Eriks K
b80fa43e99 Updates 2021-10-23 16:30:04 +03:00
62f53c0396 Short Maintenance message 2021-10-23 16:19:00 +03:00
051d4765a4 Correct sleeping if no energy 2021-10-23 16:19:00 +03:00
365ad9a719 Send state update and let the bg task live even if Captcha is required 2021-10-23 16:19:00 +03:00
Ēriks Karls
81f00bdbf6 Create codeql-analysis.yml 2021-10-23 16:16:40 +03:00
Eriks K
44d221ac1b Merge branch 'master' of github.com:eeriks/erepublik 2021-10-23 16:16:17 +03:00
7054ae2b05 Bump version: 0.26.0.2 → 0.27.0 2021-08-30 16:36:35 +03:00
2288fe01ea Fixed promo reporting 2021-08-30 16:36:25 +03:00
dfa6f7e0be Changed linting make command 2021-08-30 16:36:05 +03:00
1746f27193 Bump version: 0.26.0.1 → 0.26.0.2 2021-08-28 10:41:47 +03:00
c8f9ac9768 PromoFixup 2021-08-28 10:41:44 +03:00
7773b63520 fix
fix
2021-08-27 19:35:15 +03:00
211df41969 Bump version: 0.26.0 → 0.26.0.1 2021-08-27 19:31:20 +03:00
421b6fa919 Licence changes 2021-08-27 19:29:52 +03:00
68f40fd4ab Bump version: 0.25.1.5 → 0.26.0 2021-08-27 19:24:59 +03:00
843f02c7f0 bugfix 2021-08-27 19:24:52 +03:00
Ēriks Karls
c5e4da9dd5
Change LICENCE 2021-08-27 19:20:21 +03:00
Eriks K
e4814cfa8e updates 2021-08-18 10:40:19 +03:00
Eriks K
ba336fe8ed Zero medal reward update 2021-08-18 09:28:00 +03:00
Eriks K
fad6d4316d Merge branch 'master' of github.com:eeriks/erepublik 2021-08-18 09:20:38 +03:00
Eriks K
86e738ad08 Browser UA version update
gitignore

Updated constants

Requirements update
2021-08-18 09:19:55 +03:00
cd7065b11a Bump version: 0.25.1.4 → 0.25.1.5 2021-07-23 11:53:19 +03:00
8d096b2770 Promo bugfix 2021-07-23 11:53:02 +03:00
dd6e22af51 Bump version: 0.25.1.3 → 0.25.1.4 2021-07-23 11:14:50 +03:00
df4ed4fceb bugfix 2021-07-23 11:14:27 +03:00
b5973ef815 Bump version: 0.25.1.2 → 0.25.1.3 2021-07-23 10:29:46 +03:00
61df989cb4 Catch connection errors.
Not logged in bugfix.
Minor tweaks.

Black for setup
2021-07-23 10:29:33 +03:00
ade0f9b11a Black formatting
Style config update

Makefile update

Test fix
2021-07-21 14:20:35 +03:00
0b07ee8b8f Bump version: 0.25.1.1 → 0.25.1.2 2021-07-20 14:47:17 +03:00
b8884b4877 Requirement update
Bugfix
2021-07-20 14:05:54 +03:00
5a1f7801a2 Bump version: 0.25.1 → 0.25.1.1 2021-07-20 13:16:55 +03:00
17845f750c Bugfix 2021-07-20 13:14:13 +03:00
Eriks K
883af51197 Bump version: 0.25.0.4 → 0.25.1 2021-07-10 01:32:57 +03:00
Eriks K
814cb5ab87 Programmatic user agent spoofing 2021-07-10 01:29:31 +03:00
Eriks K
3c316bada3 Bump version: 0.25.0.3 → 0.25.0.4 2021-07-10 01:14:30 +03:00
Eriks K
ea03979943 Energy variable bugfix 2021-07-10 01:13:21 +03:00
Eriks K
9aae685b21 Bump version: 0.25.0.2 → 0.25.0.3 2021-07-10 00:47:51 +03:00
Eriks K
cae94d7aa8 requirement update 2021-07-10 00:47:40 +03:00
Eriks K
fae7b0fd37 Bump version: 0.25.0.1 → 0.25.0.2 2021-07-10 00:37:46 +03:00
Eriks K
b7771b4da2 Update 2021-07-10 00:36:22 +03:00
Eriks K
e3a10af101 Bump version: 0.25.0 → 0.25.0.1 2021-05-25 09:54:44 +03:00
Eriks K
33a5bcacf1 Backward compatability bugfix 2021-05-25 09:54:35 +03:00
Eriks K
342ca2e1cc Bump version: 0.24.2.4 → 0.25.0 2021-05-25 09:49:49 +03:00
Eriks K
580240a015 isort 2021-05-25 09:49:27 +03:00
Eriks K
1517103ba3 Remove deprecated eat task from examples 2021-05-25 09:48:08 +03:00
Eriks K
3dac8c5e74 New eating 2021-05-25 09:43:47 +03:00
Eriks K
8cf86fb9d3 Bump version: 0.24.2.3 → 0.24.2.4 2021-04-27 12:21:54 +03:00
Eriks K
cf927df6e6 bugfix 2021-04-27 12:21:50 +03:00
Eriks K
a6f5dbd05f Bump version: 0.24.2.2 → 0.24.2.3 2021-04-27 12:19:22 +03:00
Eriks K
967afa472f Rank.__str__ 2021-04-27 12:18:56 +03:00
Eriks K
a65568cd0c Bump version: 0.24.2.1 → 0.24.2.2 2021-04-27 11:05:57 +03:00
Eriks K
6e45334d99 Rank comparision 2021-04-27 11:05:51 +03:00
Eriks K
936a1010a6 Bump version: 0.24.2 → 0.24.2.1 2021-04-26 13:12:32 +03:00
Eriks K
acc528cb1d bugfix 2021-04-26 13:12:28 +03:00
Eriks K
614d273104 Bump version: 0.24.1 → 0.24.2 2021-04-23 13:46:51 +03:00
Eriks K
95966764e8 Updated rank constants 2021-04-23 13:45:58 +03:00
Eriks K
f52b078e6a Captcha challange solving simplification 2021-03-05 16:11:52 +02:00
Eriks K
3af27f6512 Cookie dump migration 2021-03-03 13:15:26 +02:00
Eriks K
6276242260 bugfixes 2021-03-03 13:13:19 +02:00
Eriks K
45623de97b Reporter update to queue messages if network error occures 2021-02-06 15:33:08 +02:00
Eriks K
25f932121c Code cleanup and JSONEncoder update to support dumping Logger instances 2021-02-06 15:32:30 +02:00
Eriks K
61be2b1edf Bump version: 0.24.0.5 → 0.24.1 2021-02-06 12:22:37 +02:00
Eriks K
ce9034ad24 Legacy fight support until 2021-02-08 00:00:00 2021-02-06 12:22:23 +02:00
Eriks K
69b2073b74 more fixes 2021-02-06 11:07:23 +02:00
Eriks K
eb048bf9f8 session dump 2021-02-06 11:07:03 +02:00
Eriks K
fe1206dc84 Cookie magick 2021-02-05 13:47:30 +02:00
Eriks K
a2a1ed3dad Cookie magick 2021-02-05 13:37:32 +02:00
Eriks K
39c8f6913e Cookie magick 2021-02-04 20:33:59 +02:00
Eriks K
d7b15b3708 changes 2021-02-04 13:50:24 +02:00
Eriks K
4fe3efa045 fix 2021-02-03 21:01:37 +02:00
Eriks K
14bcb46735 More precisly mimic javascript's JSON.stringify() 2021-02-03 20:13:51 +02:00
Eriks K
b04cc896d8 bugfix 2021-02-03 20:13:15 +02:00
Eriks K
f07062788b LocalVars 2021-02-03 18:45:23 +02:00
Eriks K
4504bdaa97 don't lose image id 2021-02-03 18:15:06 +02:00
Eriks K
ac135614cc LocalVars 2021-02-03 17:27:55 +02:00
Eriks K
41752e1f2e clickMatrix bugfix 2021-02-03 17:08:58 +02:00
Eriks K
a1739e627e refactoring 2021-02-03 16:43:49 +02:00
Eriks K
12ff11deea refactoring 2021-02-03 16:30:52 +02:00
Eriks K
4e3a16b8d4 Don't print stack and exc traces 2021-02-03 16:16:23 +02:00
Eriks K
5f56f59ab8 bugfi 2021-02-03 14:15:32 +02:00
Eriks K
50c66efbda report error update 2021-02-03 14:03:43 +02:00
Eriks K
47b3154c6a Typehints 2021-02-03 13:36:26 +02:00
Eriks K
632e4e8ad2 HttpHandler improvements 2021-02-03 12:02:06 +02:00
Eriks K
7c0d66f126 deploy 2021-02-03 02:03:02 +02:00
Eriks K
842fb64dae deploy 2021-02-03 01:58:18 +02:00
Eriks K
b22349cb1a Disabled oldSchool Shoooooooooot 2021-02-03 01:48:53 +02:00
Eriks K
a9ced91741 deploy 2021-02-03 01:47:50 +02:00
Eriks K
e374562189 Bump version: 0.24.0.4 → 0.24.0.5 2021-02-03 01:09:51 +02:00
Eriks K
e0b64e09b1 bugfix 2021-02-03 01:09:43 +02:00
Eriks K
c51337d249 Bump version: 0.24.0.3 → 0.24.0.4 2021-02-03 01:07:21 +02:00
Eriks K
f4896e0b79 Merge branch 'bugfix-v0.23.4'
# Conflicts:
#	erepublik/__init__.py
#	setup.cfg
#	setup.py
2021-02-03 00:50:03 +02:00
Eriks K
13f5c673ad Migrate Citizen.write_log to self.logger.warning where applicable 2021-02-03 00:48:50 +02:00
Eriks K
e95ffbd505 cleanup 2021-02-03 00:48:13 +02:00
Eriks K
5e638806b5 Switch from manual log creation to Python logging 2021-02-03 00:47:52 +02:00
Eriks K
7860fa3669 Bump version: 0.23.4.15 → 0.23.4.16 2021-02-02 23:57:58 +02:00
Eriks K
e38f603e8b Division switch bugfix for option to switch side 2021-02-02 23:57:54 +02:00
Eriks K
0e1c42a8fb Bump version: 0.23.4.14 → 0.23.4.15 2021-02-02 23:52:38 +02:00
Eriks K
ddc412b348 accesspoint update to change side in RW 2021-02-02 23:52:32 +02:00
Eriks K
7f3bd9b864 Bump version: 0.24.0.2 → 0.24.0.3 2021-01-28 01:46:11 +02:00
Eriks K
9154d2e493 Captcha solving 2021-01-28 01:46:03 +02:00
Eriks K
8e8b882ace Bump version: 0.24.0.1 → 0.24.0.2 2021-01-28 00:37:12 +02:00
Eriks K
d7b020c7ea bugfix 2021-01-28 00:37:10 +02:00
Eriks K
cb567bf5c0 Bump version: 0.24.0 → 0.24.0.1 2021-01-28 00:31:53 +02:00
Eriks K
bb6d1be1b5 bugfix 2021-01-28 00:31:46 +02:00
Eriks K
c546c8f5eb Bump version: 0.23.4.14 → 0.24.0 2021-01-27 23:43:48 +02:00
Eriks K
9acc2d2e65 Lets deploy! Preperation for 8th of February 2021-01-27 23:43:29 +02:00
Eriks K
51a15874f2 Bump version: 0.23.4.13 → 0.23.4.14 2021-01-21 23:12:05 +02:00
Eriks K
af8f764396 Double-quoted strings converted into single-quoted strings 2021-01-21 23:11:39 +02:00
Eriks K
65b555f2bd Minor tweaks and double-quoted strings converted into single-quoted strings 2021-01-21 19:05:00 +02:00
Eriks K
319b4414df Removed unused import 2021-01-21 19:01:03 +02:00
Eriks K
b71cb0c263 Minor tweaks and double-quoted strings converted into single-quoted strings 2021-01-21 19:00:40 +02:00
Eriks K
0936dee06c Bump version: 0.23.4.12 → 0.23.4.13 2021-01-20 22:25:16 +02:00
Eriks K
a1c6fb06a0 Task fetcher updates, state update repeater frequency increased for restricted IPs to not timeout cookie session 2021-01-20 22:25:07 +02:00
Eriks K
c57bf99976 If citizen is on restricted IP, clear cookies when session times out 2021-01-20 16:42:36 +02:00
Eriks K
995b45464f Bump version: 0.23.4.11 → 0.23.4.12 2021-01-20 16:12:51 +02:00
Eriks K
ebe28c948a requirement update 2021-01-20 16:12:40 +02:00
Eriks K
34d7230faf Response serialization update to include status code. Json encoder and decoder updates 2021-01-20 16:07:19 +02:00
Eriks K
3235991cce User agent updates 2021-01-20 15:15:31 +02:00
Eriks K
3fba1d6b3d Bump version: 0.23.4.10 → 0.23.4.11 2021-01-18 16:35:35 +02:00
Eriks K
88c8d5a9a0 Unified all event setting 2021-01-18 16:35:31 +02:00
Eriks K
382749a8d8 Bump version: 0.23.4.9 → 0.23.4.10 2021-01-18 16:12:31 +02:00
Eriks K
0c877e315b as_dict updates 2021-01-18 16:12:26 +02:00
Eriks K
c95f642fee Citizen.as_dict rewrite 2021-01-18 16:00:29 +02:00
Eriks K
da0276f9a6 Removed unused CitizenMilitary.boosters property
Explicitly updated all Citizen*.as_dict properties
2021-01-18 15:22:53 +02:00
Eriks K
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
Eriks K
56c2ca1b6e House renewal price check bugfix 2021-01-12 08:05:57 +02:00
Eriks K
3cee2d1f0f Bump version: 0.23.4.8 → 0.23.4.9 2021-01-11 21:05:22 +02:00
Eriks K
0ed03877ce Damage booster's name change 2021-01-11 21:05:12 +02:00
Eriks K
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
Eriks K
8445b556e7 Bump version: 0.23.4.7 → 0.23.4.8 2021-01-11 16:21:14 +02:00
Eriks K
d02c5c2969 dev requirement update 2021-01-11 16:20:58 +02:00
Eriks K
bff4183cb6 Added option to fight with energy bars, count small energy bars more precisely 2021-01-11 16:19:10 +02:00
Eriks K
47f5142837 Bump version: 0.23.4.6 → 0.23.4.7 2021-01-08 11:09:39 +02:00
Eriks K
a32fd039dd Report details about fighting also on telegram 2021-01-08 11:08:50 +02:00
Eriks K
311e684c0c Add telegram fight reporting API, Fix message duplication on TelegramReporter initialization 2021-01-08 11:07:59 +02:00
Eriks K
52038a86d5 PackBooster icon update 2021-01-07 17:00:59 +02:00
Eriks K
c35a107641 Bump version: 0.23.4.5 → 0.23.4.6 2021-01-07 15:55:02 +02:00
Eriks K
b53b2f0fae Update loop 2021-01-07 15:54:56 +02:00
Eriks K
8da9b6221a Bump version: 0.23.4.4 → 0.23.4.5 2021-01-07 15:36:33 +02:00
Eriks K
caa41c85f6 minor tweaks 2021-01-07 15:36:27 +02:00
Eriks K
da3b5d5768 Update inventory on booster activation 2021-01-07 15:31:46 +02:00
Eriks K
f96d0233f9 CSRF bugfix 2021-01-07 15:31:10 +02:00
Eriks K
c693a5182e Merge branch 'master' of github.com:eeriks/erepublik 2021-01-07 15:14:17 +02:00
Eriks K
0dfba6a9ff Find battle and fight - reuse already calculated hit count 2021-01-07 15:14:11 +02:00
Eriks Karls
ee3eca9658 Bump version: 0.23.4.3 → 0.23.4.4 2021-01-06 23:37:05 +02:00
Eriks Karls
0e8680daca bugfix 2021-01-06 23:35:25 +02:00
Eriks K
8e3606f1a3 Doc update 2021-01-05 19:47:43 +02:00
Eriks K
bd0bcc9ac7 Bump version: 0.23.4.2 → 0.23.4.3 2021-01-05 19:35:00 +02:00
Eriks K
c6f2226e64 . 2021-01-05 19:34:43 +02:00
Eriks K
308807d800 isort, pre-commit 2021-01-05 19:29:20 +02:00
Eriks K
91565d840e Added endpoint for collect all WC rewards 2021-01-05 19:18:20 +02:00
Eriks K
b4a9dd88f8 config generator bugdix 2021-01-05 19:17:50 +02:00
Eriks K
8435aa23ba Bump version: 0.23.4.1 → 0.23.4.2 2021-01-05 16:30:49 +02:00
Eriks K
09cd275a69 type bugfix 2021-01-05 16:30:42 +02:00
Eriks K
e23a67231e Bump version: 0.23.4 → 0.23.4.1 2021-01-05 16:19:43 +02:00
Eriks K
6a03d99abf type bugfix 2021-01-05 16:19:27 +02:00
Eriks K
2e344749a6 Bump version: 0.23.3.4 → 0.23.4 2021-01-05 15:50:55 +02:00
Eriks K
5aecefbd9d Protect those precious air boosters and 100% ground boosters 2021-01-05 15:50:38 +02:00
Eriks K
8b9ee5042d Bugfixes and inventory redone 2021-01-05 15:41:51 +02:00
Eriks K
1e93006c3d History updates 2020-12-17 18:18:46 +02:00
Eriks K
b13bfcdbf3 Bump version: 0.23.3.3 → 0.23.3.4 2020-12-17 18:09:43 +02:00
Eriks K
771dbdf826 With invalid bomb (bomb not in inventory) quit after first try 2020-12-17 18:03:04 +02:00
Eriks K
9646d112d2 If exception occures - set concurrency as available 2020-12-17 18:02:14 +02:00
Eriks K
a09c37a065 Bump version: 0.23.3.2 → 0.23.3.3 2020-12-17 15:23:39 +02:00
Eriks K
ba75e961fa Updated config generator 2020-12-17 15:20:21 +02:00
Eriks K
3b5780dbd6 Updated config generator 2020-12-17 15:18:22 +02:00
Eriks K
fccd0134b5 Merge branch 'remove-tox' 2020-12-17 14:07:46 +02:00
Eriks K
b9010fa856 Fixed MRO error 2020-12-17 14:07:29 +02:00
Eriks K
3e5410289e . 2020-12-17 14:03:45 +02:00
Eriks K
661a019b0a Broken MRO 2020-12-17 14:03:30 +02:00
Eriks K
23d682959d Party presidency and congress election bugfix if player is not part of a party 2020-12-17 12:13:44 +02:00
Eriks K
5806ccb6ca Buy food if unable to work/train because of food shortage 2020-12-14 13:51:16 +02:00
Eriks K
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
Eriks K
c458eb4b1c Bump version: 0.23.3.1 → 0.23.3.2 2020-12-11 11:12:34 +02:00
Eriks K
4af4d284c9 Updated dev requirement versions 2020-12-11 11:12:27 +02:00
Eriks K
104c1a0b16 Bump required python version 2020-12-11 11:12:01 +02:00
Eriks K
86f820771b Hits done bugfix 2020-12-11 11:11:25 +02:00
Eriks K
2a7af0cb7d Bump version: 0.23.3 → 0.23.3.1 2020-12-10 14:37:12 +02:00
Eriks K
94de509026 __all__ definitions 2020-12-10 14:36:34 +02:00
Eriks K
82d913bc47 Bump version: 0.23.2.11 → 0.23.3 2020-12-10 13:26:11 +02:00
Eriks K
c462eac369 Bomb deploy update 2020-12-10 13:25:47 +02:00
Eriks K
d522a4faeb Bump version: 0.23.2.10 → 0.23.2.11 2020-12-09 13:33:26 +02:00
Eriks K
084a47b07a TelegramBot renamed to TelegramReporter 2020-12-09 13:30:18 +02:00
Eriks K
5082855440 Default Telegram's token moved to TelegramReporter 2020-12-09 13:29:28 +02:00
Eriks K
c9971f3570 str.format() -> f-string 2020-12-09 13:28:56 +02:00
Eriks K
c0122eccdf Bump version: 0.23.2.9 → 0.23.2.10 2020-12-08 18:05:21 +02:00
Eriks K
2524173da0 Telegram bot's token can be reused 2020-12-08 18:05:18 +02:00
Eriks K
4a4b991faf Telegram bot's token can be reused 2020-12-08 18:05:01 +02:00
Eriks K
e87c48124c Bump version: 0.23.2.8 → 0.23.2.9 2020-12-07 14:10:52 +02:00
Eriks K
f0f47566a0 Bugfix: Don't travel to renew house if You can't afford the house 2020-12-07 14:10:40 +02:00
Eriks K
f88eaccf67 Bump version: 0.23.2.7 → 0.23.2.8 2020-12-07 14:01:29 +02:00
Eriks K
7be129a781 Quickfix for forbidding Dictatorship/Liberation wars 2020-12-07 14:01:22 +02:00
Eriks K
32546505b9 Bump version: 0.23.2.6 → 0.23.2.7 2020-12-04 13:13:17 +02:00
Eriks K
00ceabf8e3 cleanup 2020-12-04 13:13:10 +02:00
Eriks K
19113da8e6 cleanup 2020-12-04 13:11:11 +02:00
Eriks K
3ad7172925 cleanup 2020-12-04 13:08:42 +02:00
Eriks K
179f1a0892 Only init TelegramReporter if both chat id and token have been supplied 2020-12-04 13:08:16 +02:00
Eriks K
65adf707e2 Only init TelegramReporter if both chat id and token have been supplied 2020-12-04 13:08:04 +02:00
Eriks K
29f9ce5ccc Bump version: 0.23.2.5 → 0.23.2.6 2020-12-02 18:40:41 +02:00
Eriks K
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
Eriks K
c1e8e94cba Bump version: 0.23.2.4 → 0.23.2.5 2020-12-02 13:00:51 +02:00
Eriks K
8133461fd7 pep8 2020-12-02 13:00:33 +02:00
Eriks K
c87333411a Hotfix 2020-12-02 12:56:06 +02:00
Eriks K
d679006b34 small fix 2020-12-01 18:56:14 +02:00
Eriks K
fa308db074 Bump version: 0.23.2.3 → 0.23.2.4 2020-12-01 18:49:35 +02:00
Eriks K
a080163af9 Bugfix 2020-12-01 18:49:33 +02:00
Eriks K
8aa159edf7 Bump version: 0.23.2.2 → 0.23.2.3 2020-12-01 18:49:01 +02:00
Eriks K
1211a98227 Bugfix 2020-12-01 18:48:52 +02:00
Eriks K
734a5ef2b6 Bump version: 0.23.2.1 → 0.23.2.2 2020-12-01 18:11:09 +02:00
Eriks K
5c3b405ca8 Added utils.wait_for_lock decorator to wait for concurrency release 2020-12-01 18:11:00 +02:00
Eriks K
75de8fce96 File closing before return 2020-12-01 15:09:45 +02:00
Eriks K
01e5e44350 File closing before return 2020-12-01 15:07:17 +02:00
Eriks K
500409f74a Bump version: 0.23.2 → 0.23.2.1 2020-12-01 15:01:00 +02:00
Eriks K
9d79bb713c naming bugfix 2020-12-01 15:00:55 +02:00
Eriks K
684b2a6ba4 Bump version: 0.23.1.2 → 0.23.2 2020-12-01 14:22:40 +02:00
Eriks K
447aee8134 Concurrency flag to guard against 2020-12-01 14:22:26 +02:00
Eriks K
64249fc3d9 History 2020-12-01 13:11:45 +02:00
Eriks K
95a78b6e7e Bump version: 0.23.1.1 → 0.23.1.2 2020-12-01 13:02:29 +02:00
Eriks K
b338ea598a Seperated battle finding logic from CitizenMilitary.find_battle_and_fight method 2020-12-01 13:02:10 +02:00
Eriks K
b88e7973e8 Bump version: 0.23.1 → 0.23.1.1 2020-11-30 18:54:44 +02:00
Eriks K
c671425531 Base dmg calculations 2020-11-30 18:54:35 +02:00
Eriks K
08937d06e4 Bump version: 0.23.0 → 0.23.1 2020-11-30 18:20:48 +02:00
Eriks K
cec4510831 Requirement update 2020-11-30 18:20:42 +02:00
Eriks K
cfb9501647 PEP8 2020-11-30 18:16:13 +02:00
Eriks K
d419211955 Get max hit value for divisions on current side 2020-11-30 18:12:36 +02:00
Eriks K
0ea144db17 Added method to get division stats 2020-11-30 17:44:23 +02:00
Eriks K
1418f580cd Spin wheel of fortune updates 2020-11-30 17:43:42 +02:00
Eriks K
49575bddf5 History update for v0.23.0 2020-11-26 18:39:25 +02:00
Eriks K
c874247335 Bump version: 0.22.3.3 → 0.23.0 2020-11-26 18:32:46 +02:00
Eriks K
6e9def4394 Added .get_article(int) and .delete_article(int) methods to CitizenMedia class 2020-11-26 18:32:31 +02:00
Eriks K
d6fbaa7945 Maverick fighting should be explicitly enabled through Citizen.config 2020-11-26 16:16:15 +02:00
Eriks K
eb740c60c7 Bump version: 0.22.3.2 → 0.22.3.3 2020-11-20 18:54:20 +02:00
Eriks K
240c409739 Travel to region bugfix 2020-11-20 18:54:12 +02:00
Eriks K
f348a315c5 Bump version: 0.22.3.1 → 0.22.3.2 2020-11-20 16:57:43 +02:00
Eriks K
7a09eea2b4 CitizenAnniversary.collect_map_quest_node() now accepts argument extra, to collect extra reward 2020-11-20 16:57:36 +02:00
Eriks K
81b1069cf0 Bump version: 0.22.3 → 0.22.3.1 2020-11-20 14:46:04 +02:00
Eriks K
429d43df15 CitizenTasks.work() bugfix: When employer out of money - resign 2020-11-20 14:45:58 +02:00
Eriks K
c81986d65e CitizenEconomy.post_market_offer() bugfix 2020-11-20 14:31:16 +02:00
Eriks K
7bb988f716 PEP8 2020-11-20 14:24:11 +02:00
Eriks K
16cae24712 Bump version: 0.22.2.1 → 0.22.3 2020-11-16 12:12:30 +02:00
Eriks K
3e051fe906 History update 2020-11-16 12:07:20 +02:00
Eriks K
aa9cda9314 eRepublik temp technical difficulties 2020-11-16 12:06:59 +02:00
Eriks K
fc66db8cab Config file generator update 2020-11-16 11:56:37 +02:00
Eriks K
6bbc4f8768 eRepublik has both airplaneRaw and aircraftRaw. Possible future bugs because of this! 2020-11-16 11:56:13 +02:00
Eriks K
160b32a914 Buy market offer directly 2020-11-16 11:52:55 +02:00
Eriks K
a51c3c620e Python round to even bugfix.
`{"stock":10.494533,"consume":10.5}` rounds to 0
2020-11-16 11:52:27 +02:00
Eriks K
a1c26468eb UA updates 2020-11-12 11:47:20 +02:00
Eriks K
4895ae3663 Bump version: 0.22.2 → 0.22.2.1 2020-11-10 15:39:15 +02:00
Eriks K
b8d7cc8d7c Example updates 2020-11-10 15:38:30 +02:00
Eriks K
1d0645b490 Bump version: 0.22.1.5 → 0.22.2 2020-11-09 16:34:52 +02:00
Eriks K
30cf6203b7 History update 2020-11-09 16:34:41 +02:00
Eriks K
a32e88218d CitizenEconomy.get_market_offers now accepts searching tickets and the new aircraft qualities 2020-11-09 16:28:21 +02:00
Eriks K
a031da0ee7 Bump version: 0.22.1.4 → 0.22.1.5 2020-11-04 17:22:44 +02:00
Eriks K
bdb13fa4ae History update, WAM actions are now more verbose 2020-11-04 17:22:35 +02:00
Eriks K
e1e3b33d46 requirement update 2020-11-03 18:10:43 +02:00
Eriks K
e09ca143b1 Bump version: 0.22.1.3 → 0.22.1.4 2020-11-03 14:31:43 +02:00
Eriks K
61d0599295 House renewal bugfix 2020-11-03 14:31:37 +02:00
Eriks K
1ef600492a House renewal bugfix 2020-11-03 14:30:20 +02:00
Eriks K
377eda6445 Inventory booster bugfix 2020-11-03 14:29:42 +02:00
Eriks K
fd13667ca8 Bump version: 0.22.1.2 → 0.22.1.3 2020-10-30 12:58:26 +02:00
Eriks K
0479afcabe Report if out of money to work as manager 2020-10-30 12:58:18 +02:00
Eriks K
486f022f35 Unified Item namig from constants.INDUSTRIES 2020-10-30 12:43:45 +02:00
Eriks K
638373e452 Bump version: 0.22.1.1 → 0.22.1.2 2020-10-27 17:04:40 +02:00
Eriks K
04f357cc70 Bugfix 2020-10-27 17:04:37 +02:00
Eriks K
ed4ffe5af6 Bump version: 0.22.1 → 0.22.1.1 2020-10-22 18:04:13 +03:00
Eriks K
8aa90a7dbf Lint 2020-10-22 18:04:06 +03:00
Eriks K
e798859105 Bump version: 0.22.0 → 0.22.1 2020-10-22 16:27:09 +03:00
Eriks K
241f1642ce Session dump/load, Citizen.inventory update, memory and network optimization, python3.6 support 2020-10-22 16:26:59 +03:00
Eriks K
a4128b5d89 Bump version: 0.21.5.8 → 0.22.0 2020-10-21 14:46:24 +03:00
Eriks K
22c2a0ffd2 New Features!
Dump session to file!
Load session from file!
2020-10-21 14:45:29 +03:00
Eriks K
38f0335354 Bump version: 0.21.5.7 → 0.21.5.8 2020-10-07 09:22:01 +03:00
Eriks K
889435b94e House renewal bugfix 2020-10-07 09:21:36 +03:00
Eriks K
bb16c27674 Bump version: 0.21.5.6 → 0.21.5.7 2020-10-06 12:35:19 +03:00
Eriks K
963d7ca11a bugfix 2020-10-06 12:35:14 +03:00
Eriks K
36c7fefdf7 Bump version: 0.21.5.5 → 0.21.5.6 2020-09-30 08:40:14 +03:00
Eriks Karls
d9fa30b06e bugfix in default weapon switch 2020-09-30 08:35:35 +03:00
Eriks Karls
b53dc447f4 switch to deploy 2020-09-30 08:13:36 +03:00
Eriks K
233d8d83f8 Bump version: 0.21.5.4 → 0.21.5.5 2020-09-29 18:02:52 +03:00
Eriks K
ec62d90aa2 wheeloffortune bugfix 2020-09-29 18:02:52 +03:00
Eriks K
0c433a56da Bump version: 0.21.5.3 → 0.21.5.4 2020-09-29 17:38:44 +03:00
Eriks K
ad24338f4d wheeloffortune bugfix 2020-09-29 17:38:26 +03:00
Eriks K
6f4bc65d1b Bump version: 0.21.5.2 → 0.21.5.3 2020-09-29 17:21:00 +03:00
Eriks K
cc09ba7ee7 wheeloffortune argument bugfix 2020-09-29 17:20:53 +03:00
Eriks K
9e1166a460 Bump version: 0.21.5.1 → 0.21.5.2 2020-09-29 17:17:07 +03:00
Eriks K
fb0042c00d wheeloffortune url bugfix 2020-09-29 17:17:00 +03:00
Eriks K
bb800578e7 Bump version: 0.21.5 → 0.21.5.1 2020-09-29 15:06:39 +03:00
Eriks K
7025f750dc PySocks as requirement 2020-09-29 15:06:30 +03:00
Eriks K
bf77f21b60 MyCompanies export as dict optimised 2020-09-29 15:04:51 +03:00
Eriks K
6b7639d7fb Bump version: 0.21.4.8 → 0.21.5 2020-09-29 10:52:36 +03:00
Eriks K
3b1c1928fd Added proxy support 😉 2020-09-29 10:52:14 +03:00
Eriks K
2e26c2db79 Bump version: 0.21.4.7 → 0.21.4.8 2020-09-25 10:10:33 +03:00
Eriks K
78c055fee2 bugfix 2020-09-25 10:10:27 +03:00
Eriks K
fe41c4cdc6 Bump version: 0.21.4.6 → 0.21.4.7 2020-09-23 13:22:38 +03:00
Eriks K
123b6cf4ed Fight reporting unified and moved to Reporter.report_fighting 2020-09-23 13:22:31 +03:00
Eriks K
f652b02443 Fight reporting duplicate 2020-09-23 13:17:46 +03:00
Eriks K
73537e4742 Bump version: 0.21.4.5 → 0.21.4.6 2020-09-23 11:15:32 +03:00
Eriks K
955432e0d2 Check also for maintenance in BaseCitizen._errors_in_response() 2020-09-23 11:15:18 +03:00
Eriks K
1d93864dca Bump version: 0.21.4.4 → 0.21.4.5 2020-09-22 16:30:03 +03:00
Eriks K
c472d688be error logging 2020-09-22 16:29:50 +03:00
Eriks K
bff9a2bec9 Bump version: 0.21.4.3 → 0.21.4.4 2020-09-21 20:40:40 +03:00
Eriks K
973ea40e00 bugfix 2020-09-21 20:40:24 +03:00
Eriks K
52c85fdf28 Bump version: 0.21.4.2 → 0.21.4.3 2020-09-21 20:18:02 +03:00
Eriks K
a889e9efa1 bugfix 2020-09-21 20:17:56 +03:00
Eriks K
a9a0cdc6d5 Bump version: 0.21.4.1 → 0.21.4.2 2020-09-21 12:39:44 +03:00
Eriks K
1c102488b6 Test fix to not run if WC end is near 2020-09-21 12:39:27 +03:00
Eriks K
c38acef2a0 Bump version: 0.21.4 → 0.21.4.1 2020-09-21 11:34:59 +03:00
Eriks K
48b5e473aa doc generation bugfixes 2020-09-21 11:34:50 +03:00
Eriks K
7fadeb1a49 Bump version: 0.21.3 → 0.21.4 2020-09-21 11:26:51 +03:00
Eriks K
b723660f23 Fixups and type checks 2020-09-21 11:26:32 +03:00
Eriks K
f10eeec498 requirement update 2020-09-21 11:24:01 +03:00
Eriks K
230167f93d mypy bugfix 2020-09-21 11:23:43 +03:00
Eriks K
d5ed989e80 Bump version: 0.21.2.2 → 0.21.3 2020-08-18 16:37:27 +03:00
Eriks K
6fc24b8adf requirements 2020-08-18 16:37:07 +03:00
Eriks K
cf797f2f60 Fixes and updates 2020-08-18 13:14:41 +03:00
Eriks K
ad29045ace Bump version: 0.21.2.1 → 0.21.2.2 2020-07-29 11:40:41 +03:00
Eriks K
c919e46af5 PoliticsAPI extended and bugfixed 2020-07-29 11:40:32 +03:00
Eriks K
644b4d70e1 Bump version: 0.21.2 → 0.21.2.1 2020-07-29 11:23:31 +03:00
Eriks Karls
6dbbd054ba bugfix 2020-07-29 09:27:02 +03:00
Eriks Karls
0ee952e504 bugfix 2020-07-29 09:21:49 +03:00
Eriks K
bb9b198a53 Bump version: 0.21.1 → 0.21.2 2020-07-28 19:34:19 +03:00
Eriks K
cb22e631ca Merge branch 'memory-optimisation'
* memory-optimisation:
  Company cleanup optimisation
  JSON.dump sort_keys parameter throwing mysterious errors
  Fixed memory leak in Battle and MyCompanies classes
2020-07-28 19:33:52 +03:00
Eriks K
c43e20c8f6 Return all Non-Terrain divisions and their bh damage 2020-07-28 19:33:30 +03:00
Eriks K
c8f41b97af Company cleanup optimisation 2020-07-28 19:25:22 +03:00
Eriks K
d483bcbcb9 JSON.dump sort_keys parameter throwing mysterious errors 2020-07-28 18:29:25 +03:00
Eriks K
a316f277fb Fixed memory leak in Battle and MyCompanies classes 2020-07-28 18:28:03 +03:00
Eriks K
e8c81d17e6 Weapons kind should be singular - 'weapon' 2020-07-19 07:56:15 +03:00
Eriks K
edb4d8851b Bump version: 0.21.0.3 → 0.21.1 2020-07-16 11:15:04 +03:00
Eriks K
76edd6bb7d Update traveling to incorporate travel cooldown (1travel / 15sec) 2020-07-16 11:14:35 +03:00
Eriks K
8da376b852 Change variable names to be more precise if argument must be and id or instance (battle/battle_id, division/division_id) 2020-07-16 11:13:29 +03:00
Eriks K
b6b0be7442 Minimize error reporting locals to not duplicate instance.json data 2020-07-16 11:11:35 +03:00
Eriks K
ed434b605d Refactor '.format()' calls to f-strings 2020-07-16 11:08:31 +03:00
Eriks K
2bd311def6 Dev requirement update 2020-07-14 15:37:59 +03:00
Ēriks Karls
4b437c2ba6
Update README.rst 2020-07-14 14:11:20 +03:00
Ēriks Karls
cc8a1450d4
Merge pull request #3 from codacy-badger/codacy-badge
Add a Codacy badge to README.rst
2020-07-14 14:08:45 +03:00
The Codacy Badger
0c9f3756ea Add Codacy badge 2020-07-14 11:07:36 +00:00
Eriks K
9de07950b8 Bump version: 0.21.0.2 → 0.21.0.3 2020-07-13 11:24:29 +03:00
Eriks Karls
766b7b2d95 bugfix 2020-07-12 10:45:44 +03:00
Eriks K
c730981865 Bump version: 0.21.0.1 → 0.21.0.2 2020-07-11 10:21:21 +03:00
Eriks K
d70c3e2c9e Typos 2020-07-11 10:21:12 +03:00
Eriks K
d044af6d2f Bump version: 0.21.0 → 0.21.0.1 2020-07-11 10:08:09 +03:00
Eriks K
dd75b10d2f missing __hash__ 2020-07-11 09:54:23 +03:00
Eriks K
a45dd7e0c3 Bump version: 0.20.4 → 0.21.0 2020-07-11 09:39:25 +03:00
Eriks K
316a826c93 Merge branch 'master' of github.com:eeriks/erepublik 2020-07-11 09:38:20 +03:00
Eriks Karls
c9710733e2 Typo 2020-07-11 09:37:22 +03:00
Eriks K
9e678d6b51 Bump version: 0.20.3.11 → 0.20.4 2020-07-10 17:13:15 +03:00
Eriks K
88517cb076 Bugfix 2020-07-10 17:09:28 +03:00
Eriks K
01c8aef012 Bugfix: South Africa link 2020-07-10 17:08:58 +03:00
Eriks K
d7ac66bd69 Bump version: 0.20.3.10 → 0.20.3.11 2020-07-10 12:16:53 +03:00
Eriks K
e8739caca1 fix 2020-07-10 12:16:18 +03:00
Eriks K
9df9c1cd87 Bump version: 0.20.3.9 → 0.20.3.10 2020-07-10 00:47:42 +03:00
Eriks K
1b004f163a bugfix 2020-07-10 00:47:36 +03:00
Eriks K
72bd2787d3 Bump version: 0.20.3.8 → 0.20.3.9 2020-07-09 19:11:04 +03:00
Eriks K
bfa6cb1e78 bugfix 2020-07-09 19:10:55 +03:00
Eriks K
1db7c98b47 Bump version: 0.20.3.7 → 0.20.3.8 2020-07-09 18:51:09 +03:00
Eriks K
1f21a71c74 bugfix 2020-07-09 18:51:00 +03:00
Eriks K
0531c8997b Bump version: 0.20.3.6 → 0.20.3.7 2020-07-09 17:42:24 +03:00
Eriks K
5a8a0a3920 mavericks 2020-07-09 17:42:15 +03:00
Eriks K
d938908986 Bump version: 0.20.3.5 → 0.20.3.6 2020-07-09 14:42:51 +03:00
Eriks K
ffbbd25e54 Code cleanup 2020-07-09 14:42:40 +03:00
Eriks K
81bd09e13e Bump version: 0.20.3.4 → 0.20.3.5 2020-07-09 08:53:45 +03:00
Eriks K
fc4295d8bd bugix 2020-07-09 08:53:32 +03:00
Eriks K
7bbf7cb34a Bump version: 0.20.3.3 → 0.20.3.4 2020-07-09 08:46:19 +03:00
Eriks K
8ce56a31f7 bugix 2020-07-09 08:46:12 +03:00
Eriks K
ef23b3b8db Bump version: 0.20.3.2 → 0.20.3.3 2020-07-09 08:34:41 +03:00
Eriks K
cd861ea29b bugfix 2020-07-09 08:34:38 +03:00
Eriks K
5b580f7c79 Bump version: 0.20.3.1 → 0.20.3.2 2020-07-09 08:27:55 +03:00
Eriks K
0061503581 passed int instead of Country to travel 2020-07-09 08:27:48 +03:00
Eriks K
c78dbae925 Merge branch 'master' of github.com:eeriks/erepublik 2020-07-09 08:25:10 +03:00
Eriks Karls
6a9b574454 class serialization 2020-07-09 08:09:58 +03:00
Eriks K
cde97c6259 Bump version: 0.20.3 → 0.20.3.1 2020-07-08 19:30:47 +03:00
Eriks K
7fa02be7d3 bugfix 2020-07-08 19:30:41 +03:00
Eriks K
098bfe5f3f Bump version: 0.20.2.2 → 0.20.3 2020-07-08 18:28:56 +03:00
Eriks K
c26689a8fa Start hunting empty medals from oldest battles 2020-07-08 18:28:20 +03:00
Eriks K
94c416faa5 Battle, BattleSide, BattleDivision repr and str definitions 2020-07-08 18:27:48 +03:00
Eriks K
ba840765be Moving away from passing object ids around and towards passing objects and even using object functions 2020-07-08 17:30:22 +03:00
Eriks K
3927b9ba6b Bump version: 0.20.2.1 → 0.20.2.2 2020-07-06 15:52:42 +03:00
Eriks K
cd5c0b161f Switch to Fight mode if deployed is active 2020-07-06 15:52:37 +03:00
Eriks K
39defc0fd7 Bump version: 0.20.2 → 0.20.2.1 2020-07-04 12:28:09 +03:00
Eriks K
6c8dbcd99e Updated fighting division chooser 2020-07-04 12:28:02 +03:00
Eriks K
d07334f602 Bump version: 0.20.1.7 → 0.20.2 2020-07-04 00:31:33 +03:00
Eriks K
bcb27da54f Summer challange shanged battle list API 2020-07-04 00:31:26 +03:00
Eriks Karls
7ac22b5e11 Bump version: 0.20.1.6 → 0.20.1.7 2020-07-03 23:56:07 +03:00
Eriks Karls
5bd3d72a63 Quickfix for summer terrains 2020-07-03 23:54:32 +03:00
Eriks K
33d2c641df Config.json creator HTML 2020-07-03 15:32:13 +03:00
Eriks K
d1e078e443 Bump version: 0.20.1.5 → 0.20.1.6 2020-06-29 14:58:27 +03:00
Eriks K
71c64b0cf5 minor tweaks 2020-06-29 14:57:40 +03:00
Eriks K
3b5b15553d Bomb deploy bugfix where deploying in RW could leave player stranded in non residency region. Battle update bugfix where self.all_battles could be empty while re-parsing every battle 2020-06-29 14:48:37 +03:00
Eriks K
d077e10f15 Bump version: 0.20.1.4 → 0.20.1.5 2020-06-26 17:27:11 +03:00
Eriks K
8eb5235f12 Deploy bombs in RWs bugfix 2020-06-26 17:27:02 +03:00
Eriks K
a873d223c1 Bump version: 0.20.1.3 → 0.20.1.4 2020-06-25 14:18:41 +03:00
Eriks K
ed96143c80 Flake8
Battle.div is no longer a defaultdict, but should raise error if trying to access division which isn't assigned
2020-06-25 14:17:05 +03:00
Eriks K
b49e4ab594 Bump version: 0.20.1.2 → 0.20.1.3 2020-06-19 13:44:39 +03:00
Eriks K
2f69090c03 bugfix 2020-06-19 13:44:33 +03:00
Eriks K
df170048af Bump version: 0.20.1.1 → 0.20.1.2 2020-06-19 13:37:02 +03:00
Eriks K
8ca845cf17 Add damage amount to inventory bomb 2020-06-19 13:36:45 +03:00
Eriks K
ce7874fbf5 Bump version: 0.20.1 → 0.20.1.1 2020-06-18 10:14:50 +03:00
Eriks K
6abfc98fbd Test requirements 2020-06-18 10:13:55 +03:00
Eriks K
66f0e648df Citizen.to_json() bugfixed and optimised 2020-06-18 10:10:22 +03:00
Eriks K
7cf6cf0e12 Bump version: 0.20.0 → 0.20.1 2020-06-16 17:00:09 +03:00
Eriks K
a825917a98 WAM/Employ bugfix
Company sorting bugfix
2020-06-16 16:59:56 +03:00
Eriks K
603604213d Requirement update 2020-06-15 16:22:53 +03:00
Eriks K
f83df449ae Merge commit '98947e6bbe6feda9f80d630b54c132fa2d5a5949' into v0.20.0
* commit '98947e6bbe6feda9f80d630b54c132fa2d5a5949':
  Update pythonpackage.yml
  Create pythonpackage.yml
2020-06-15 16:03:02 +03:00
Eriks K
b480ed7230 Companies and holdings created as python objects from Dicts 2020-06-15 16:02:36 +03:00
Eriks K
67677f356f eRepublik updated contributions endpoint 2020-06-15 15:59:03 +03:00
Eriks K
ff869e0403 Bomb deploy bugfix 2020-06-15 15:47:48 +03:00
Eriks K
845cd8d174 Bugfix - auto buy raw bought all available items in offer, not the required amount 2020-06-01 11:11:48 +03:00
Eriks K
4e337177f2 Restricted IP check 2020-05-25 12:30:03 +03:00
Eriks K
eed244deb5 Article commenting bugfix 2020-05-19 14:03:41 +03:00
Eriks K
1e7c9a395e Article publishing bugfix 2020-05-19 13:56:27 +03:00
Eriks K
7aa353bc06 minor bugfix 2020-05-15 09:52:49 +03:00
Eriks K
6642839af5 WAM failed when added employee work unit count is less than available 2020-05-15 09:26:10 +03:00
Eriks K
c216d98287 'dict' object has no attribute 'json' 2020-05-15 09:17:56 +03:00
Eriks K
588475d554 NoneType object has no method get 2020-05-14 17:15:11 +03:00
Eriks K
ab3ce2b8c3 tuple indices must be integers or slices, not str 2020-05-14 16:51:55 +03:00
Eriks K
a208c8bcf0 Object of type Response is not JSON serializable 2020-05-14 16:41:18 +03:00
Eriks K
09c6255b84 Tests updated 2020-05-14 15:01:06 +03:00
Eriks K
ff2a0e02dc Example updates 2020-05-14 14:54:25 +03:00
Eriks K
5712007e3f Typos 2020-05-14 14:30:02 +03:00
Eriks K
f64a9dc709 eRepublik has moved citizen contributions to different campaign list endpoint 2020-05-14 14:07:30 +03:00
Eriks K
4cfe25b0b1 Allways add eRepublik commit id to bug reports and differentiate between callers and own commit ids 2020-05-14 13:54:37 +03:00
Eriks K
c094ef26b4 Flake8 2020-05-14 12:45:57 +03:00
Eriks K
3cac1cf041 Script will log actions done more verbosely to be more transparent about done actions 2020-05-14 12:29:14 +03:00
Eriks K
ac4fc9baab Less intrusive reporting 2020-05-14 12:11:39 +03:00
Eriks K
b760a2f66c TypeHinting 2020-05-14 11:12:34 +03:00
Eriks K
5c47b70ea6 Wrong offer assignment, resulting in returned data of newest offers not cheapest 2020-05-12 14:19:10 +03:00
Eriks K
05964f6c58 CitizenEconomy.get_market_offers() fixed 2020-05-12 13:45:32 +03:00
Eriks Karls
d95c472ede Donation rate limit 2020-04-30 15:11:55 +03:00
Eriks Karls
49726b8cec __all__ list extended, created function for error catching and processing, error processing minor fix 2020-04-29 10:53:38 +03:00
Eriks Karls
9a0cbf77da Citizen.work_as_manager return types normalized and documented 2020-04-29 10:52:07 +03:00
Eriks Karls
904fd4efc8 Moved utils.report_promo to classes.Reporter.report_promo 2020-04-23 14:39:42 +03:00
Eriks Karls
1bbe72f3e1 Fixed imports 2020-04-23 14:32:13 +03:00
Eriks Karls
2aa1cbd79e PEP8 2020-04-23 14:28:26 +03:00
Eriks Karls
2eecd9fd4d Fixed error preventing wam in second holding if employees had been assigned in the first request 2020-04-23 14:16:33 +03:00
Eriks Karls
2efc9496a0 Fighting also available in other divisions for Maverick players 2020-04-14 18:56:45 +03:00
Eriks Karls
1b5b5f736f Multiple achievements are joined under one notification - use achievementAmount for count 2020-03-27 10:31:39 +02:00
Eriks Karls
4b4ed18cdb Award posting bugfix 2020-03-23 14:38:54 +02:00
Eriks Karls
d928ffc8df log message formatting 2020-03-05 13:05:37 +02:00
Eriks Karls
260344bcbe fix 2020-03-05 10:00:22 +02:00
Eriks Karls
d4a3719645 telegram queue never cleared 2020-03-04 11:34:03 +02:00
Ēriks Karls
98947e6bbe
Update pythonpackage.yml 2020-03-03 19:18:33 +02:00
Ēriks Karls
24d81bbadf
Create pythonpackage.yml 2020-03-03 19:16:19 +02:00
Eriks Karls
93f2f2887f Added wheel of fortune endpoints, restructured work as manager, limiting log row length to 120 characters 2020-03-03 13:07:39 +02:00
Eriks Karls
7ec15a9645 New endpoints opened 2020-02-27 18:25:35 +02:00
Eriks Karls
e0c09672b1 call to super must include email and password 2020-02-27 18:25:04 +02:00
Eriks Karls
d6a0d5a704 BaseCitizen should have email and password at initialization to be able to login 2020-02-27 18:23:59 +02:00
Eriks Karls
22dc18d80d Leaderboards are now directly available from Citizen instance 2020-02-27 16:03:58 +02:00
Eriks Karls
772c09a2af Citizen.eat() and Citizen.eat_eb() were lost after restructuring 2020-02-27 08:23:27 +02:00
Eriks Karls
bf5f3d74b3 Remove depricated Config.wt property
Add Config.ot for overtime enabling/disabling
2020-02-26 18:43:27 +02:00
Eriks Karls
70e78023eb Sorted inheritance 2020-02-26 18:42:42 +02:00
Eriks Karls
8dcebdecd2 no message 2020-02-26 18:42:25 +02:00
Eriks Karls
77bcfb3df6 Move commit id to __init__ 2020-02-26 18:41:56 +02:00
Eriks Karls
a01b85154f Bump version: 0.19.4.2 → 0.20.0 2020-02-26 18:29:26 +02:00
Eriks Karls
04bb0be837 Modularity improvements 2020-02-26 18:19:56 +02:00
Eriks Karls
62e265e7e1 COMMIT_ID updater 2020-02-26 18:18:47 +02:00
Eriks Karls
dd2c20cc41 no message 2020-02-26 12:09:45 +02:00
Eriks Karls
b76ea2c4ae Medal notifications 2020-02-26 12:09:36 +02:00
Eriks Karls
b7211b7c75 Notification parsing and deleting 2020-02-26 12:09:03 +02:00
Eriks Karls
07ba1795d3 WIP: Splitting Citizen class into logical pieces for encapsulation, modularity and easier maintenance 2020-02-24 17:20:16 +02:00
Eriks Karls
ce02a39158 Move eRepublik API helper class to seperate file and split up class into logical pieces 2020-02-24 17:19:06 +02:00
Eriks Karls
a5dfc07018 Bump version: 0.19.4.1 → 0.19.4.2 2020-02-20 12:43:51 +02:00
Eriks Karls
e6ce02fc09 requirement update 2020-02-20 12:37:15 +02:00
Eriks Karls
149071ae86 Bump version: 0.19.4 → 0.19.4.1 2020-02-20 12:32:13 +02:00
Eriks Karls
72375f40ca Dev versioning 2020-02-20 09:58:03 +02:00
Eriks Karls
fd1880c50f Some notifications are still being displayed the old way 2020-02-19 18:52:40 +02:00
Eriks Karls
f73f2b7b9f Bump version: 0.19.3 → 0.19.4 2020-02-18 20:12:02 +02:00
Eriks Karls
f6433908b4 New notifications API 2020-02-18 20:11:34 +02:00
Eriks Karls
2fd317153f Bump version: 0.19.2 → 0.19.3 2020-01-27 01:54:03 +02:00
Eriks Karls
256a180bd6 if error occures on main thread - simplify error logging 2020-01-27 01:53:52 +02:00
Eriks Karls
c7dbeb2078 Bump version: 0.19.1 → 0.19.2 2020-01-26 20:45:18 +02:00
Eriks Karls
8e5ae0320a Merge branch 'bugfix'
* bugfix:
  Hey Plato! If You're reading this - fix your variable types and there will be 90% less bugs in Your code!!!
  Some TelegramBot tweaks
  when full energy update citizen info would stop working because trying to get timedelta from now - _last_full_energy_report
  Too broad exception was cought without notifying about actual error - when Telegram isn't enabled
2020-01-26 20:44:43 +02:00
Eriks Karls
5c258d7aae Hey Plato! If You're reading this - fix your variable types and there will be 90% less bugs in Your code!!!
{'weaponId': 6, 'weaponQuantity': 0, 'damage': 120}
{'weaponId': '7', 'weaponQuantity': 1185, 'inClip': 7, 'damage': 200}
{'weaponId': 10, 'weaponQuantity': 0, 'damage': 100}
2020-01-26 20:44:21 +02:00
Ēriks Karls
75b43fc455
Merge pull request #2 from eeriks/eeriks-patch-1
GHSA-7fcj-pq9j-wh2r
2020-01-17 15:41:02 +02:00
Ēriks Karls
2362dc51e8
GHSA-7fcj-pq9j-wh2r
https://github.com/advisories/GHSA-7fcj-pq9j-wh2r
2020-01-17 15:40:34 +02:00
Eriks Karls
a2cf479135 Some TelegramBot tweaks 2020-01-16 13:49:24 +02:00
Eriks Karls
00b87dc832 when full energy update citizen info would stop working because trying to get timedelta from now - _last_full_energy_report 2020-01-14 13:43:10 +02:00
Eriks Karls
0dd1ae9ac5 Too broad exception was cought without notifying about actual error - when Telegram isn't enabled 2020-01-14 13:42:34 +02:00
Eriks Karls
76bd40c655 Bump version: 0.19.0 → 0.19.1 2020-01-13 21:33:58 +02:00
Eriks Karls
15e6deebda Battle initialization without valid data should be avoided to not run into strange and hard to trace bugs.
Jsonification updates - if simplejson is available some packages are importing simplejson with try-except and later throwing simplejson errors which should be cought when calling .json() on every request.
Fixed error logging
2020-01-13 21:33:50 +02:00
Eriks Karls
69d0e7df0a Bump version: 0.18.3 → 0.19.0 2020-01-13 10:40:33 +02:00
Eriks Karls
4f92894ab6 DocUpdate 2020-01-13 10:40:22 +02:00
Eriks Karls
9c64bfac0f Merge branch 'inventory_updates'
* inventory_updates:
  Python 3.8, isort, requirement update
  Representation of Citizen class
  Created method for current products on sale. Updated inventory to also include products on sale

# Conflicts:
#	erepublik/citizen.py
2020-01-13 10:31:05 +02:00
Eriks Karls
1f07f2e270 Update:
Citizen.set_default_weapon() - eRepublik should return list with all available weapon qualities, but when a battle is just launched, they return only dict with barehands
Citizen.fight() - no longer calls self.set_default_weapon()
Citizen.find_battle_and_fight() - now calls self.set_default_weapon() just before fighting
Citizen.update_war_info() - returns previous battle list if responses 'last_updated' isn't more than 30s old

New:
Citizen.get_battle_for_war(war_id) - returns Battle instance for specific war, if battle is active for given war
2020-01-13 10:28:42 +02:00
Eriks Karls
71d204843d Python 3.8, isort, requirement update 2020-01-09 12:03:11 +02:00
Eriks Karls
d9305214eb Representation of Citizen class 2020-01-07 19:55:31 +02:00
Eriks Karls
5556d5f772 Created method for current products on sale. Updated inventory to also include products on sale 2020-01-07 16:28:42 +02:00
Eriks Karls
1c47d169d2 Bump version: 0.18.2 → 0.18.3 2020-01-07 11:32:59 +02:00
Eriks Karls
ef44787bad make clean-pyc removes log/ and debug/ run 'artifacts' 2020-01-07 11:32:48 +02:00
Eriks Karls
42431134e1 UA update 2020-01-07 11:30:40 +02:00
Eriks Karls
bedaeeefd1 Battle division update 2020-01-07 11:15:40 +02:00
Eriks Karls
bbf304aa99 Bump version: 0.18.1 → 0.18.2 2020-01-05 10:53:39 +02:00
Eriks Karls
a2447959e7 fight must receive battle id as int, added warnings support 2020-01-05 10:53:26 +02:00
Eriks Karls
700bd8d98e Bump version: 0.18.0 → 0.18.1 2020-01-02 22:43:01 +02:00
Eriks Karls
3599dc40fc More logging, Citizen.get_raw_surplus() fixed and moved to Citizen.my_companies.get_wam_raw_usage() 2020-01-02 22:42:40 +02:00
Eriks Karls
6ba727a781 promo spam loop 2020-01-02 18:49:38 +02:00
Eriks Karls
7f1829a5d7 Bump version: 0.17.3 → 0.18.0 2019-12-18 16:26:10 +02:00
Eriks Karls
e374aa8a54 Implemented division switching,
improved multi bomb deploy with auto traveling,
Citizen.fight() simplified battle data gathering logic -> Citizen.shoot logic improved
Citizen.all_battles are now defaultdict with default value of empty/invalid battle, for times when trying to do things with battle which is not in all_battle dict
2019-12-18 16:25:52 +02:00
Eriks Karls
7edfa3b004 Bump version: 0.17.2 → 0.17.3 2019-12-18 11:45:02 +02:00
Eriks Karls
12aee23739 Variable and method redeclaration 2019-12-18 11:44:18 +02:00
Eriks Karls
b7f8182ef5 Bump version: 0.17.1 → 0.17.2 2019-12-13 19:32:29 +02:00
Eriks Karls
39093accd0 Type hinting. Class parameter defined lists where shared accross instances. 2019-12-13 19:30:43 +02:00
Eriks Karls
aba8c15fd3 AutoPost medals 2019-12-03 15:22:35 +02:00
Eriks Karls
f294506a2d Updated wars list, added default weapon choosing (q7 - ground, bare hands - air) 2019-12-03 09:52:53 +02:00
Eriks Karls
fd56c6c389 By default sort battles by time 2019-12-03 09:44:14 +02:00
Eriks Karls
4f613ee5ac remvoed unused variables 2019-12-03 09:43:55 +02:00
Eriks Karls
a7dd528597 Citizen get() and post() signature update, check if server isn't compaining about request flooding 2019-12-03 09:42:51 +02:00
Eriks Karls
24c755d414 code style 2019-12-03 09:41:07 +02:00
Eriks Karls
13b639dc5a Bump version: 0.17.0 → 0.17.1 2019-11-21 14:04:49 +02:00
Eriks Karls
ec1141a46e set serialization 2019-11-21 14:04:43 +02:00
Eriks Karls
77170433c2 Bump version: 0.16.1 → 0.17.0 2019-11-21 11:12:49 +02:00
Eriks Karls
4736f70203 History update 2019-11-21 11:12:43 +02:00
Eriks Karls
48a27997ac Bump version: 0.16.0 → 0.16.1 2019-11-21 11:10:15 +02:00
Eriks Karls
90bec82630 12th anniversary minimal methods 2019-11-21 11:06:29 +02:00
Eriks Karls
aedfbf4465 12th anniversary endpoints 2019-11-21 10:42:55 +02:00
Eriks Karls
66f459c692 Inventory structure update 2019-10-30 19:42:05 +02:00
Eriks Karls
ef27960ff1 no message 2019-10-30 19:35:40 +02:00
Eriks Karls
c48af9a891 Thread stopping 2019-10-30 18:16:18 +02:00
Eriks Karls
1abfdb71ac Code cleanup and serialization improvements 2019-10-30 16:55:33 +02:00
Eriks Karls
e060f67666 More inventory structure updates 2019-10-29 16:06:07 +02:00
Eriks Karls
06d8d1c0b5 Telegram threading queue has been messing with error reporting 2019-10-29 16:05:22 +02:00
Eriks Karls
adda8dcb54 Structure requests by year/month/date folders, to keep requests in cleaner format.
The same medal kind (Maverick div BHs) can have different reward value - group them by kind-reward.
Citizen.post bugfix (with no data and json arguments) TODO: Must check where post is called without data or json
2019-10-28 14:17:45 +02:00
Eriks Karls
c7f084436d Inventory structure update 2019-10-18 18:18:39 +03:00
Eriks Karls
94a87091a4 Allow full energy reports once every half an hour 2019-10-17 19:15:17 +03:00
Eriks Karls
c0b97f112d TelegramBot.send_message should always append to send queue 2019-10-16 15:10:38 +03:00
Eriks Karls
3d895bd085 Damage calculation 2019-10-16 15:09:28 +03:00
Eriks Karls
d548d1bbf1 Hit calculation can be static 2019-10-15 20:03:38 +03:00
Eriks Karls
b1eefcc662 CSRF Attack Detecked loop on POST requests. 2019-10-15 20:03:22 +03:00
Eriks Karls
41798c446c Return successfully transfered item count 2019-10-15 11:28:56 +03:00
Eriks Karls
074da3adbe Telegram reporter queue bug 2019-10-14 19:21:00 +03:00
Eriks Karls
6c9a9e920d Delay telegram notification sending by appending multiple messages to queue and after minute of inactivity clear the queue by sending all messages 2019-10-14 13:44:31 +03:00
Eriks Karls
ffa2fc109c Travel for fighting fixed 2019-10-14 13:14:30 +03:00
Eriks Karls
f7f4028f32 Revert "Travel for fighting"
This reverts commit 07c88810923b7051e997db746076a9e18656c0f5.
2019-10-14 13:03:36 +03:00
Eriks Karls
e91705ce90 no message 2019-10-14 13:03:19 +03:00
Eriks Karls
ca65a1ffe1 iPython indexer infinite loop and crash 2019-10-14 12:54:59 +03:00
Eriks Karls
07c8881092 Travel for fighting 2019-10-13 23:50:45 +03:00
Eriks Karls
25e534f783 Always print should_fight message 2019-10-08 09:32:00 +03:00
Eriks Karls
daa071f0f5 RTD build image url fixed, setup.py typo 2019-10-08 09:31:09 +03:00
Eriks Karls
2f8120bd0d Telegram formatting 2019-10-01 09:59:37 +03:00
Eriks Karls
6b2c073abe Damage booster activisation update and bugfix 2019-10-01 09:59:22 +03:00
Eriks Karls
c298d66086 Don't allow to fight before WeekChange even if force_fight (levelup, 75pp etc) 2019-10-01 09:58:37 +03:00
Eriks Karls
bf971972bf History update with version number 2019-09-29 09:47:15 +03:00
Eriks Karls
50f3d291ca Bump version: 0.15.3 → 0.16.0 2019-09-29 09:45:46 +03:00
Eriks Karls
b03140f2b8 RTD build image url fixed, setup.py typo 2019-09-29 09:42:24 +03:00
Eriks Karls
fe9a118875 Telegram loop traceback formatting
Fixed double code execution in Citizen.update_citizen_info(None) calling Citizen.update_citizen_info(html) and executing code twice.
2019-09-29 09:40:25 +03:00
Eriks Karls
17c73c79a7 Trying to find how and where Telegram spam happens 2019-09-28 16:03:31 +03:00
Eriks Karls
7533608316 Version updates 2019-09-27 13:51:09 +03:00
Eriks Karls
71a7f55338 Changes in eRepublik html 2019-09-27 11:50:51 +03:00
Eriks Karls
0ca0f49f92 bugfix 2019-09-27 11:20:00 +03:00
Eriks Karls
3f1b0018b2 Misplaced telegram queueing 2019-09-27 11:11:18 +03:00
Eriks Karls
2aaa4aad65 cleanup 2019-09-27 11:03:22 +03:00
Eriks Karls
b44a3a4b62 Init update 2019-09-27 11:03:02 +03:00
Eriks Karls
3a7dd9a6fa Init update 2019-09-27 11:02:26 +03:00
Eriks Karls
630c7cbc76 Was trying to send messages before citizen has been initialized 2019-09-27 10:26:53 +03:00
Eriks Karls
1ee47dfdcf Telegram integration 2019-09-26 15:53:21 +03:00
Eriks Karls
ab34135b73 Telegram integration 2019-09-26 15:44:21 +03:00
Eriks Karls
20bba4b9f9 Telegram integration 2019-09-26 15:42:16 +03:00
Eriks Karls
8db4ab1f0f Company sorting for wam: Raw factories, food, weapon, house, air, q7...q1, Final factories, food, weapon, house, air, q7...q1 2019-09-25 09:38:27 +03:00
Eriks Karls
acbf1590d7 If not enough food for wam - buy food and wam 2019-09-25 09:36:42 +03:00
Eriks Karls
63dd2d4f77 Optimisation and test cleanup 2019-09-05 15:36:01 +03:00
Eriks Karls
896b1b2432 Bugfix, cleanup and optimisation 2019-09-05 15:01:54 +03:00
Eriks Karls
60543e02c8 Merge branch 'master' into tests
* master:
  Update citizen.py
  Update citizen.py
2019-09-03 11:56:00 +03:00
Eriks Karls
b06e9a2933 Merge branch 'master' of github.com:eeriks/erepublik_script
* 'master' of github.com:eeriks/erepublik_script:
  Update citizen.py
  Update citizen.py
2019-09-03 11:55:44 +03:00
Eriks Karls
4eb96d7f1e Testing should travel and should fight 2019-09-03 11:55:34 +03:00
Ēriks Karls
c8a1d8c8e8
Update citizen.py
If not employee find new job
2019-09-01 19:58:52 +03:00
Ēriks Karls
c85f0417f2
Update citizen.py
Apply for work bugfix
2019-09-01 19:47:17 +03:00
Eriks Karls
7573f29950 Freshening up documentation 2019-08-28 17:16:46 +03:00
Eriks Karls
597d27117c Donating money now return status true or false 2019-08-27 11:16:36 +03:00
Eriks Karls
95570b7c17 Article voting and endorsing 2019-08-27 11:03:08 +03:00
Eriks Karls
bc4ba671d6 Endpoint method names renamed to better represent urls 2019-08-27 11:02:52 +03:00
Eriks Karls
088219a60a Endpoint method names renamed to better represent urls 2019-08-27 10:51:57 +03:00
Eriks Karls
53266b1c94 Merge branch 'master' of github.com:eeriks/erepublik_script 2019-08-27 10:17:34 +03:00
Eriks Karls
065a0ff3ec bugfix 2019-08-27 10:13:37 +03:00
Eriks Karls
c8e90ca910 bugfix 2019-08-27 10:13:14 +03:00
Ēriks Karls
bbab84bf5b
Update utils.py 2019-08-26 22:42:06 +03:00
Eriks Karls
fff17469e0 More verbose Citizen class serialization 2019-08-26 10:06:18 +03:00
Eriks Karls
0d208a8d32 Add timezone to datetime serialization 2019-08-26 10:05:52 +03:00
Eriks Karls
86004eb81b Don't serialize password nor email 2019-08-26 10:05:22 +03:00
Eriks Karls
7e05e35ebf More verbose Object representation in json format 2019-08-26 09:47:04 +03:00
Eriks Karls
411b158487 Citizen.buy_food() now buys 48h worth of food
When doing WAM and fialing because not enough food - buy food
Integrated new RW side chooser
2019-08-26 09:32:12 +03:00
Eriks Karls
d6e161d815 Bump version: 0.15.2 → 0.15.3 2019-08-24 15:44:22 +03:00
Eriks Karls
1285e95cec Inventory usage bugfixes 2019-08-24 10:48:04 +03:00
Eriks Karls
324db2c136 Fix new division numbering 2019-08-24 10:27:31 +03:00
Eriks Karls
1c6b41e41b Inventory updates and booster usage updates 2019-08-24 10:03:52 +03:00
Eriks Karls
a52552afb7 Use MyJSONEncoder from classes 2019-08-22 14:33:45 +03:00
Eriks Karls
28bfdc7b20 Use country ids from constant in utils 2019-08-22 14:33:07 +03:00
Eriks Karls
de1b734728 Don't check levelup when eating 2019-08-22 14:32:32 +03:00
Eriks Karls
fa5646ecfd Receive daily order 2019-08-22 14:32:00 +03:00
Eriks Karls
339392cfb0 Regex bugfix 2019-08-13 15:44:07 +03:00
Eriks Karls
7ebba458cb Max time timezones 2019-08-12 14:19:52 +03:00
Eriks Karls
4f436292af Organisation account fetcher - UNSTABLE 2019-08-12 12:54:58 +03:00
Eriks Karls
416c391d21 Improved levelup energy management 2019-08-11 01:45:49 +03:00
Eriks Karls
66c53ef985 Typo 2019-08-11 01:44:45 +03:00
Eriks Karls
3fa7d097fe Found awards switch to ASCII friendlier multiplier char - ✕ -> x
Localize max div end time
2019-08-05 11:55:39 +03:00
Eriks Karls
6af9675c39 Added gameTokensMarket endpoint 2019-08-05 10:20:36 +03:00
Eriks Karls
2343a6c6c8 WeeklyChallange end energy saver tweaks 2019-08-05 10:20:19 +03:00
Eriks Karls
7e56f01a38 Added gameTokensMarket endpoint 2019-08-05 10:13:48 +03:00
Eriks Karls
e14cbc18e9 Bump version: 0.15.1 → 0.15.2 2019-08-01 17:24:07 +03:00
Eriks Karls
048ce798dd All battle div div containing dicts where reference to the same object 2019-08-01 17:23:24 +03:00
Eriks Karls
e5b7cde044 Bump version: 0.15.0 → 0.15.1 2019-08-01 15:06:34 +03:00
Eriks Karls
6bbc7a1f64 Typehinting and battle/war stuff - last battle, attackable regions if CP, etc 2019-08-01 15:05:44 +03:00
Eriks Karls
4eccb339bb Deploy bombs 2019-08-01 15:04:16 +03:00
Eriks Karls
dbeb6e9ba5 Market scraper updates 2019-08-01 09:37:26 +03:00
Eriks Karls
9c9bb5ae40 Region attacking post updated 2019-08-01 09:37:08 +03:00
Eriks Karls
d8eb69f82a Travel bugfix and market scraper fixup 2019-07-31 22:39:54 +03:00
Eriks Karls
42c430213f Minor updates 2019-07-31 21:41:24 +03:00
Eriks Karls
39dbcaa27d Continuing work on return Response free Citizen class 2019-07-31 21:41:08 +03:00
Eriks Karls
8911adb81c Traveling improved for Citizen class 2019-07-31 21:38:34 +03:00
Eriks Karls
7927c162f8 More precise type hint 2019-07-31 21:37:29 +03:00
Eriks Karls
92b7c45a7d Report promos to erep.lv 2019-07-31 21:36:57 +03:00
Eriks Karls
53257487d8 Write on country wall update 2019-07-30 18:32:12 +03:00
Eriks Karls
8690c4d3f2 minor fix 2019-07-30 11:10:35 +03:00
Eriks Karls
43c6bce160 minor fix 2019-07-30 11:10:09 +03:00
Eriks Karls
c4f598c1ba Bump version: 0.14.7 → 0.15.0 2019-07-30 10:22:38 +03:00
Eriks Karls
c48d90dec3 First step towards removing manual response parsing in Citizen class 2019-07-30 10:22:29 +03:00
Eriks Karls
953902476f Bump version: 0.14.6.1 → 0.14.7 2019-07-30 09:32:10 +03:00
Eriks Karls
156cbbb61c Typehinting changes 2019-07-30 09:32:03 +03:00
Eriks Karls
b72039c865 PyPi needs new version number, even if accidentaly published version has been removed 2019-07-29 15:17:14 +03:00
Eriks Karls
9587538fdc Merge branch 'master' of github.com:eeriks/erepublik_script
* 'master' of github.com:eeriks/erepublik_script:
  Bump version: 0.14.5 → 0.14.6
2019-07-29 13:54:09 +03:00
Eriks Karls
e775679581 Bump version: 0.14.5 → 0.14.6 2019-07-29 13:53:17 +03:00
Eriks Karls
3aa305ea74 Switch from python-slugify to self hosted (Taken from Django 2.2.1 django.utils.text.slugify) 2019-07-29 13:52:33 +03:00
Eriks Karls
6b2e5ffb68 Bump version: 0.14.5 → 0.14.6 2019-07-29 13:33:02 +03:00
Eriks Karls
bb8634fe56 After deleting debug directory next request failed if SlowRequests.debug == True 2019-07-29 13:31:07 +03:00
Eriks Karls
25a0d8993e eRepublikAPI now has it's own token property 2019-07-29 13:19:19 +03:00
Eriks Karls
a1d10bb427 Bump version: 0.14.4 → 0.14.5 2019-07-23 17:55:08 +03:00
Eriks Karls
ec701396d9 project name migration 2019-07-23 17:55:04 +03:00
Eriks Karls
d8035b42e3 Underline too short 2019-07-23 17:53:00 +03:00
Eriks Karls
42320a14a4 no message 2019-07-23 17:50:14 +03:00
Eriks Karls
de4b059b7d Package name update: erepublik_script → eRepublik 2019-07-23 16:41:21 +03:00
Eriks Karls
dc106cc87d Bump version: 0.14.3 → 0.14.4 2019-07-23 14:37:35 +03:00
Eriks Karls
bb2c13d63a requirement update 2019-07-23 14:37:26 +03:00
Eriks Karls
ea48fbe7e1 Wall post comment creation endpoints 2019-07-23 14:37:07 +03:00
Eriks Karls
65a3a9f678 Possible memory leak addressed 2019-07-23 14:36:27 +03:00
Eriks Karls
0757345e17 . 2019-07-23 14:34:49 +03:00
Eriks Karls
6f4b32b12c code cleanup 2019-07-22 11:54:33 +03:00
Eriks Karls
69265a35e8 Bump version: 0.14.2 → 0.14.3 2019-07-22 11:03:44 +03:00
Eriks Karls
f12bd0ed57 requirements 2019-07-22 11:03:14 +03:00
Eriks Karls
2d246cbf4b Continuation from previous project's version 2019-07-19 11:06:58 +03:00
Eriks Karls
1a24228d8a Bump version: 0.1.1 → 0.1.2 2019-07-19 11:00:20 +03:00
Eriks Karls
4e89f57b92 default, all enabled config file 2019-07-19 11:00:08 +03:00
Eriks Karls
a2af70a3ef cli update 2019-07-19 10:59:45 +03:00
61 changed files with 8953 additions and 19904 deletions

View File

@ -19,3 +19,9 @@ insert_final_newline = false
[Makefile]
indent_style = tab
[*.py]
max_line_length = 120
line_length=120
multi_line_output=0
balanced_wrapping=True

5
.flake8 Normal file
View File

@ -0,0 +1,5 @@
[flake8]
ignore = E203,E722
exclude = .git,__pycache__,venv,docs,debug,log
max-line-length = 120
#max-complexity = 10

View File

@ -1,6 +1,6 @@
* eRepublik script version:
* Python version:
* Operating System:
* eRepublik script version:
* Python version:
* Operating System:
### Description
@ -9,7 +9,7 @@ Tell us what happened, what went wrong, and what you expected to happen.
### What I Did
```
``` python
Paste the command(s) you ran and the output.
If there was a crash, please include the traceback here.
```

71
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '16 0 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

41
.github/workflows/pythonpackage.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Python package
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Lint with isort
run: |
pip install -U isort
isort --check erepublik
- name: Lint with Black
run: |
pip install -U black
black --check erepublik
- name: Lint with flake8
run: |
pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
# - name: Test with pytest
# run: |
# pip install pytest
# pytest

6
.gitignore vendored
View File

@ -37,7 +37,6 @@ pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
@ -85,7 +84,7 @@ celerybeat-schedule
# virtualenv
.venv
venv/
venv*/
ENV/
# Spyder project settings
@ -103,4 +102,5 @@ ENV/
debug/
log/
docs/
*dump.json
.idea/

15
.readthedocs.yaml Normal file
View File

@ -0,0 +1,15 @@
version: 2
build:
os: "ubuntu-20.04"
tools:
python: "3.9"
# Build from the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# Explicitly set the version of Python and its requirements
python:
install:
- requirements: requirements-dev.txt

View File

@ -1,16 +0,0 @@
# Config file for automatic testing at travis-ci.org
language: python
python:
- 3.6
- 3.5
- 3.4
- 2.7
# Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install: pip install -U tox-travis
# Command to run tests, e.g. python setup.py test
script: tox

View File

@ -15,7 +15,7 @@ Types of Contributions
Report Bugs
~~~~~~~~~~~
Report bugs at https://github.com/eeriks/erepublik_script/issues.
Report bugs at https://github.com/eeriks/erepublik/issues.
If you are reporting a bug, please include:
@ -23,12 +23,6 @@ If you are reporting a bug, please include:
* Any details about your local setup that might be helpful in troubleshooting.
* Detailed steps to reproduce the bug.
Fix Bugs
~~~~~~~~
Look through the GitHub issues for bugs. Anything tagged with "bug" and "help
wanted" is open to whoever wants to implement it.
Implement Features
~~~~~~~~~~~~~~~~~~
@ -45,7 +39,7 @@ articles, and such.
Submit Feedback
~~~~~~~~~~~~~~~
The best way to send feedback is to file an issue at https://github.com/eeriks/erepublik_script/issues.
The best way to send feedback is to file an issue at https://github.com/eeriks/erepublik/issues.
If you are proposing a feature:
@ -57,17 +51,17 @@ If you are proposing a feature:
Get Started!
------------
Ready to contribute? Here's how to set up `erepublik_script` for local development.
Ready to contribute? Here's how to set up `erepublik` for local development.
1. Fork the `erepublik_script` repo on GitHub.
1. Fork the `erepublik` repo on GitHub.
2. Clone your fork locally::
$ git clone git@github.com:your_name_here/erepublik_script.git
$ git clone git@github.com:your_name_here/erepublik.git
3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
3. This is how you set up your fork for local development::
$ mkvirtualenv erepublik_script
$ cd erepublik_script/
$ cd erepublik/
$ python3 -m venv venv
$ python setup.py develop
4. Create a branch for local development::
@ -76,14 +70,13 @@ Ready to contribute? Here's how to set up `erepublik_script` for local developme
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_script tests
$ python setup.py test or py.test
$ tox
$ make lint
$ 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::
@ -102,8 +95,8 @@ Before you submit a pull request, check that it meets these guidelines:
2. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring, and add the
feature to the list in README.rst.
3. The pull request should work for Python 2.7, 3.4, 3.5 and 3.6, and for PyPy. Check
https://travis-ci.org/eeriks/erepublik_script/pull_requests
3. The pull request should work for Python 3.7.1. Check
https://travis-ci.org/eeriks/erepublik/pull_requests
and make sure that the tests pass for all supported Python versions.
Tips
@ -112,7 +105,7 @@ Tips
To run a subset of tests::
$ python -m unittest tests.test_erepublik_script
$ python -m unittest tests.test_erepublik
Deploying
---------
@ -121,8 +114,6 @@ A reminder for the maintainers on how to deploy.
Make sure all your changes are committed (including an entry in HISTORY.rst).
Then run::
$ bumpversion patch # possible: major / minor / patch
$ bumpversion patch # possible: major / minor / patch / dev
$ git push
$ git push --tags
Travis will then deploy to PyPI if tests pass.

View File

@ -2,6 +2,140 @@
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
* Restricted IP check
* Bomb deploy improvements
* More verbose action logging
* Division switching for maverick scripts
* New medal endpoint is correctly parsed
* WAM/Employ modularized
0.19.0 (2020-01-13)
-------------------
* Created method for current products on sale.
* Updated inventory to also include products on sale
* set_default_weapon() - eRepublik should return list with all available weapon qualities, but when a battle is just launched, they return only dict with barehands
* fight() - no longer calls self.set_default_weapon()
* find_battle_and_fight() - now calls self.set_default_weapon() just before fighting
* update_war_info() - returns previous battle list if responses 'last_updated' isn't more than 30s old
* get_battle_for_war(war_id) - returns Battle instance for specific war, if battle is active for given war
* Citizen.get_raw_surplus() fixed and moved to Citizen.my_companies.get_wam_raw_usage()
* Implemented division switching
* improved multi bomb deploy with auto traveling,
* Citizen.fight() simplified battle data gathering logic -> Citizen.shoot logic improved
0.17.0 (2019-11-21)
-------------------
* 12th anniversary's endpoints added
* Telegram message queue optimisation
* WC end fighting energy bugfix
* More strict fighting limiting before week change
* Improved and fixed ground damage booster usage
0.16.0 (2019-09-29)
-------------------
* Telegram notification integration
* Improved serialization to JSON
* When failing to do WAM because of not enough food - buy food
* Buy food buys 48h worth instead of 24h energy
0.15.3 (2019-08-24)
-------------------
* Update after eRepublik changed campaign apis
0.15.0 (2019-07-30)
-------------------
* CitizenAPI class methods renamed to "private", they are intended to be used internally.
* TODO: None of the Citizen class's methods should return Response object - CitizenAPI is meant for that.
0.14.4 (2019-07-23)
-------------------
* Wall post comment endpoints updated with comment create endpoints.
0.1.0 (2019-07-19)
------------------

686
LICENSE
View File

@ -1,22 +1,674 @@
MIT License
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2019, Eriks Karls
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Preamble
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
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.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -43,32 +43,32 @@ clean-pyc: ## remove Python file artifacts
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +
rm -rf debug/
rm -rf log/
clean-test: ## remove test and coverage artifacts
rm -fr .tox/
rm -f .coverage
rm -fr htmlcov/
rm -fr .pytest_cache
lint: ## check style with flake8
flake8 erepublik_script tests
isort erepublik examples tests
black erepublik examples tests
flake8 erepublik examples tests
test: ## run tests quickly with the default Python
python setup.py test
python -m unittest
test-all: ## run tests on every Python version with tox
tox
coverage: ## check code coverage quickly with the default Python
coverage run --source erepublik_script setup.py test
coverage: lint ## check code coverage quickly with the default Python
coverage run --source erepublik setup.py test
coverage report -m
coverage html
$(BROWSER) htmlcov/index.html
docs: ## generate Sphinx HTML documentation, including API docs
rm -f docs/erepublik_script.rst
rm -f docs/erepublik.rst
rm -f docs/modules.rst
sphinx-apidoc -o docs/ erepublik_script
sphinx-apidoc -o docs/ erepublik
$(MAKE) -C docs clean
$(MAKE) -C docs html
$(BROWSER) docs/_build/html/index.html

View File

@ -3,28 +3,20 @@ eRepublik script
================
.. image:: https://img.shields.io/pypi/v/erepublik_script.svg
:target: https://pypi.python.org/pypi/erepublik_script
.. image:: https://img.shields.io/pypi/v/erepublik.svg
:target: https://pypi.python.org/pypi/erepublik
.. image:: https://img.shields.io/travis/eeriks/erepublik_script.svg
:target: https://travis-ci.org/eeriks/erepublik_script
.. image:: https://readthedocs.org/projects/erepublik-script/badge/?version=latest
:target: https://erepublik-script.readthedocs.io/en/latest/?badge=latest
.. image:: https://readthedocs.org/projects/erepublik/badge/?version=latest
:target: https://erepublik.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. image:: https://pyup.io/repos/github/eeriks/erepublik_script/shield.svg
:target: https://pyup.io/repos/github/eeriks/erepublik_script/
:alt: Updates
Python package for automated eRepublik playing
Python package for eRepublik automated playing
* Free software: MIT license
* Documentation: https://erepublik-script.readthedocs.io.
* Free software: GPLv3.0
* Documentation: https://erepublik.readthedocs.io/en/latest/
* Source code: https://github.com/eeriks/erepublik
Features

36
config.json Normal file
View File

@ -0,0 +1,36 @@
{
"air": true,
"all_in": true,
"auto_buy_raw": true,
"auto_sell": [
"food",
"weapon",
"house",
"airplane",
"foodRaw",
"weaponRaw",
"houseRaw",
"airplaneRaw"
],
"auto_sell_all": true,
"boosters": true,
"continuous_fighting": true,
"debug": true,
"email": "",
"employ": true,
"epic_hunt": true,
"epic_hunt_ebs": true,
"fight": true,
"gold_buy": true,
"ground": true,
"interactive": true,
"next_energy": true,
"ot": true,
"password": "",
"random_sleep": true,
"rw_def_side": true,
"train": true,
"travel_to_fight": true,
"wam": true,
"work": true
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
<html><head><meta http-equiv="refresh" content="0;url=https://www.erepublik.com/en"/></head></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"error":false,"enabled":true,"type":{"anniversary":false,"flavorPacks":false,"springChallenge":false,"summerChallenge":false,"halloweenChallenge":false},"timeLeft":346765,"nextReward":{"maxReward":false,"type":"icon_energy_booster","text":"+1 Energy recovery until the end of Day 4,262"},"maxRewardId":0,"player":{"avatar":"//cdnt.erepublik.net/7efiav4XZ4SMvXAtgEk1NciUmAg=/55x55/smart/avatars/Citizens/2009/07/08/4b57b9ebb0232f0d6c3f6f2c21b8ab95.jpg?c022b6df6f643263dba839cb35b7a9ab","name":"inpoc1","prestigePoints":14170},"progress":0.9141935483871,"rewards":{"normal":[{"id":59,"collectedBefore":58,"percent":0.74193548387097,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":60,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":61,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":62,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":63,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":64,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":65,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":66,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":67,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":68,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":69,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":70,"collectedBefore":0,"percent":0.016129032258065,"label":"Reach 14,250 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","tooltip":"Reach 14250 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","status":"","icon":"energy_booster"},{"id":71,"collectedBefore":0,"percent":0.016129032258065,"label":"Reach 14,500 Prestige Points to unlock the following reward: 10 Energy Bars","tooltip":"Reach 14500 Prestige Points to unlock the following reward: 10 Energy Bars","status":"","icon":"energy_bars"},{"id":72,"collectedBefore":0,"percent":0.032258064516129,"label":"Reach 15,000 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","tooltip":"Reach 15000 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","status":"","icon":"energy_booster"},{"id":73,"collectedBefore":0,"percent":0.016129032258065,"label":"Reach 15,250 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","tooltip":"Reach 15250 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","status":"","icon":"energy_booster"},{"id":74,"collectedBefore":0,"percent":0.016129032258065,"label":"Reach 15,500 Prestige Points to unlock the following reward: 15 Energy Bars","tooltip":"Reach 15500 Prestige Points to unlock the following reward: 15 Energy Bars","status":"","icon":"energy_bars"}],"extra":[]}}

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python -msphinx
SPHINXPROJ = erepublik_script
SPHINXPROJ = erepublik
SOURCEDIR = .
BUILDDIR = _build

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# erepublik_script documentation build configuration file, created by
# erepublik documentation build configuration file, created by
# sphinx-quickstart on Fri Jun 9 13:47:02 2017.
#
# This file is execfile()d with the current directory set to its
@ -20,9 +20,14 @@
#
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
import erepublik_script
sys.path.insert(0, os.path.abspath(".."))
import datetime
import edx_theme
import erepublik
# -- General configuration ---------------------------------------------
@ -32,33 +37,33 @@ import erepublik_script
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "edx_theme"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
source_suffix = ".rst"
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = u'eRepublik script'
copyright = u"2019, Eriks Karls"
author = u"Eriks Karls"
project = "eRepublik script"
copyright = "2017-%i, Eriks Karls" % datetime.date.today().year
author = "Eriks Karls"
# The version info for the project you're documenting, acts as replacement
# for |version| and |release|, also used in various other places throughout
# the built documents.
#
# The short X.Y version.
version = erepublik_script.__version__
version = erepublik.__version__
# The full version, including alpha/beta/rc tags.
release = erepublik_script.__version__
release = erepublik.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -70,10 +75,10 @@ language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
@ -84,7 +89,8 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
html_theme = "edx_theme"
html_theme_path = [edx_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a
# theme further. For a list of options available for each theme, see the
@ -95,13 +101,13 @@ html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# html_static_path = ["_static"]
# -- Options for HTMLHelp output ---------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'erepublik_scriptdoc'
htmlhelp_basename = "erepublikdoc"
# -- Options for LaTeX output ------------------------------------------
@ -110,15 +116,12 @@ latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
@ -128,9 +131,7 @@ latex_elements = {
# (source start file, target name, title, author, documentclass
# [howto, manual, or own class]).
latex_documents = [
(master_doc, 'erepublik_script.tex',
u'eRepublik script Documentation',
u'Eriks Karls', 'manual'),
(master_doc, "erepublik.tex", "eRepublik script Documentation", "Eriks Karls", "manual"),
]
@ -138,11 +139,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'erepublik_script',
u'eRepublik script Documentation',
[author], 1)
]
man_pages = [(master_doc, "erepublik", "eRepublik script Documentation", [author], 1)]
# -- Options for Texinfo output ----------------------------------------
@ -151,13 +148,13 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'erepublik_script',
u'eRepublik script Documentation',
author,
'erepublik_script',
'One line description of project.',
'Miscellaneous'),
(
master_doc,
"erepublik",
"eRepublik script Documentation",
author,
"erepublik",
"One line description of project.",
"Miscellaneous",
),
]

53
docs/erepublik.rst Normal file
View File

@ -0,0 +1,53 @@
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.utils module
----------------------
.. automodule:: erepublik.utils
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: erepublik
:members:
:undoc-members:
:show-inheritance:

512
docs/index.html Normal file
View File

@ -0,0 +1,512 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="Eriks Karls">
<meta name="generator" content="Jekyll v4.0.1">
<title>eBot configuration</title>
<!-- CSS only -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" 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/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<div class="p-3 text-center">
<h1>eBot configuration file generator</h1>
<!-- <h2>-->
<!-- <span class="d-inline d-sm-none d-md-none d-lg-none d-xl-none">XS</span>-->
<!-- <span class="d-none d-sm-inline d-md-none d-lg-none d-xl-none">SM</span>-->
<!-- <span class="d-none d-sm-none d-md-inline d-lg-none d-xl-none">MD</span>-->
<!-- <span class="d-none d-sm-none d-md-none d-lg-inline d-xl-none">LG</span>-->
<!-- <span class="d-none d-sm-none d-md-none d-lg-none d-xl-inline">XL</span>-->
<!-- </h2>-->
</div>
<div class="row pt-4">
<div class="col-12">
<form>
<div class="row">
<div class="col-12 col-sm-8 col-md-6">
<h3>Login data</h3>
<div class="form-group">
<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-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>
<label class="custom-control-label" for="work">Work</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="train" checked>
<label class="custom-control-label" for="train">Train</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="ot" checked>
<label class="custom-control-label" for="ot">Work overtime</label>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-6">
<h3>Production</h3>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="wam" checked>
<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="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>
<label class="custom-control-label" for="auto_buy_raw">Auto buy missing RAW</label>
</div>
</div>
<div class="form-group">
<h6 class="">Auto sell produced products</h6>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_frm">
<label class="custom-control-label" for="auto_sell_frm">Food Raw</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_food">
<label class="custom-control-label" for="auto_sell_food">Food</label>
</div>
<div class="clearfix"></div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_wrm">
<label class="custom-control-label" for="auto_sell_wrm">Weapon Raw</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_weapons">
<label class="custom-control-label" for="auto_sell_weapons">Weapon</label>
</div>
<div class="clearfix"></div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_hrm">
<label class="custom-control-label" for="auto_sell_hrm">House Raw</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_house">
<label class="custom-control-label" for="auto_sell_house">House</label>
</div>
<div class="clearfix"></div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_arm">
<label class="custom-control-label" for="auto_sell_arm">Aircraft Raw</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_air">
<label class="custom-control-label" for="auto_sell_air">Aircraft Weapon</label>
</div>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_all">
<label class="custom-control-label" for="auto_sell_all">Auto sell all (also from inventory)</label>
</div>
</div>
<div class="col-12 col-sm-6">
<h3><span style="text-decoration: line-through;">Fighting</span></h3>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="fight" disabled>
<label class="custom-control-label" for="fight">Fight</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="air" disabled>
<label class="custom-control-label" for="air">Air</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="ground" disabled>
<label class="custom-control-label" for="ground">Ground</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="boosters" disabled>
<label class="custom-control-label" for="boosters">Use ground boosters</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="continuous_fighting" disabled>
<label class="custom-control-label" for="continuous_fighting">Continue fighting all FF in round</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="next_energy" disabled>
<label class="custom-control-label" for="next_energy">Fight for next WC +1hp/6min if reachable by FF</label>
</div>
</div>
<div class="form-group">
<div class="form-check form-check-inline">
<input type="radio" class="form-check-input" onchange="updateJson()" id="all_in" name="fight_amount" value="all_in" disabled>
<label class="form-check-label" for="all_in">All energy</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" class="form-check-input" onchange="updateJson()" id="h_energy" name="fight_amount" value="h_energy" disabled>
<label class="form-check-label" for="h_energy">1h energy</label>
</div>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="rw_def_side" disabled>
<label class="custom-control-label" for="rw_def_side">In RWs fight on right side (occupier/defender)</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="travel_to_fight" disabled>
<label class="custom-control-label" for="travel_to_fight">Travel to fight</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="epic_hunt" disabled>
<label class="custom-control-label" for="epic_hunt">Hunt epics</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="epic_hunt_ebs" disabled>
<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>
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title">config.json</h5>
<h6 class="card-subtitle mb-2 text-muted">Copy-paste the content below into 'config.json' file</h6>
<pre id="json-output" class="bg-light card-text"></pre>
</div>
</div>
</div>
</div>
</div>
<script>
function disable(element){
element.checked = false;
element.disabled = true;
element.value = null;
}
function updateJson() {
let config = {};
let email = document.getElementById('email'); // Generated
config.email = email.value;
config.password = "";
let work = document.getElementById('work'); // Generated
config.work = work.checked;
let train = document.getElementById('train'); // Generated
config.train = train.checked;
let ot = document.getElementById('ot'); // Generated
config.ot = ot.checked;
let renew_houses = document.getElementById('renew_houses'); // Generated
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
config.interactive = interactive.checked;
let debug = document.getElementById('debug'); // Generated
config.debug = debug.checked;
let wam = document.getElementById('wam'); // Generated
config.wam = wam.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
let auto_sell_frm = document.getElementById('auto_sell_frm'); // Generated
let auto_sell_food = document.getElementById('auto_sell_food'); // Generated
let auto_sell_wrm = document.getElementById('auto_sell_wrm'); // Generated
let auto_sell_weapons = document.getElementById('auto_sell_weapons'); // Generated
let auto_sell_hrm = document.getElementById('auto_sell_hrm'); // Generated
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.employees) {
auto_buy_raw.disabled = false;
auto_sell_all.disabled = false;
auto_sell_frm.disabled = false;
auto_sell_food.disabled = false;
auto_sell_wrm.disabled = false;
auto_sell_weapons.disabled = false;
auto_sell_hrm.disabled = false;
auto_sell_house.disabled = false;
auto_sell_arm.disabled = false;
auto_sell_air.disabled = false;
} else {
disable(auto_buy_raw);
disable(auto_sell_all);
disable(auto_sell_food);
disable(auto_sell_weapons);
disable(auto_sell_house);
disable(auto_sell_air);
disable(auto_sell_frm);
disable(auto_sell_wrm);
disable(auto_sell_hrm);
disable(auto_sell_arm);
}
config.auto_buy_raw = auto_buy_raw.checked;
config.auto_sell_all = auto_sell_all.checked;
config.auto_sell = [];
if (auto_sell_food.checked) config.auto_sell.push("food");
if (auto_sell_weapons.checked) config.auto_sell.push("weapon");
if (auto_sell_house.checked) config.auto_sell.push("house");
if (auto_sell_air.checked) config.auto_sell.push("airplane");
if (auto_sell_frm.checked) config.auto_sell.push("foodRaw");
if (auto_sell_wrm.checked) config.auto_sell.push("weaponRaw");
if (auto_sell_hrm.checked) config.auto_sell.push("houseRaw");
if (auto_sell_arm.checked) config.auto_sell.push("airplaneRaw");
let fight = document.getElementById('fight'); // Generated
config.fight = fight.checked;
let air = document.getElementById('air'); // Generated
let ground = document.getElementById('ground'); // Generated
let boosters = document.getElementById('boosters'); // Generated
let continuous_fighting = document.getElementById('continuous_fighting'); // Generated
let next_energy = document.getElementById('next_energy'); // Generated
let all_in = document.getElementById('all_in'); // Generated
let h_energy = document.getElementById('h_energy'); // Generated
let rw_def_side = document.getElementById('rw_def_side'); // Generated
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) {
air.disabled = false;
ground.disabled = false;
boosters.disabled = false;
continuous_fighting.disabled = false;
next_energy.disabled = false;
all_in.disabled = false;
h_energy.disabled = false;
rw_def_side.disabled = false;
travel_to_fight.disabled = false;
epic_hunt.disabled = false;
epic_hunt_ebs.disabled = false;
if (!epic_hunt.checked) disable(epic_hunt_ebs);
} else {
disable(air);
disable(ground);
disable(boosters);
disable(continuous_fighting);
disable(next_energy);
disable(all_in);
disable(h_energy);
disable(rw_def_side);
disable(travel_to_fight);
disable(epic_hunt);
disable(epic_hunt_ebs);
}
config.air = air.checked;
config.ground = ground.checked;
config.boosters = boosters.checked;
config.continuous_fighting = continuous_fighting.checked;
config.next_energy = next_energy.checked;
config.all_in = all_in.checked;
config.rw_def_side = rw_def_side.checked;
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);
}
updateJson();
</script>
</body>
</html>
<!--
{
"email": "",
"password": "",
"work": true,
"train": true,
"ot": true,
"renew_houses": true,
"random_sleep": true,
"buy_gold": true,
"interactive": true,
"debug": true,
"wam": true,
"employees": true,
"auto_buy_raw": true,
"auto_sell_all": true,
"auto_sell": [
"food",
"weapon",
"house",
"airplane",
"foodRaw",
"weaponRaw",
"houseRaw",
"airplaneRaw"
],
"fight": true,
"air": true,
"boosters": true,
"continuous_fighting": true,
"next_energy": true,
"all_in": false,
"rw_def_side": true,
"travel_to_fight": true,
"epic_hunt": true,
"epic_hunt_ebs": true
}
-->

View File

@ -1,5 +1,5 @@
Welcome to eRepublik script's documentation!
======================================
============================================
.. toctree::
:maxdepth: 2

View File

@ -12,7 +12,7 @@ To install eRepublik script, run this command in your terminal:
.. code-block:: console
$ pip install erepublik_script
$ pip install erepublik
This is the preferred method to install eRepublik script, as it will always install the most recent stable release.
@ -32,13 +32,13 @@ You can either clone the public repository:
.. code-block:: console
$ git clone git://github.com/eeriks/erepublik_script
$ git clone git://github.com/eeriks/erepublik
Or download the `tarball`_:
.. code-block:: console
$ curl -OL https://github.com/eeriks/erepublik_script/tarball/master
$ curl -OL https://github.com/eeriks/erepublik/tarball/master
Once you have a copy of the source, you can install it with:
@ -47,5 +47,5 @@ Once you have a copy of the source, you can install it with:
$ python setup.py install
.. _Github repo: https://github.com/eeriks/erepublik_script
.. _tarball: https://github.com/eeriks/erepublik_script/tarball/master
.. _Github repo: https://github.com/eeriks/erepublik
.. _tarball: https://github.com/eeriks/erepublik/tarball/master

View File

@ -9,7 +9,7 @@ if "%SPHINXBUILD%" == "" (
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=erepublik_script
set SPHINXPROJ=erepublik
if "%1" == "" goto help

7
docs/modules.rst Normal file
View File

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

View File

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

859
erepublik/Fighting Normal file
View File

@ -0,0 +1,859 @@
# TODO: Fix fighting and reimplement CitizenMilitary module
# class CitizenMilitary(CitizenTravel):
# all_battles: Dict[int, classes.Battle] = None
# __last_war_update_data = None
#
# active_fs: bool = False
#
# @property
# def as_dict(self):
# d = super().as_dict
# d.update(active_fs=self.active_fs, all_battles=self.all_battles)
# return d
# def update_all():
# super().update_all()
# self.update_war_info()
#
# def update_war_info(self):
# if (
# self.__last_war_update_data
# and self.__last_war_update_data.get("last_updated", 0) + 30 > self.now.timestamp()
# ):
# r_json = self.__last_war_update_data
# else:
# r_json = self._get_military_campaigns_json_list().json()
# if r_json.get("countries"):
# if self.all_battles is None:
# self.all_battles = {}
# self.__last_war_update_data = r_json
# if r_json.get("battles"):
# all_battles = {}
# for battle_data in r_json.get("battles", {}).values():
# all_battles[battle_data.get("id")] = classes.Battle(battle_data)
# # old_all_battles = self.all_battles
# self.all_battles = all_battles
# # for battle in old_all_battles.values():
# # utils._clear_up_battle_memory(battle)
#
# def get_battle_for_war(self, war_id: int) -> Optional[classes.Battle]:
# self.update_war_info()
# war_info = self.get_war_status(war_id)
# return self.all_battles.get(war_info.get("battle_id"), None)
#
# def get_war_status(self, war_id: int) -> Dict[str, Union[bool, Dict[int, str]]]:
# r = self._get_wars_show(war_id)
# html = r.text
# ret = {}
# reg_re = re.compile(fr'data-war-id="{war_id}" data-region-id="(\d+)" data-region-name="([- \w]+)"')
# if reg_re.findall(html):
# ret.update(regions={}, can_attack=True)
# for reg in reg_re.findall(html):
# ret["regions"].update({int(reg[0]): reg[1]})
# elif re.search(
# r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" '
# r'class="join" title="Join"><span>Join</span></a>',
# html,
# ):
# battle_id = re.search(
# r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" '
# r'class="join" title="Join"><span>Join</span></a>',
# html,
# ).group(1)
# ret.update(can_attack=False, battle_id=int(battle_id))
# elif re.search(r"This war is no longer active.", html):
# ret.update(can_attack=False, ended=True)
# else:
# ret.update(can_attack=False)
# return ret
#
# def get_available_weapons(self, battle_id: int):
# return self._get_military_show_weapons(battle_id).json()
#
# def set_default_weapon(self, battle: classes.Battle, division: classes.BattleDivision) -> int:
# available_weapons = self._get_military_show_weapons(battle.id).json()
# while not isinstance(available_weapons, list):
# available_weapons = self._get_military_show_weapons(battle.id).json()
# weapon_quality = -1
# weapon_damage = 0
# if not division.is_air:
# for weapon in available_weapons:
# try:
# if weapon["weaponQuantity"] > 30 and weapon["weaponInfluence"] > weapon_damage:
# weapon_quality = int(weapon["weaponId"])
# weapon_damage = weapon["weaponInfluence"]
# except ValueError:
# pass
# return self.change_weapon(battle, weapon_quality, division)
#
# def change_weapon(self, battle: classes.Battle, quality: int, battle_zone: classes.BattleDivision) -> int:
# r = self._post_military_change_weapon(battle.id, battle_zone.id, quality)
# influence = r.json().get("weaponInfluence")
# self._report_action(
# "MILITARY_WEAPON", f"Switched to q{quality} weapon," f" new influence {influence}", kwargs=r.json()
# )
# return influence
#
# def sorted_battles(self, sort_by_time: bool = True, only_tp=False) -> List[classes.Battle]:
# cs_battles_priority_air: List[classes.Battle] = []
# cs_battles_priority_ground: List[classes.Battle] = []
# cs_battles_air: List[classes.Battle] = []
# cs_battles_ground: List[classes.Battle] = []
# deployed_battles_air: List[classes.Battle] = []
# deployed_battles_ground: List[classes.Battle] = []
# ally_battles_air: List[classes.Battle] = []
# ally_battles_ground: List[classes.Battle] = []
# other_battles_air: List[classes.Battle] = []
# other_battles_ground: List[classes.Battle] = []
#
# ret_battles: List[classes.Battle] = []
# if sort_by_time:
# battle_list = sorted(self.all_battles.values(), key=lambda b: b.start)
# battle_list.reverse()
# else:
# battle_list = sorted(self.all_battles.values(), key=lambda b: b.id)
#
# contribution_json = self._get_military_campaigns_json_citizen().json()
# contributions: List[Dict[str, int]] = contribution_json.get("contributions") or []
# contributions.sort(key=lambda b: -b.get("damage"))
#
# for contribution_battle in contributions:
# if contribution_battle.get("battle_id") and contribution_battle.get("battle_id") in self.all_battles:
# ret_battles.append(self.all_battles[contribution_battle.get("battle_id")])
#
# for battle in battle_list:
# battle_sides = [battle.invader.country, battle.defender.country]
# if battle.id in ret_battles:
# continue
# # CS Battles
# elif self.details.citizenship in battle_sides:
# if battle.has_air:
# if battle.defender.id == self.details.citizenship:
# cs_battles_priority_air.append(battle)
# else:
# cs_battles_air.append(battle)
# else:
# if battle.defender.id == self.details.citizenship:
# cs_battles_priority_ground.append(battle)
# else:
# cs_battles_ground.append(battle)
#
# # Current location battles:
# elif self.details.current_country in battle_sides:
# if battle.has_air:
# deployed_battles_air.append(battle)
# else:
# deployed_battles_ground.append(battle)
#
# # Deployed battles and allied battles:
# elif self.details.current_country in battle.invader.allies + battle.defender.allies + battle_sides:
# if self.details.current_country in battle.invader.deployed + battle.defender.deployed:
# if battle.has_air:
# deployed_battles_air.append(battle)
# else:
# deployed_battles_ground.append(battle)
# # Allied battles:
# else:
# if battle.has_air:
# ally_battles_air.append(battle)
# else:
# ally_battles_ground.append(battle)
# else:
# if battle.has_air:
# other_battles_air.append(battle)
# else:
# other_battles_ground.append(battle)
#
# cs_battles = cs_battles_priority_air + cs_battles_priority_ground + cs_battles_air + cs_battles_ground
# if only_tp:
# return cs_battles
# deployed_battles = deployed_battles_air + deployed_battles_ground
# other_battles = ally_battles_air + ally_battles_ground + other_battles_air + other_battles_ground
# ret_battles = ret_battles + cs_battles + deployed_battles + other_battles
# return ret_battles
#
# def get_cheap_tp_divisions(self) -> Dict[str, List[Tuple[int, classes.BattleDivision]]]:
# air_divs: List[Tuple[int, classes.BattleDivision]] = []
# ground_divs: List[Tuple[int, classes.BattleDivision]] = []
# check_maverick = self.maverick and self.config.maverick
# for battle in reversed(self.sorted_battles(True, True)):
# for division in battle.div.values():
# is_start_ok = utils.good_timedelta(division.battle.start, timedelta(minutes=-1)) < self.now
# if not division.terrain and is_start_ok and not division.div_end:
# if division.is_air and self.config.air:
# division_medals = self.get_battle_round_data(division)
# medal = division_medals[self.details.citizenship == division.battle.defender.country]
# if not medal:
# air_divs.append((0, division))
# else:
# air_divs.append((medal.get("1").get("raw_value"), division))
# elif not division.is_air and self.config.ground:
# if not division.div == self.division and not check_maverick:
# continue
# division_medals = self.get_battle_round_data(division)
# medal = division_medals[self.details.citizenship == division.battle.defender.country]
# if not medal:
# ground_divs.append((0, division))
# else:
# ground_divs.append((medal.get("1").get("raw_value"), division))
#
# air_divs.sort(key=lambda z: (z[0], z[1].battle.start))
# ground_divs.sort(key=lambda z: (z[0], z[1].battle.start))
# return {"air": air_divs, "ground": ground_divs}
#
# @property
# def has_battle_contribution(self):
# return bool(self.__last_war_update_data.get("citizen_contribution", []))
#
# def find_battle_to_fight(
# self, silent: bool = False
# ) -> Tuple[classes.Battle, classes.BattleDivision, classes.BattleSide]:
# self.update_war_info()
# for battle in self.sorted_battles(self.config.sort_battles_time):
# if not isinstance(battle, classes.Battle):
# continue
# if battle.is_dict_lib:
# continue
# battle_zone: Optional[classes.BattleDivision] = None
# for div in battle.div.values():
# if div.terrain == 0:
# if div.div_end:
# continue
# maverick_ok = self.maverick and self.config.maverick
# if self.config.air and div.is_air:
# battle_zone = div
# break
# elif self.config.ground and not div.is_air and (div.div == self.division or maverick_ok):
# battle_zone = div
# break
# else:
# continue
# if not battle_zone:
# continue
# allies = (
# battle.invader.deployed + battle.defender.deployed + [battle.invader.country, battle.defender.country]
# )
#
# travel_needed = self.details.current_country not in allies
#
# if battle.is_rw:
# side = battle.defender if self.config.rw_def_side else battle.invader
# else:
# defender_side = self.details.current_country in battle.defender.allies + [
# battle.defender.country,
# ]
# side = battle.defender if defender_side else battle.invader
#
# if not silent:
# self.write_log(str(battle))
#
# travel = (
# (self.config.travel_to_fight and self.should_travel_to_fight() or self.config.force_travel)
# if travel_needed
# else True
# )
#
# if not travel:
# continue
# yield battle, battle_zone, side
#
# def find_battle_and_fight(self):
# count = self.should_fight()[0]
# if count:
# self.write_log("Checking for battles to fight in...")
# for battle, division, side in self.find_battle_to_fight():
#
# allies = (
# battle.invader.deployed
# + battle.defender.deployed
# + [battle.invader.country, battle.defender.country]
# )
#
# travel_needed = self.details.current_country not in allies
#
# if battle.start > self.now:
# self.sleep(utils.get_sleep_seconds(battle.start))
#
# if travel_needed and not self.change_division(battle, division, side):
# break
#
# if self.change_division(battle, division):
# self.set_default_weapon(battle, division)
# self.fight(battle, division, side, count)
# self.travel_to_residence()
# break
#
# def fight(
# self,
# battle: classes.Battle,
# division: classes.BattleDivision,
# side: classes.BattleSide = None,
# count: int = None,
# ) -> Optional[int]:
# """Fight in a battle.
#
# Will auto activate booster and travel if allowed to do it.
# :param battle: Battle battle to fight in
# :type battle: Battle
# :param division: Division number to fight in available choices
# :type division: BattleDivision
# :param side: BattleSide or None. Battle side to fight in, If side not == invader id or not in invader deployed
# allies list, then defender's side is chosen
# :type side: BattleSide
# :param count: How many hits to do, if not specified self.should_fight() is called.
# :type count: int
# :param use_ebs: Should use energy bars if count > 0 and not enough food_fights
# :type use_ebs: bool
# :return: None if no errors while fighting, otherwise error count.
# :rtype: int
# """
# if self.restricted_ip:
# self.write_warning("Fighting is not allowed from restricted IP!")
# 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_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
# )
# if count is None:
# count = self.should_fight()[0]
#
# self.write_log(f"Fighting in battle for {battle.region_name} on {side} side in d{division.div}")
#
# if self.now < utils.localize_dt(datetime(2021, 2, 8)):
# error_count = total_damage = total_hits = 0
# ok_to_fight = True
# while ok_to_fight and error_count < 10 and count > 0:
# while all((count > 0, error_count < 10, self.energy.energy >= 50)):
# hits, error, damage = self._shoot(battle, division, side)
# count -= hits
# total_hits += hits
# total_damage += damage
# error_count += error
# else:
# if self.energy.energy < 50 or error_count >= 10 or count <= 0:
# self.write_log(f"Hits: {total_hits:>4} | Damage: {total_damage}")
# ok_to_fight = False
# if total_damage:
# self.report_fighting(battle, not side.is_defender, division, total_damage, total_hits)
# return error_count
# else:
# deployment_id = self.deploy(division, side, count * 10)
# self.sleep(count // 3) # TODO: connect to eRepublik's WS and get from there when deploy ends
# energy_used = 0
# if deployment_id:
# self.write_warning(
# "If eRepublik responds with HTTP 500 Internal Error, "
# "it is kind of ok, because deployment has not finished yet."
# )
# deployment_data = self._post_military_fight_deploy_deploy_report_data(deployment_id).json()
# if not deployment_data.get("error"):
# data = deployment_data["data"]
# total_damage = int(data["damage"].replace(",", ""))
# energy_used = int(data["energySpent"].replace(",", ""))
# self.details.pp += int(data["rewards"]["prestigePoints"].replace(",", ""))
# self.report_fighting(battle, not side.is_defender, division, total_damage, energy_used // 10)
# return energy_used
#
# def _shoot(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide):
# if division.is_air:
# response = self._post_military_fight_air(battle.id, side.id, division.id)
# else:
# response = self._post_military_fight_ground(battle.id, side.id, division.id)
#
# if "Zone is not meant for " in response.text:
# self.sleep(5)
# return 0, 1, 0
# try:
# r_json = response.json()
# except (ValueError, HTTPError, RequestException):
# return 0, 10, 0
# hits = 0
# damage = 0
# err = False
# if r_json.get("error"):
# if r_json.get("message") == "SHOOT_LOCKOUT":
# pass
# elif r_json.get("message") == "NOT_ENOUGH_WEAPONS":
# self.set_default_weapon(battle, division)
# elif r_json.get("message") == "Cannot activate a zone with a non-native division":
# self.write_warning("Wrong division!!")
# return 0, 10, 0
# elif r_json.get("message") == "ZONE_INACTIVE":
# self.write_warning("Wrong division!!")
# return 0, 10, 0
# elif r_json.get("message") == "NON_BELLIGERENT":
# self.write_warning("Dictatorship/Liberation wars are not supported!")
# return 0, 10, 0
# elif r_json.get("message") in ["FIGHT_DISABLED", "DEPLOYMENT_MODE"]:
# self._post_main_profile_update(
# "options", params='{"optionName":"enable_web_deploy","optionValue":"off"}'
# )
# self.set_default_weapon(battle, division)
# else:
# if r_json.get("message") == "UNKNOWN_SIDE":
# self._rw_choose_side(battle, side)
# elif r_json.get("message") == "CHANGE_LOCATION":
# countries = [side.country] + side.deployed
# self.travel_to_battle(battle, countries)
# err = True
# elif r_json.get("message") == "ENEMY_KILLED":
# # Non-InfantryKit players
# if r_json["user"]["earnedXp"]:
# hits = r_json["user"]["earnedXp"]
# # InfantryKit player
# # The almost always safe way (breaks on levelup hit)
# elif self.energy.energy >= r_json["details"]["wellness"]: # Haven't reached levelup
# hits = (self.energy.energy - r_json["details"]["wellness"]) // 10
# else:
# hits = r_json["hits"]
# if r_json["user"]["epicBattle"]:
# hits /= 1 + r_json["user"]["epicBattle"]
#
# self.energy.energy = 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)
# else:
# err = True
#
# return hits, err, damage
#
# 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: classes.Battle
# :param division: BattleDivision
# :type division: classes.BattleDivision
# :param bomb_id: int bomb id
# :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
# """
#
# if not isinstance(count, int) or count < 1:
# count = 1
# has_traveled = False
# if battle.is_rw:
# has_traveled = self.travel_to_battle(battle, [battle.defender.country])
# self._rw_choose_side(battle, battle.invader if inv_side else battle.defender)
# if inv_side:
# 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)
# 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, division.id, side.id, bomb_id).json()
# if not r.get("error"):
# deployed_count += 1
# self.sleep(0.5)
# elif r.get("message") == "LOCKED":
# self.sleep(0.5)
# elif r.get("message") == "INVALID_BOMB":
# errors = 10
# else:
# errors += 1
#
# if has_traveled:
# self.travel_to_residence()
#
# self._report_action("MILITARY_BOMB", f"Deployed {deployed_count} bombs in battle {battle.id}")
# return deployed_count
#
# def change_division(
# self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None
# ) -> bool:
# """Change division.
#
# :param battle: classes.Battle
# :type battle: classes.Battle
# :param division: int target division to switch to
# :type division: classes.BattleDivision
# :param side: Side to choose
# :type side: classes.BattleSide
# :return:
# """
# resp = self._post_main_battlefield_change_division(battle.id, division.id, side.id if side else None)
# if resp.json().get("error"):
# self.write_warning(resp.json().get("message"))
# return False
# self._report_action(
# "MILITARY_DIV_SWITCH", f"Switched to d{division.div} in battle {battle.id}", kwargs=resp.json()
# )
# return True
#
# def get_ground_hit_dmg_value(
# self,
# rang: int = None,
# strength: float = None,
# elite: bool = None,
# ne: bool = False,
# booster_50: bool = False,
# booster_100: bool = False,
# tp: bool = True,
# ) -> Decimal:
# if not rang or not strength or elite is None:
# r = self._get_main_citizen_profile_json(self.details.citizen_id).json()
# if not rang:
# rang = r["military"]["militaryData"]["ground"]["rankNumber"]
# if not strength:
# strength = r["military"]["militaryData"]["ground"]["strength"]
# if elite is None:
# elite = r["citizenAttributes"]["level"] > 100
# if ne:
# tp = True
#
# return utils.calculate_hit(strength, rang, tp, elite, ne, 50 if booster_50 else 100 if booster_100 else 0)
#
# def get_air_hit_dmg_value(
# self, rang: int = None, elite: bool = None, ne: bool = False, weapon: bool = False
# ) -> Decimal:
# if not rang or elite is None:
# r = self._get_main_citizen_profile_json(self.details.citizen_id).json()
# if not rang:
# rang = r["military"]["militaryData"]["aircraft"]["rankNumber"]
# if elite is None:
# elite = r["citizenAttributes"]["level"] > 100
#
# return utils.calculate_hit(0, rang, True, elite, ne, 0, 20 if weapon else 0)
#
# def activate_damage_booster(self, ground: bool = True) -> int:
# kind = "damage" if ground else "aircraftDamage"
# 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
# break
# if booster:
# kind = "damage" if ground else "air_damage"
# self._report_action("MILITARY_BOOSTER", f"Activated {booster['name']}")
# resp = self._post_economy_activate_booster(booster["quality"], booster["durability"], kind).json()
# self._update_inventory_data(resp)
# return self.get_active_damage_booster(ground)
#
# def get_active_damage_booster(self, ground: bool = True) -> int:
# kind = "damage" if ground else "aircraftDamage"
# boosters = self.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:
# 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) -> bool:
# self._report_action("MILITARY_BOOSTER", f"Activated {kind} booster")
# resp = self._post_main_activate_battle_effect(battle_id, kind, self.details.citizen_id).json()
# return not resp.get("error")
#
# def activate_pp_booster(self, pp_item: types.InvFinalItem) -> bool:
# self._report_action("MILITARY_BOOSTER", f'Activated {pp_item["name"]}')
# resp = self._post_economy_activate_booster(
# pp_item["quality"], pp_item["durability"], "prestige_points"
# ).json()
# self._update_inventory_data(resp)
# return pp_item.get("kind") in self.inventory.active
#
# def _rw_choose_side(self, battle: classes.Battle, side: classes.BattleSide) -> Response:
# return self._post_main_battlefield_travel(side.id, battle.id)
#
# def should_travel_to_fight(self) -> bool:
# ret = False
# if self.config.always_travel:
# ret = True
# elif self.should_do_levelup: # Do levelup
# ret = True
# # Get to next Energy +1
# elif self.next_reachable_energy and self.config.next_energy:
# ret = True
# # 1h worth of energy
# elif self.energy.energy + self.energy.interval * 3 >= self.energy.limit:
# ret = True
# return ret
#
# def should_fight(self) -> Tuple[int, str, bool]:
# """Checks if citizen should fight at this moment
# :rtype: Tuple[int, str, bool]
# """
# count = 0
# force_fight = False
# msg = "Fighting not allowed!"
# if not self.config.fight:
# return count, msg, force_fight
#
# # Do levelup
# if self.is_levelup_reachable:
# msg = "Level up"
# if self.should_do_levelup:
# count = (self.energy.limit * 3) // 10
# force_fight = True
# else:
# self.write_log("Waiting for fully recovered energy before leveling up.")
#
# # Levelup reachable
# elif self.is_levelup_close:
# count = self.details.xp_till_level_up - (self.energy.limit // 10) + 5
# msg = "Fighting for close Levelup. Doing %i hits" % count
# force_fight = True
#
# elif self.details.pp < 75:
# count = 75 - self.details.pp
# msg = "Obligatory fighting for at least 75pp"
# force_fight = True
#
# elif self.config.continuous_fighting and self.has_battle_contribution:
# count = self.energy.food_fights
# msg = "Continuing to fight in previous battle"
#
# # All-in (type = all-in and full ff)
# elif self.config.all_in and self.energy.energy + self.energy.interval * 3 >= self.energy.limit:
# count = self.energy.food_fights
# msg = "Fighting all-in. Doing %i hits" % count
#
# # Get to next Energy +1
# elif self.config.next_energy and self.next_reachable_energy:
# count = self.next_reachable_energy
# msg = "Fighting for +1 energy. Doing %i hits" % count
#
# # 1h worth of energy
# elif self.energy.energy + self.energy.interval * 3 >= self.energy.limit:
# count = self.energy.interval
# msg = "Fighting for 1h energy. Doing %i hits" % count
# force_fight = True
#
# return (count if count > 0 else 0), msg, force_fight
#
# def get_battle_round_data(self, division: classes.BattleDivision) -> Tuple[Any, Any]:
# battle = division.battle
#
# r = self._post_military_battle_console(
# battle.id,
# "battleStatistics",
# 1,
# zoneId=battle.zone_id,
# round_id=battle.zone_id,
# division=division.div,
# battleZoneId=division.id,
# type="damage",
# )
# r_json = r.json()
# return r_json.get(str(battle.invader.id)).get("fighterData"), r_json.get(str(battle.defender.id)).get(
# "fighterData"
# )
#
# def get_battle_division_stats(self, division: classes.BattleDivision) -> Dict[str, Any]:
# battle = division.battle
# r = self._get_military_battle_stats(battle.id, division.div, division.id)
# return r.json()
#
# def get_division_max_hit(self, division: classes.BattleDivision) -> int:
# """Returns max hit in division for current side (if not on either side returns 0)
#
# :param division: BattleDivision for which to get max hit value
# :type division: classes.BattleDivision
# :return: max hit value
# :rtype: int
# """
# return self.get_battle_division_stats(division).get("maxHit", -1)
#
# def schedule_attack(self, war_id: int, region_id: int, region_name: str, at_time: datetime):
# if at_time:
# self.sleep(utils.get_sleep_seconds(at_time))
# self.get_csrf_token()
# self.launch_attack(war_id, region_id, region_name)
#
# def get_active_wars(self, country: constants.Country = None) -> List[int]:
# r = self._get_country_military(country.link if country else self.details.citizenship.link)
# all_war_ids = re.findall(r'//www\.erepublik\.com/en/wars/show/(\d+)"', r.text)
# return [int(wid) for wid in all_war_ids]
#
# def get_last_battle_of_war_end_time(self, war_id: int) -> datetime:
# r = self._get_wars_show(war_id)
# html = r.text
# last_battle_id = int(re.search(
# r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)">', html
# ).group(1))
# console = self._post_military_battle_console(last_battle_id, "warList", 1).json()
# battle = console.get("list")[0]
# return utils.localize_dt(datetime.strptime(battle.get("result").get("end"), "%Y-%m-%d %H:%M:%S"))
#
# def launch_attack(self, war_id: int, region_id: int, region_name: str):
# self._post_wars_attack_region(war_id, region_id, region_name)
# self._report_action("MILITARY_QUEUE_ATTACK", f"Battle for *{region_name}* queued")
#
# def get_country_mus(self, country: constants.Country) -> Dict[int, str]:
# ret = {}
# r = self._get_main_leaderboards_damage_rankings(country.id)
# for data in r.json()["mu_filter"]:
# if data["id"]:
# ret.update({data["id"]: data["name"]})
# r = self._get_main_leaderboards_damage_aircraft_rankings(country.id)
# for data in r.json()["mu_filter"]:
# if data["id"]:
# ret.update({data["id"]: data["name"]})
# return ret
#
# def get_mu_members(self, mu_id: int) -> Dict[int, str]:
# ret = {}
# r = self._get_military_unit_data(mu_id)
#
# for page in range(int(r.json()["panelContents"]["pages"])):
# r = self._get_military_unit_data(mu_id, currentPage=page + 1)
# for user in r.json()["panelContents"]["members"]:
# if not user["isDead"]:
# ret.update({user["citizenId"]: user["name"]})
# return ret
#
# def get_citizen_weekly_daily_orders_done(self, citizen_id: int = None, weeks_ago: int = 0) -> int:
# if citizen_id is None:
# citizen_id = self.details.citizen_id
# profile = self._get_main_citizen_profile_json(citizen_id).json()
# mu_id = profile.get("military", {}).get("militaryUnit", {}).get("id", 0)
# if mu_id:
# name = profile.get("citizen", {}).get("name")
# member = self._get_military_unit_data(
# mu_id,
# currentPage=1,
# panel="members",
# sortBy="dailyOrdersCompleted",
# weekFilter=f"week{weeks_ago}",
# search=name,
# ).json()
# return member.get("panelContents", {}).get("members", [{}])[0].get("dailyOrdersCompleted")
# return 0
#
# def get_possibly_empty_medals(self):
# self.update_war_info()
# for battle in self.all_battles.values():
# for division in battle.div.values():
# if division.wall["dom"] == 50 or division.wall["dom"] > 98:
# yield division, division.wall["for"] == battle.invader.country.id
#
# def report_fighting(
# self, battle: classes.Battle, invader: bool, division: classes.BattleDivision, damage: float, hits: int
# ):
# self.reporter.report_fighting(battle, invader, division, damage, hits)
# if self.config.telegram:
# self.telegram.report_fight(battle, invader, division, damage, hits)
#
# def get_deploy_inventory(self, division: classes.BattleDivision, side: classes.BattleSide):
# ret = self._post_fight_deploy_get_inventory(division.battle.id, side.id, division.id).json()
# # if ret.get('recoverableEnergyBuyFood'):
# # self.buy_food()
# # return self.get_deploy_inventory(division, side)
# if ret.get("captcha"):
# self.do_captcha_challenge()
# if ret.get("error"):
# if ret.get("message") == "Deployment disabled.":
# self._post_main_profile_update(
# "options", params='{"optionName":"enable_web_deploy","optionValue":"on"}'
# )
# return self.get_deploy_inventory(division, side)
# else:
# self.report_error(f"Unable to get deployment inventory because: {ret.get('message')}")
# return ret
#
# def deploy(self, division: classes.BattleDivision, side: classes.BattleSide, energy: int, _retry=0) -> int:
# _energy = int(energy)
# deploy_inv = self.get_deploy_inventory(division, side)
# if not deploy_inv["minEnergy"] <= energy <= deploy_inv["maxEnergy"]:
# return 0
# energy_sources = {}
# source_idx = 0
# recoverable = deploy_inv["recoverableEnergy"]
# for source in reversed(sorted(deploy_inv["energySources"], key=lambda s: (s["type"], s.get("quality", 0)))):
# if source["type"] == "pool":
# _energy -= source["energy"]
# elif source["type"] in ["food", "energy_bar"]:
# recovers = source["energy"] // source["amount"]
# amount = (recoverable if source["type"] == "food" else _energy) // recovers
# amount = amount if amount < source["amount"] else source["amount"]
# if amount > 0:
# energy_sources.update({f"energySources[{source_idx}][quality]": source["quality"]})
# energy_sources.update({f"energySources[{source_idx}][amount]": amount})
# source_idx += 1
# used_energy = amount * recovers
# recoverable -= used_energy
# _energy -= used_energy
# if _energy <= 0:
# break
# if _energy > 0:
# energy -= _energy
# weapon_q = -1
# weapon_strength = 0
# if not division.is_air:
# for weapon in sorted(deploy_inv["weapons"], key=lambda w: w["damageperHit"]):
# if (weapon["damageperHit"] or 0) > weapon_strength and (weapon["amount"] or 0) > 50:
# weapon_q = weapon["quality"]
# r = self._post_fight_deploy_start_deploy(
# division.battle.id, side.id, division.id, energy, weapon_q, **energy_sources
# ).json()
# if r.get("error"):
# self.report_error(f"Deploy failed: '{r.get('message')}'")
# if r.get("message") == "Deployment disabled.":
# self._post_main_profile_update(
# "options", params='{"optionName":"enable_web_deploy","optionValue":"on"}'
# )
# if _retry < 5:
# return self.deploy(division, side, energy, _retry + 1)
# else:
# self.report_error("Unable to deploy 5 times!")
# return 0
# return r.get("deploymentId")
# def should_fight(self, silent: bool = True) -> Tuple[int, str, bool]:
# if not hasattr(super, "should_fight"):
# return 0, "Unable to fight", False
# count, log_msg, force_fight = super().should_fight()
#
# if count > 0 and not force_fight:
# if self.energy.food_fights - self.my_companies.ff_lockdown < count:
# log_msg = (
# f"Fight count modified (old count: {count} | FF: {self.energy.food_fights} | "
# f"WAM ff_lockdown: {self.my_companies.ff_lockdown} |"
# f" New count: {count - self.my_companies.ff_lockdown})"
# )
# count -= self.my_companies.ff_lockdown
# if count <= 0:
# count = 0
# log_msg = f"Not fighting because WAM needs {self.my_companies.ff_lockdown} food fights"
#
# if self.max_time_till_full_ff > self.time_till_week_change:
# max_count = (int(self.time_till_week_change.total_seconds()) // 360 * self.energy.interval) // 10
# log_msg = (
# f"End for Weekly challenge is near (Recoverable until WC end {max_count}hp | want to do {count}hits)"
# )
# count = count if max_count > count else max_count
#
# if not silent:
# self.write_log(log_msg)
#
# return count, log_msg, force_fight

11
erepublik/__init__.py Normal file
View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
"""Top-level package for eRepublik script."""
__author__ = """Eriks Karls"""
__email__ = "eriks@72.lv"
__version__ = "0.29.2.3"
from erepublik.citizen import Citizen
__all__ = ["Citizen", "__version__"]

214
erepublik/_logging.py Normal file
View File

@ -0,0 +1,214 @@
import base64
import datetime
import inspect
import logging
import os
import sys
import weakref
from logging import LogRecord, handlers
from pathlib import Path
from typing import Any, Dict, Union
import requests
from erepublik.classes import Reporter
from erepublik.constants import erep_tz
from erepublik.utils import json, json_dumps, json_loads, slugify
class ErepublikFileHandler(handlers.TimedRotatingFileHandler):
_file_path: Path
def __init__(self, filename: str = "log/erepublik.log", *args, **kwargs):
log_path = Path(filename)
self._file_path = log_path
log_path.parent.mkdir(parents=True, exist_ok=True)
at_time = erep_tz.localize(datetime.datetime.now()).replace(hour=0, minute=0, second=0, microsecond=0)
kwargs.update(atTime=at_time)
super().__init__(filename, when="d", *args, **kwargs)
def doRollover(self) -> None:
self._file_path.parent.mkdir(parents=True, exist_ok=True)
super().doRollover()
def emit(self, record: LogRecord) -> None:
self._file_path.parent.mkdir(parents=True, exist_ok=True)
super().emit(record)
class ErepublikLogConsoleHandler(logging.StreamHandler):
def __init__(self, *_):
super().__init__(sys.stdout)
class ErepublikFormatter(logging.Formatter):
"""override logging.Formatter to use an aware datetime object"""
dbg_fmt = "[%(asctime)s] DEBUG: %(module)s: %(lineno)d: %(msg)s"
info_fmt = "[%(asctime)s] %(msg)s"
default_fmt = "[%(asctime)s] %(levelname)s: %(msg)s"
def converter(self, timestamp: Union[int, float]) -> datetime.datetime:
return datetime.datetime.fromtimestamp(timestamp).astimezone(erep_tz)
def format(self, record: logging.LogRecord) -> str:
"""
Format the specified record as text.
The record's attribute dictionary is used as the operand to a
string formatting operation which yields the returned string.
Before formatting the dictionary, a couple of preparatory steps
are carried out. The message attribute of the record is computed
using LogRecord.getMessage(). If the formatting string uses the
time (as determined by a call to usesTime(), formatTime() is
called to format the event time. If there is exception information,
it is formatted using formatException() and appended to the message.
"""
if record.levelno == logging.DEBUG:
self._fmt = self.dbg_fmt
elif record.levelno == logging.INFO:
self._fmt = self.info_fmt
else:
self._fmt = self.default_fmt
self._style = logging.PercentStyle(self._fmt)
record.message = record.getMessage()
if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
s = self.formatMessage(record)
if record.exc_info:
# Cache the traceback text to avoid converting it multiple times
# (it's constant anyway)
if not record.exc_text:
record.exc_text = self.formatException(record.exc_info)
return s
def formatTime(self, record, datefmt=None):
dt = self.converter(record.created)
if datefmt:
s = dt.strftime(datefmt)
else:
s = dt.strftime("%Y-%m-%d %H:%M:%S")
return s
def usesTime(self):
return self._style.usesTime()
class ErepublikErrorHTTTPHandler(handlers.HTTPHandler):
def __init__(self, reporter: Reporter):
logging.Handler.__init__(self, level=logging.ERROR)
self._reporter = weakref.ref(reporter)
self.host = "erep.lv"
self.url = "/ebot/error/"
self.method = "POST"
self.secure = True
self.credentials = (str(reporter.citizen_id), reporter.key)
self.context = None
@property
def reporter(self):
return self._reporter()
def _get_last_response(self) -> Dict[str, str]:
response = self.reporter.citizen.r
url = response.url
last_index = url.index("?") if "?" in url else len(response.url)
name = slugify(response.url[len(self.reporter.citizen.url) : last_index])
html = response.text
try:
json_loads(html)
ext = "json"
except json.decoder.JSONDecodeError:
ext = "html"
try:
resp_time = (
datetime.datetime.strptime(response.headers.get("date"), "%a, %d %b %Y %H:%M:%S %Z")
.replace(tzinfo=datetime.timezone.utc)
.astimezone(erep_tz)
.strftime("%F_%H-%M-%S")
)
except: # noqa
resp_time = slugify(response.headers.get("date"))
return dict(
name=f"{resp_time}_{name}.{ext}",
content=html.encode("utf-8"),
mimetype="application/json" if ext == "json" else "text/html",
)
def _get_local_vars(self) -> str:
trace = inspect.trace()
local_vars = {}
if trace:
local_vars = trace[-1][0].f_locals
if local_vars.get("__name__") == "__main__":
local_vars.update(
commit_id=local_vars.get("COMMIT_ID"),
interactive=local_vars.get("INTERACTIVE"),
version=local_vars.get("__version__"),
config=local_vars.get("CONFIG"),
)
else:
stack = inspect.stack()
report_error_caller_found = False
for frame in stack:
if report_error_caller_found:
local_vars = frame.frame.f_locals
break
if "report_error" in str(frame.frame):
report_error_caller_found = True
if "state_thread" in local_vars:
local_vars.pop("state_thread", None)
from erepublik import Citizen
if isinstance(local_vars.get("self"), Citizen):
local_vars["self"] = repr(local_vars["self"])
if isinstance(local_vars.get("player"), Citizen):
local_vars["player"] = repr(local_vars["player"])
if isinstance(local_vars.get("citizen"), Citizen):
local_vars["citizen"] = repr(local_vars["citizen"])
return json_dumps(local_vars)
def _get_instance_json(self) -> str:
if self.reporter:
return self.reporter.citizen.to_json(False)
return ""
def mapLogRecord(self, record: logging.LogRecord) -> Dict[str, Any]:
data = super().mapLogRecord(record)
# Log last response
resp = self._get_last_response()
files = [
("file", (resp.get("name"), resp.get("content"), resp.get("mimetype"))),
]
files += list(("file", (f, open(f"log/{f}", "rb"))) for f in os.listdir("log") if f.endswith(".log"))
local_vars_json = self._get_local_vars()
if local_vars_json:
files.append(("file", ("local_vars.json", local_vars_json, "application/json")))
instance_json = self._get_instance_json()
if instance_json:
files.append(("file", ("instance.json", instance_json, "application/json")))
data.update(files=files)
return data
def emit(self, record):
"""
Emit a record.
Send the record to the Web server as a percent-encoded dictionary
"""
try:
proto = "https" if self.secure else "http"
u, p = self.credentials
s = "Basic " + base64.b64encode(f"{u}:{p}".encode("utf-8")).strip().decode("ascii")
headers = {"Authorization": s}
data = self.mapLogRecord(record)
files = data.pop("files") if "files" in data else None
requests.post(f"{proto}://{self.host}{self.url}", headers=headers, data=data, files=files)
except Exception:
self.handleError(record)

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]]]]

886
erepublik/access_points.py Normal file
View File

@ -0,0 +1,886 @@
import datetime
import hashlib
import random
import time
from typing import Any, Dict, List, Mapping, Union
from requests import Response, Session
from requests.exceptions import ConnectionError
from requests_toolbelt.utils import dump
from erepublik import constants, utils
__all__ = ["SlowRequests", "CitizenAPI"]
class SlowRequests(Session):
last_time: datetime.datetime
timeout: datetime.timedelta = datetime.timedelta(milliseconds=500)
debug: bool = False
def __init__(self, proxies: Dict[str, str] = None, user_agent: str = None):
super().__init__()
if proxies:
self.proxies = proxies
if user_agent is None:
user_agent = self.get_random_user_agent()
self.request_log_name = utils.get_file(utils.now().strftime("debug/requests_%Y-%m-%d.log"))
self.last_time = utils.now()
self.headers.update({"User-Agent": user_agent})
self.hooks["response"] = [self._log_response]
@property
def as_dict(self):
return dict(
last_time=self.last_time,
timeout=self.timeout,
cookies=self.cookies.get_dict(),
debug=self.debug,
user_agent=self.headers["User-Agent"],
request_log_name=self.request_log_name,
proxies=self.proxies,
)
def request(self, method, url, *args, **kwargs):
self._slow_down_requests()
self._log_request(url, method, **kwargs)
try:
resp = super().request(method, url, *args, **kwargs)
except ConnectionError:
time.sleep(1)
return self.request(method, url, *args, **kwargs)
# self._log_response(resp)
return resp
def _slow_down_requests(self):
ltt = utils.good_timedelta(self.last_time, self.timeout)
if ltt > utils.now():
seconds = (ltt - utils.now()).total_seconds()
time.sleep(seconds if seconds > 0 else 0)
self.last_time = utils.now()
def _log_request(self, url, method, data=None, json=None, params=None, **kwargs):
if self.debug:
args = {}
kwargs.pop("allow_redirects", None)
if kwargs:
args.update({"kwargs": kwargs})
if data:
args.update({"data": data})
if json:
args.update({"json": json})
if params:
args.update({"params": params})
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"))
def _log_response(self, response: Response, *args, **kwargs):
redirect = kwargs.get("redirect")
url = response.request.url
if self.debug:
if response.history and not redirect:
for hist_resp in response.history:
self._log_request(hist_resp.request.url, "REDIRECT")
self._log_response(hist_resp, redirect=True)
fd_path = "debug/requests"
fd_time = self.last_time.strftime("%Y/%m/%d/%H-%M-%S")
fd_name = utils.slugify(url[len(CitizenBaseAPI.url) :])
fd_extra = "_REDIRECT" if redirect else ""
try:
utils.json.loads(response.text)
fd_ext = "json"
except utils.json.JSONDecodeError:
fd_ext = "html"
filename = f"{fd_path}/{fd_time}_{fd_name}{fd_extra}.{fd_ext}"
utils.write_file(filename, response.text)
if not redirect:
data = dump.dump_all(response)
utils.write_file(f"debug/dumps/{fd_time}_{fd_name}{fd_extra}.{fd_ext}.dump", data.decode("utf8"))
@staticmethod
def get_random_user_agent() -> str:
windows_x64 = "Windows NT 10.0; Win64; x64"
linux_x64 = "X11; Linux x86_64"
android = [] # [f"Android {version}; Mobile" for version in range(10, 13)]
firefox_template = "Mozilla/5.0 ({osystem}; rv:{version}.0) Gecko/20100101 Firefox/{version}.0"
firefox_versions = range(92, 97)
chrome_template = (
"Mozilla/5.0 ({osystem}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version} Safari/537.36"
)
chrome_versions = ["92.0.4515.159", "93.0.4577.82", "94.0.4606.81", "95.0.4638.54", "96.0.4664.110"]
uas = []
for osystem in [windows_x64, linux_x64, *android]:
for version in firefox_versions:
uas.append(firefox_template.format(osystem=osystem, version=version))
for version in chrome_versions:
uas.append(chrome_template.format(osystem=osystem, version=version))
return random.choice(uas)
class CitizenBaseAPI:
url: str = "https://www.erepublik.com/en"
_req: SlowRequests
token: str
def __init__(self):
"""Class for unifying eRepublik known endpoints and their required/optional parameters"""
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)
def get(self, url: str, **kwargs) -> Response:
return self._req.get(url, **kwargs)
def _get_main(self) -> Response:
return self.get(self.url)
def set_socks_proxy(self, host: str, port: int, username: str = None, password: str = None):
url = f"socks5://{username}:{password}@{host}:{port}" if username and password else f"socks5://{host}:{port}"
self._req.proxies = dict(http=url, https=url)
def set_http_proxy(self, host: str, port: int, username: str = None, password: str = None):
url = f"http://{username}:{password}@{host}:{port}" if username and password else f"http://{host}:{port}"
self._req.proxies = dict(http=url)
def _get_main_session_captcha(self) -> Response:
return self.get(f"{self.url}/main/sessionCaptcha")
def _get_main_session_unlock_popup(self) -> Response:
return self.get(f"{self.url}/main/sessionUnlockPopup")
def _post_main_session_get_challenge(self, captcha_id: int, image_id: str = "") -> Response:
c = [cookie.name for cookie in self._req.cookies if not cookie.has_nonstandard_attr("HttpOnly")]
env = dict(l=["tets"], s=[], c=c, m=0)
data = dict(_token=self.token, captchaId=captcha_id, env=utils.b64json(env))
if image_id:
data.update(imageId=image_id, isRefresh=True)
return self.post(f"{self.url}/main/sessionGetChallenge", data=data)
def _post_main_session_unlock(
self, captcha_id: int, image_id: str, challenge_id: str, coords: List[Dict[str, int]], src: str
) -> Response:
c = [cookie.name for cookie in self._req.cookies if not cookie.has_nonstandard_attr("HttpOnly")]
env = dict(l=["tets"], s=[], c=c, m=0)
session_hash = hashlib.sha256(",".join(env["l"] + env["s"]).encode("utf8")).hexdigest()
cookies_hash = hashlib.sha256(",".join(env["c"]).encode("utf8")).hexdigest()
cookie_kwargs = dict(
expires=int(time.time()) + 120,
path="/en/main/sessionUnlock",
domain=".www.erepublik.com",
secure=True,
rest={"HttpOnly": True},
)
self._req.cookies.set("sh", session_hash, **cookie_kwargs)
self._req.cookies.set("ch", cookies_hash, **cookie_kwargs)
b64_env = utils.b64json(env)
data = dict(
_token=self.token,
captchaId=captcha_id,
imageId=image_id,
challengeId=challenge_id,
clickMatrix=utils.json_dumps(coords).replace(" ", ""),
isMobile=0,
env=b64_env,
src=src,
)
return self.post(
f"{self.url}/main/sessionUnlock",
data=data,
json=data,
headers={"X-Requested-With": "XMLHttpRequest", "Referer": "https://www.erepublik.com/en"},
)
def _post_energy_refill_get_inventory(self):
return self.post(f"{self.url}/economy/energyRefill-getInventory", data={"_token": self.token})
class ErepublikAnniversaryAPI(CitizenBaseAPI):
def _post_main_collect_anniversary_reward(self) -> Response:
return self.post(f"{self.url}/main/collect-anniversary-reward", data={"_token": self.token})
# 12th anniversary endpoints
def _get_anniversary_quest_data(self) -> Response:
return self.get(f"{self.url}/main/anniversaryQuestData")
def _post_map_rewards_unlock(self, node_id: int) -> Response:
data = {"nodeId": node_id, "_token": self.token}
return self.post(f"{self.url}/main/map-rewards-unlock", data=data)
def _post_map_rewards_speedup(self, node_id: int, currency_amount: int) -> Response:
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, 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:
return self.post(f"{self.url}/main/wheeloffortune-spin", data={"_token": self.token, "_currentCost": cost})
def _post_main_wheel_of_fortune_build(self) -> Response:
return self.post(f"{self.url}/main/wheeloffortune-build", data={"_token": self.token})
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:
data.update({"page": page})
return self.post(f"{self.url}/main/articleComments", data=data)
def _post_main_article_comments_create(self, message: str, article_id: int, parent: int = 0) -> Response:
data = dict(_token=self.token, message=message, articleId=article_id)
if parent:
data.update({"parentId": parent})
return self.post(f"{self.url}/main/articleComments/create", data=data)
def _post_main_donate_article(self, article_id: int, amount: int) -> Response:
data = dict(_token=self.token, articleId=article_id, amount=amount)
return self.post(f"{self.url}/main/donate-article", data=data)
def _post_main_write_article(self, title: str, content: str, country_id: int, kind_id: int) -> Response:
data = dict(
_token=self.token,
article_name=title,
article_body=content,
article_location=country_id,
article_category=kind_id,
)
return self.post(f"{self.url}/main/write-article", data=data)
def _post_main_vote_article(self, article_id: int) -> Response:
data = dict(_token=self.token, articleId=article_id)
return self.post(f"{self.url}/main/vote-article", data=data)
class ErepublikCompanyAPI(CitizenBaseAPI):
def _post_economy_assign_to_holding(self, factory_id: int, holding_id: int) -> Response:
data = dict(_token=self.token, factoryId=factory_id, action="assign", holdingCompanyId=holding_id)
return self.post(f"{self.url}/economy/assign-to-holding", data=data)
def _post_economy_create_company(self, industry_id: int, building_type: int = 1) -> Response:
data = {"_token": self.token, "company[industry_id]": industry_id, "company[building_type]": building_type}
return self.post(
f"{self.url}/economy/create-company", data=data, headers={"Referer": f"{self.url}/economy/create-company"}
)
def _get_economy_inventory_items(self) -> Response:
return self.get(f"{self.url}/economy/inventory-items/")
def _get_economy_job_market_json(self, country_id: int) -> Response:
return self.get(f"{self.url}/economy/job-market-json/{country_id}/1/desc")
def _get_economy_my_companies(self) -> Response:
return self.get(f"{self.url}/economy/myCompanies")
def _post_economy_train(self, tg_ids: List[int]) -> Response:
data: Dict[str, Union[int, str]] = {}
for idx, tg_id in enumerate(tg_ids):
data["grounds[%i][id]" % idx] = tg_id
data["grounds[%i][train]" % idx] = 1
if data:
data["_token"] = self.token
return self.post(f"{self.url}/economy/train", data=data)
def _post_economy_upgrade_company(self, factory: int, level: int, pin: str = None) -> Response:
data = dict(_token=self.token, type="upgrade", companyId=factory, level=level, pin="" if pin is None else pin)
return self.post(f"{self.url}/economy/upgrade-company", data=data)
def _post_economy_work(self, action_type: str, wam: List[int] = None, employ: Dict[int, int] = None) -> Response:
data: Dict[str, Union[int, str]] = dict(action_type=action_type, _token=self.token)
if action_type == "production":
if employ is None:
employ = {}
if wam is None:
wam = []
max_idx = 0
for company_id in sorted(wam or []):
data.update(
{
f"companies[{max_idx}][id]": company_id,
f"companies[{max_idx}][employee_works]": employ.pop(company_id, 0),
f"companies[{max_idx}][own_work]": 1,
}
)
max_idx += 1
for company_id in sorted(employ or []):
data.update(
{
f"companies[{max_idx}][id]": company_id,
f"companies[{max_idx}][employee_works]": employ.pop(company_id, 0),
f"companies[{max_idx}][own_work]": 0,
}
)
max_idx += 1
return self.post(f"{self.url}/economy/work", data=data)
def _post_economy_work_overtime(self) -> Response:
data = dict(action_type="workOvertime", _token=self.token)
return self.post(f"{self.url}/economy/workOvertime", data=data)
def _post_economy_job_market_apply(self, citizen_id: int, salary: float) -> Response:
data = dict(_token=self.token, citizenId=citizen_id, salary=salary)
return self.post(f"{self.url}/economy/job-market-apply", data=data)
def _post_economy_resign(self) -> Response:
return self.post(
f"{self.url}/economy/resign",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={"_token": self.token, "action_type": "resign"},
)
def _post_economy_sell_company(self, factory_id: int, pin: int = None, sell: bool = True) -> Response:
data = dict(_token=self.token, pin="" if pin is None else pin)
if sell:
data.update({"sell": "sell"})
else:
data.update({"dissolve": factory_id})
return self.post(f"{self.url}/economy/sell-company/{factory_id}", data=data, headers={"Referer": self.url})
class ErepublikCountryAPI(CitizenBaseAPI):
def _get_country_military(self, country_name: str) -> Response:
return self.get(f"{self.url}/country/military/{country_name}")
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"}
)
class ErepublikEconomyAPI(CitizenBaseAPI):
def _get_economy_citizen_accounts(self, organisation_id: int) -> Response:
return self.get(f"{self.url}/economy/citizen-accounts/{organisation_id}")
def _get_economy_my_market_offers(self) -> Response:
return self.get(f"{self.url}/economy/myMarketOffers")
def _get_main_job_data(self) -> Response:
return self.get(f"{self.url}/main/job-data")
def _post_main_buy_gold_items(self, currency: str, item: str, amount: int) -> Response:
data = dict(itemId=item, currency=currency, amount=amount, _token=self.token)
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, _token=self.token)
return self.post(f"{self.url}/economy/activateBooster", data=data)
def _post_economy_activate_house(self, quality: int) -> Response:
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, quality: int) -> Response:
data = dict(citizen_id=citizen_id, amount=amount, industry_id=industry, quality=quality, _token=self.token)
return self.post(
f"{self.url}/economy/donate-items-action",
data=data,
headers={"Referer": f"{self.url}/economy/donate-items/{citizen_id}"},
)
def _post_economy_donate_money_action(self, citizen_id: int, amount: float = 0.0, currency: int = 62) -> Response:
data = dict(citizen_id=citizen_id, _token=self.token, currency_id=currency, amount=amount)
return self.post(
f"{self.url}/economy/donate-money-action",
data=data,
headers={"Referer": f"{self.url}/economy/donate-money/{citizen_id}"},
)
def _post_economy_exchange_purchase(self, amount: float, currency: int, offer: int) -> Response:
data = dict(_token=self.token, amount=amount, currencyId=currency, offerId=offer)
return self.post(f"{self.url}/economy/exchange/purchase/", data=data)
def _post_economy_exchange_retrieve(self, personal: bool, page: int, currency: int) -> Response:
data = dict(_token=self.token, personalOffers=int(personal), page=page, currencyId=currency)
return self.post(f"{self.url}/economy/exchange/retrieve/", data=data)
def _post_economy_game_tokens_market(self, action: str) -> Response:
assert action in [
"retrieve",
]
data = dict(_token=self.token, action=action)
return self.post(f"{self.url}/economy/gameTokensMarketAjax", data=data)
def _post_economy_marketplace(self, country: int, industry: int, quality: int, order_asc: bool = True) -> Response:
data = dict(
countryId=country,
industryId=industry,
quality=quality,
ajaxMarket=1,
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, 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=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: # 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
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
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
return self.get(f"{self.url}/main/leaderboards-kills-rankings/{country_id}/{weeks}/{mu_id}/{div}")
class ErepublikLocationAPI(CitizenBaseAPI):
def _get_main_city_data_residents(self, city_id: int, page: int = 1, params: Mapping[str, Any] = None) -> Response:
if params is None:
params = {}
return self.get(f"{self.url}/main/city-data/{city_id}/residents", params={"currentPage": page, **params})
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}")
def _get_military_show_weapons(self, battle_id: int) -> Response:
return self.get(f"{self.url}/military/show-weapons", params={"_token": self.token, "battleId": battle_id})
def _get_military_campaigns(self) -> Response:
return self.get(f"{self.url}/military/campaigns-new/")
def _get_military_campaigns_json_list(self) -> Response:
return self.get(f"{self.url}/military/campaignsJson/list")
def _get_military_campaigns_json_citizen(self) -> Response:
return self.get(f"{self.url}/military/campaignsJson/citizen")
def _get_military_unit_data(self, unit_id: int, **kwargs) -> Response:
params = {"groupId": unit_id, "panel": "members", **kwargs}
return self.get(f"{self.url}/military/military-unit-data/", params=params)
def _post_main_activate_battle_effect(self, battle_id: int, kind: str, citizen_id: int) -> Response:
data = dict(battleId=battle_id, citizenId=citizen_id, type=kind, _token=self.token)
return self.post(f"{self.url}/main/fight-activateBattleEffect", data=data)
def _post_main_battlefield_travel(self, side_id: int, battle_id: int) -> Response:
data = dict(_token=self.token, sideCountryId=side_id, battleId=battle_id)
return self.post(f"{self.url}/main/battlefieldTravel", data=data)
def _post_main_battlefield_change_division(self, battle_id: int, division_id: int, side_id: int = None) -> Response:
data = dict(_token=self.token, battleZoneId=division_id, battleId=battle_id)
if side_id is not None:
data.update(sideCountryId=side_id)
return self.post(f"{self.url}/main/battlefieldTravel", data=data)
def _get_wars_show(self, war_id: int) -> Response:
return self.get(f"{self.url}/wars/show/{war_id}")
def _post_military_fight_activate_booster(self, battle_id: int, quality: int, duration: int, kind: str) -> Response:
data = dict(type=kind, quality=quality, duration=duration, battleId=battle_id, _token=self.token)
return self.post(f"{self.url}/military/fight-activateBooster", data=data)
def _post_military_change_weapon(
self,
battle_id: int,
battle_zone: int,
weapon_level: int,
) -> Response:
data = dict(battleId=battle_id, _token=self.token, battleZoneId=battle_zone, customizationLevel=weapon_level)
return self.post(f"{self.url}/military/change-weapon", data=data)
def _post_military_battle_console(self, battle_id: int, action: str, page: int = 1, **kwargs) -> Response:
data = dict(battleId=battle_id, action=action, _token=self.token)
if action == "battleStatistics":
data.update(
round=kwargs["round_id"],
zoneId=kwargs["round_id"],
leftPage=page,
rightPage=page,
division=kwargs["division"],
type=kwargs.get("type", "damage"),
)
elif action == "warList":
data.update(page=page)
return self.post(f"{self.url}/military/battle-console", data=data)
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:
data = dict(sideId=side_id, battleId=battle_id, _token=self.token, battleZoneId=zone_id)
return self.post(f"{self.url}/military/fight-shoooot/{battle_id}", data=data)
def _post_military_fight_ground(self, battle_id: int, side_id: int, zone_id: int) -> Response:
data = dict(sideId=side_id, battleId=battle_id, _token=self.token, battleZoneId=zone_id)
return self.post(f"{self.url}/military/fight-shooot/{battle_id}", data=data)
def _post_fight_deploy_deploy_report_data(self, deployment_id: int) -> Response:
data = dict(_token=self.token, deploymentId=deployment_id)
return self.post(f"{self.url}/military/fightDeploy-deployReportData", data=data)
def _post_fight_deploy_get_inventory(self, battle_id: int, side_id: int, battle_zone_id: int) -> Response:
data = dict(_token=self.token, battleId=battle_id, sideCountryId=side_id, battleZoneId=battle_zone_id)
return self.post(f"{self.url}/military/fightDeploy-getInventory", data=data)
def _post_fight_deploy_start_deploy(
self, battle_id: int, side_id: int, battle_zone_id: int, energy: int, weapon: int, **kwargs
) -> Response:
data = dict(
_token=self.token,
battleId=battle_id,
battleZoneId=battle_zone_id,
sideCountryId=side_id,
weaponQuality=weapon,
totalEnergy=energy,
**kwargs,
)
return self.post(f"{self.url}/military/fightDeploy-startDeploy", data=data)
def _post_military_fight_deploy_deploy_report_data(self, deployment_id: int) -> Response:
data = dict(_token=self.token, deploymentId=deployment_id)
return self.post(f"{self.url}/military/fightDeploy-deployReportData", data=data)
class ErepublikPoliticsAPI(CitizenBaseAPI):
def _get_candidate_party(self, party_slug: str) -> Response:
return self.get(f"{self.url}/candidate/{party_slug}")
def _get_main_party_members(self, party_id: int) -> Response:
return self.get(f"{self.url}/main/party-members/{party_id}")
def _get_main_rankings_parties(self, country_id: int) -> Response:
return self.get(f"{self.url}/main/rankings-parties/1/{country_id}")
def _post_candidate_for_congress(self, presentation: str = "") -> Response:
data = dict(_token=self.token, presentation=presentation)
return self.post(f"{self.url}/candidate-for-congress", data=data)
def _get_presidential_elections(self, country_id: int, timestamp: int) -> Response:
return self.get(f"{self.url}/main/presidential-elections/{country_id}/{timestamp}")
def _post_propose_president_candidate(self, party_slug: str, citizen_id: int) -> Response:
return self.post(
f"{self.url}/propose-president-candidate/{party_slug}", data=dict(_token=self.token, citizen=citizen_id)
)
def _get_auto_propose_president_candidate(self, party_slug: str) -> Response:
return self.get(f"{self.url}/auto-propose-president-candidate/{party_slug}")
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(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,
countryNameConfirm=constants.COUNTRIES[attack_country_id].link,
)
return self.post(f"{self.url}/{constants.COUNTRIES[self_country_id].link}/new-war", data=data)
def _post_new_donation(self, country_id: int, amount: int, org_name: str, debate: str = "") -> Response:
data = dict(
requirments=1,
_token=self.token,
debate=debate,
currency=1,
value=amount,
commit="Propose",
type_name=org_name,
)
return self.post(f"{self.url}/{constants.COUNTRIES[country_id].link}/new-donation", data=data)
class ErepublikProfileAPI(CitizenBaseAPI):
def _get_main_citizen_hovercard(self, citizen_id: int) -> Response:
return self.get(f"{self.url}/main/citizen-hovercard/{citizen_id}")
def _get_main_citizen_profile_json(self, citizen_id: int) -> Response:
return self.get(f"{self.url}/main/citizen-profile-json/{citizen_id}")
def _get_main_citizen_notifications(self) -> Response:
return self.get(f"{self.url}/main/citizenDailyAssistant")
def _get_main_citizen_daily_assistant(self) -> Response:
return self.get(f"{self.url}/main/citizenNotifications")
def _get_main_messages_paginated(self, page: int = 1) -> Response:
return self.get(f"{self.url}/main/messages-paginated/{page}")
def _get_main_money_donation_accept(self, donation_id: int) -> Response:
return self.get(f"{self.url}/main/money-donation/accept/{donation_id}", params={"_token": self.token})
def _get_main_money_donation_reject(self, donation_id: int) -> Response:
return self.get(f"{self.url}/main/money-donation/reject/{donation_id}", params={"_token": self.token})
def _get_main_notifications_ajax_community(self, page: int = 1) -> Response:
return self.get(f"{self.url}/main/notificationsAjax/community/{page}")
def _get_main_notifications_ajax_system(self, page: int = 1) -> Response:
return self.get(f"{self.url}/main/notificationsAjax/system/{page}")
def _get_main_notifications_ajax_report(self, page: int = 1) -> Response:
return self.get(f"{self.url}/main/notificationsAjax/report/{page}")
def _get_main_training_grounds_json(self) -> Response:
return self.get(f"{self.url}/main/training-grounds-json")
def _get_main_weekly_challenge_data(self) -> Response:
return self.get(f"{self.url}/main/weekly-challenge-data")
def _post_main_citizen_add_remove_friend(self, citizen: int, add: bool) -> Response:
data = dict(_token=self.token, citizenId=citizen, url="//www.erepublik.com/en/main/citizen-addRemoveFriend")
if add:
data.update({"action": "addFriend"})
else:
data.update({"action": "removeFriend"})
return self.post(f"{self.url}/main/citizen-addRemoveFriend", data=data)
def _post_main_daily_task_reward(self) -> Response:
return self.post(f"{self.url}/main/daily-tasks-reward", data=dict(_token=self.token))
def _post_delete_message(self, msg_id: list) -> Response:
data = {"_token": self.token, "delete_message[]": msg_id}
return self.post(f"{self.url}/main/messages-delete", data)
def _post_eat(self, color: str) -> Response:
data = dict(_token=self.token, buttonColor=color)
return self.post(f"{self.url}/main/eat", params=data)
def _post_main_global_alerts_close(self, alert_id: int) -> Response:
data = dict(_token=self.token, alert_id=alert_id)
return self.post(f"{self.url}/main/global-alerts/close", data=data)
def _post_forgot_password(self, email: str) -> Response:
data = dict(_token=self.token, email=email, commit="Reset password")
return self.post(f"{self.url}/forgot-password", data=data)
def _post_login(self, email: str, password: str) -> Response:
data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember="on")
return self.post(f"{self.url}/login", data=data)
def _post_main_messages_alert(self, notification_ids: List[int]) -> Response:
data = {"_token": self.token, "delete_alerts[]": notification_ids, "deleteAllAlerts": "1", "delete": "Delete"}
return self.post(f"{self.url}/main/messages-alerts/1", data=data)
def _post_main_notifications_ajax_community(self, notification_ids: List[int], page: int = 1) -> Response:
data = {"_token": self.token, "delete_alerts[]": notification_ids}
return self.post(f"{self.url}/main/notificationsAjax/community/{page}", data=data)
def _post_main_notifications_ajax_system(self, notification_ids: List[int], page: int = 1) -> Response:
data = {"_token": self.token, "delete_alerts[]": notification_ids}
return self.post(f"{self.url}/main/notificationsAjax/system/{page}", data=data)
def _post_main_notifications_ajax_report(self, notification_ids: List[int], page: int = 1) -> Response:
data = {"_token": self.token, "delete_alerts[]": notification_ids}
return self.post(f"{self.url}/main/notificationsAjax/report/{page}", data=data)
def _post_main_messages_compose(self, subject: str, body: str, citizens: List[int]) -> Response:
url_pk = 0 if len(citizens) > 1 else str(citizens[0])
data = dict(
citizen_name=",".join([str(x) for x in citizens]),
citizen_subject=subject,
_token=self.token,
citizen_message=body,
)
return self.post(f"{self.url}/main/messages-compose/{url_pk}", data=data)
def _post_military_group_missions(self) -> Response:
data = dict(action="check", _token=self.token)
return self.post(f"{self.url}/military/group-missions", data=data)
def _post_main_weekly_challenge_reward(self, reward_id: int) -> Response:
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)
class ErepublikTravelAPI(CitizenBaseAPI):
def _post_main_travel(self, check: str, **kwargs) -> Response:
data = dict(_token=self.token, check=check, **kwargs)
return self.post(f"{self.url}/main/travel", data=data)
def _post_main_travel_data(self, **kwargs) -> Response:
return self.post(f"{self.url}/main/travelData", data=dict(_token=self.token, **kwargs))
class ErepublikWallPostAPI(CitizenBaseAPI):
# ## Country
def _post_main_country_comment_retrieve(self, post_id: int) -> Response:
data = {"_token": self.token, "postId": post_id}
return self.post(f"{self.url}/main/country-comment/retrieve/json", data=data)
def _post_main_country_comment_create(self, post_id: int, comment_message: str) -> Response:
data = {"_token": self.token, "postId": post_id, "comment_message": comment_message}
return self.post(f"{self.url}/main/country-comment/create/json", data=data)
def _post_main_country_post_create(self, body: str, post_as: int) -> Response:
data = {"_token": self.token, "post_message": body, "post_as": post_as}
return self.post(f"{self.url}/main/country-post/create/json", data=data)
def _post_main_country_post_retrieve(self) -> Response:
data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post(f"{self.url}/main/country-post/retrieve/json", data=data)
# ## Military Unit
def _post_main_military_unit_comment_retrieve(self, post_id: int) -> Response:
data = {"_token": self.token, "postId": post_id}
return self.post(f"{self.url}/main/military-unit-comment/retrieve/json", data=data)
def _post_main_military_unit_comment_create(self, post_id: int, comment_message: str) -> Response:
data = {"_token": self.token, "postId": post_id, "comment_message": comment_message}
return self.post(f"{self.url}/main/military-unit-comment/create/json", data=data)
def _post_main_military_unit_post_create(self, body: str, post_as: int) -> Response:
data = {"_token": self.token, "post_message": body, "post_as": post_as}
return self.post(f"{self.url}/main/military-unit-post/create/json", data=data)
def _post_main_military_unit_post_retrieve(self) -> Response:
data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post(f"{self.url}/main/military-unit-post/retrieve/json", data=data)
# ## Party
def _post_main_party_comment_retrieve(self, post_id: int) -> Response:
data = {"_token": self.token, "postId": post_id}
return self.post(f"{self.url}/main/party-comment/retrieve/json", data=data)
def _post_main_party_comment_create(self, post_id: int, comment_message: str) -> Response:
data = {"_token": self.token, "postId": post_id, "comment_message": comment_message}
return self.post(f"{self.url}/main/party-comment/create/json", data=data)
def _post_main_party_post_create(self, body: str) -> Response:
data = {"_token": self.token, "post_message": body}
return self.post(f"{self.url}/main/party-post/create/json", data=data)
def _post_main_party_post_retrieve(self) -> Response:
data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post(f"{self.url}/main/party-post/retrieve/json", data=data)
# ## Friend's Wall
def _post_main_wall_comment_retrieve(self, post_id: int) -> Response:
data = {"_token": self.token, "postId": post_id}
return self.post(f"{self.url}/main/wall-comment/retrieve/json", data=data)
def _post_main_wall_comment_create(self, post_id: int, comment_message: str) -> Response:
data = {"_token": self.token, "postId": post_id, "comment_message": comment_message}
return self.post(f"{self.url}/main/wall-comment/create/json", data=data)
def _post_main_wall_post_create(self, body: str) -> Response:
data = {"_token": self.token, "post_message": body}
return self.post(f"{self.url}/main/wall-post/create/json", data=data)
def _post_main_wall_post_retrieve(self) -> Response:
data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post(f"{self.url}/main/wall-post/retrieve/json", data=data)
# ## Medal posting
def _post_main_wall_post_automatic(self, message: str, achievement_id: int) -> Response:
return self.post(
f"{self.url}/main/wall-post/automatic",
data=dict(_token=self.token, message=message, achievementId=achievement_id),
)
class CitizenAPI(
ErepublikArticleAPI,
ErepublikCountryAPI,
ErepublikCompanyAPI,
ErepublikEconomyAPI,
ErepublikLeaderBoardAPI,
ErepublikLocationAPI,
ErepublikMilitaryAPI,
ErepublikProfileAPI,
ErepublikPresidentAPI,
ErepublikPoliticsAPI,
ErepublikAnniversaryAPI,
ErepublikWallPostAPI,
ErepublikTravelAPI,
):
pass

2488
erepublik/citizen.py Normal file

File diff suppressed because it is too large Load Diff

1378
erepublik/classes.py Normal file

File diff suppressed because it is too large Load Diff

666
erepublik/constants.py Normal file
View File

@ -0,0 +1,666 @@
import datetime
from typing import Dict, Optional, Union
import pytz
__all__ = [
"erep_tz",
"min_datetime",
"max_datetime",
"Country",
"Industries",
"Rank",
"AIR_RANKS",
"AIR_RANK_NAMES",
"AIR_RANK_POINTS",
"COUNTRIES",
"FOOD_ENERGY",
"GROUND_RANKS",
"GROUND_RANK_NAMES",
"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:
id: int
name: str
link: str
iso: str
def __init__(self, country_id: int, name: str, link: str, iso: str):
self.id = country_id
self.name = name
self.link = link
self.iso = iso
def __hash__(self):
return hash((self.id, self.name))
def __repr__(self):
return f"Country({self.id}, '{self.name}', '{self.link}', '{self.iso}')"
def __str__(self):
return f"#{self.id} {self.name}"
def __format__(self, format_spec):
return self.iso
def __int__(self):
return self.id
def __eq__(self, other):
if isinstance(other, (int, float)):
return self.id == int(other)
else:
try:
return self.id == int(other)
except ValueError:
return self == other
@property
def as_dict(self):
return dict(id=self.id, name=self.name, iso=self.iso)
class Industries:
__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",
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",
24: "aircraftRaw",
25: "ARM q2",
26: "ARM q3",
27: "ARM q4",
28: "ARM q5",
}
def __getitem__(self, item: Union[int, str]) -> Optional[Union[int, str]]:
if isinstance(item, int):
return self.__by_id.get(item, None)
elif isinstance(item, str):
return self.__by_name.get(item.lower(), None)
return
def __getattr__(self, item) -> Optional[Union[int, str]]:
return self[item]
@property
def as_dict(self):
return dict(by_id=self.__by_id, by_name=self.__by_name)
class Rank:
id: int
name: str
rank_points: int
is_air: bool
def __init__(self, id: int, name: str, rank_points: int, is_air: bool = False):
self.id = id
self.name = name
self.rank_points = rank_points
self.is_air = bool(is_air)
def __int__(self):
return self.id
def __eq__(self, other):
if isinstance(other, Rank):
return self.id == other.id if other.is_air == self.is_air else False
else:
return self.id == int(other)
def __ne__(self, other):
if isinstance(other, Rank):
return not self.id == other.id if other.is_air == self.is_air else True
else:
return not self.id == int(other)
def __lt__(self, other):
if isinstance(other, Rank):
return self.id < other.id if other.is_air == self.is_air else False
else:
return self.id < int(other)
def __le__(self, other):
if isinstance(other, Rank):
return self.id <= other.id if other.is_air == self.is_air else False
else:
return self.id <= int(other)
def __gt__(self, other):
if isinstance(other, Rank):
return self.id > other.id if other.is_air == self.is_air else False
else:
return self.id > int(other)
def __ge__(self, other):
if isinstance(other, Rank):
return self.id >= other.id if other.is_air == self.is_air else False
else:
return self.id >= int(other)
@property
def as_dict(self):
return dict(id=self.id, name=self.name, rank_points=self.rank_points, is_air=self.is_air)
@property
def __dict__(self):
return self.as_dict
def __str__(self):
return f"{'Air' if self.is_air else 'Ground'}Rank<#{self.id} {self.name}>"
def __repr__(self):
return str(self)
AIR_RANK_NAMES: Dict[int, str] = {
1: "Airman",
2: "Airman 1st Class",
3: "Airman 1st Class*",
4: "Airman 1st Class**",
5: "Airman 1st Class***",
6: "Airman 1st Class****",
7: "Airman 1st Class*****",
8: "Senior Airman",
9: "Senior Airman*",
10: "Senior Airman**",
11: "Senior Airman***",
12: "Senior Airman****",
13: "Senior Airman*****",
14: "Staff Sergeant",
15: "Staff Sergeant*",
16: "Staff Sergeant**",
17: "Staff Sergeant***",
18: "Staff Sergeant****",
19: "Staff Sergeant*****",
20: "Aviator",
21: "Aviator*",
22: "Aviator**",
23: "Aviator***",
24: "Aviator****",
25: "Aviator*****",
26: "Flight Lieutenant",
27: "Flight Lieutenant*",
28: "Flight Lieutenant**",
29: "Flight Lieutenant***",
30: "Flight Lieutenant****",
31: "Flight Lieutenant*****",
32: "Squadron Leader",
33: "Squadron Leader*",
34: "Squadron Leader**",
35: "Squadron Leader***",
36: "Squadron Leader****",
37: "Squadron Leader*****",
38: "Chief Master Sergeant",
39: "Chief Master Sergeant*",
40: "Chief Master Sergeant**",
41: "Chief Master Sergeant***",
42: "Chief Master Sergeant****",
43: "Chief Master Sergeant*****",
44: "Wing Commander",
45: "Wing Commander*",
46: "Wing Commander**",
47: "Wing Commander***",
48: "Wing Commander****",
49: "Wing Commander*****",
50: "Group Captain",
51: "Group Captain*",
52: "Group Captain**",
53: "Group Captain***",
54: "Group Captain****",
55: "Group Captain*****",
56: "Air Commodore",
57: "Air Commodore*",
58: "Air Commodore**",
59: "Air Commodore***",
60: "Air Commodore****",
61: "Air Commodore*****",
62: "Air Vice Marshal",
63: "Air Vice Marshal*",
64: "Air Vice Marshal**",
65: "Air Vice Marshal***",
66: "Air Vice Marshal****",
67: "Air Vice Marshal*****",
68: "Air Marshal",
69: "Air Marshal*",
70: "Air Marshal**",
71: "Air Marshal***",
72: "Air Marshal****",
73: "Air Marshal*****",
74: "Air Chief Marshal",
75: "Air Chief Marshal*",
76: "Air Chief Marshal**",
77: "Air Chief Marshal***",
78: "Air Chief Marshal****",
79: "Air Chief Marshal*****",
}
AIR_RANK_POINTS: Dict[int, Optional[int]] = {
1: 0,
2: 10,
3: 25,
4: 45,
5: 70,
6: 100,
7: 140,
8: 190,
9: 270,
10: 380,
11: 530,
12: 850,
13: 1300,
14: 2340,
15: 3300,
16: 4200,
17: 5150,
18: 6100,
19: 7020,
20: 9100,
21: 12750,
22: 16400,
23: 20000,
24: 23650,
25: 27300,
26: 35500,
27: 48000,
28: 60000,
29: 72400,
30: 84500,
31: 97000,
32: 110000,
33: 140000,
34: 170000,
35: 210000,
36: 290000,
37: 350000,
38: 429000,
39: 601000,
40: 772000,
41: 944000,
42: 1115000,
43: 1287000,
44: 1673000,
45: 2238000,
46: 2804000,
47: 3369000,
48: 3935000,
49: 4500000,
50: 5020000,
51: 7028000,
52: 9036000,
53: 11044000,
54: 13052000,
55: 15060000,
56: 19580000,
57: 27412000,
58: 35244000,
59: 43076000,
60: 50908000,
61: 58740000,
62: 76360000,
63: 113166443,
64: 137448000,
65: None,
66: None,
67: None,
68: None,
69: None,
70: None,
71: None,
72: None,
73: None,
74: None,
75: None,
76: None,
77: None,
78: None,
79: None,
}
AIR_RANKS: Dict[int, Rank] = {i: Rank(i, AIR_RANK_NAMES[i], AIR_RANK_POINTS[i], True) for i in range(1, 80)}
COUNTRIES: Dict[int, Country] = {
1: Country(1, "Romania", "Romania", "ROU"),
9: Country(9, "Brazil", "Brazil", "BRA"),
10: Country(10, "Italy", "Italy", "ITA"),
11: Country(11, "France", "France", "FRA"),
12: Country(12, "Germany", "Germany", "DEU"),
13: Country(13, "Hungary", "Hungary", "HUN"),
14: Country(14, "China", "China", "CHN"),
15: Country(15, "Spain", "Spain", "ESP"),
23: Country(23, "Canada", "Canada", "CAN"),
24: Country(24, "USA", "USA", "USA"),
26: Country(26, "Mexico", "Mexico", "MEX"),
27: Country(27, "Argentina", "Argentina", "ARG"),
28: Country(28, "Venezuela", "Venezuela", "VEN"),
29: Country(29, "United Kingdom", "United-Kingdom", "GBR"),
30: Country(30, "Switzerland", "Switzerland", "CHE"),
31: Country(31, "Netherlands", "Netherlands", "NLD"),
32: Country(32, "Belgium", "Belgium", "BEL"),
33: Country(33, "Austria", "Austria", "AUT"),
34: Country(34, "Czech Republic", "Czech-Republic", "CZE"),
35: Country(35, "Poland", "Poland", "POL"),
36: Country(36, "Slovakia", "Slovakia", "SVK"),
37: Country(37, "Norway", "Norway", "NOR"),
38: Country(38, "Sweden", "Sweden", "SWE"),
39: Country(39, "Finland", "Finland", "FIN"),
40: Country(40, "Ukraine", "Ukraine", "UKR"),
41: Country(41, "Russia", "Russia", "RUS"),
42: Country(42, "Bulgaria", "Bulgaria", "BGR"),
43: Country(43, "Turkey", "Turkey", "TUR"),
44: Country(44, "Greece", "Greece", "GRC"),
45: Country(45, "Japan", "Japan", "JPN"),
47: Country(47, "South Korea", "South-Korea", "KOR"),
48: Country(48, "India", "India", "IND"),
49: Country(49, "Indonesia", "Indonesia", "IDN"),
50: Country(50, "Australia", "Australia", "AUS"),
51: Country(51, "South Africa", "South-Africa", "ZAF"),
52: Country(52, "Republic of Moldova", "Republic-of-Moldova", "MDA"),
53: Country(53, "Portugal", "Portugal", "PRT"),
54: Country(54, "Ireland", "Ireland", "IRL"),
55: Country(55, "Denmark", "Denmark", "DNK"),
56: Country(56, "Iran", "Iran", "IRN"),
57: Country(57, "Pakistan", "Pakistan", "PAK"),
58: Country(58, "Israel", "Israel", "ISR"),
59: Country(59, "Thailand", "Thailand", "THA"),
61: Country(61, "Slovenia", "Slovenia", "SVN"),
63: Country(63, "Croatia", "Croatia", "HRV"),
64: Country(64, "Chile", "Chile", "CHL"),
65: Country(65, "Serbia", "Serbia", "SRB"),
66: Country(66, "Malaysia", "Malaysia", "MYS"),
67: Country(67, "Philippines", "Philippines", "PHL"),
68: Country(68, "Singapore", "Singapore", "SGP"),
69: Country(69, "Bosnia and Herzegovina", "Bosnia-Herzegovina", "BiH"),
70: Country(70, "Estonia", "Estonia", "EST"),
80: Country(80, "Montenegro", "Montenegro", "MNE"),
71: Country(71, "Latvia", "Latvia", "LVA"),
72: Country(72, "Lithuania", "Lithuania", "LTU"),
73: Country(73, "North Korea", "North-Korea", "PRK"),
74: Country(74, "Uruguay", "Uruguay", "URY"),
75: Country(75, "Paraguay", "Paraguay", "PRY"),
76: Country(76, "Bolivia", "Bolivia", "BOL"),
77: Country(77, "Peru", "Peru", "PER"),
78: Country(78, "Colombia", "Colombia", "COL"),
79: Country(79, "Republic of Macedonia (FYROM)", "Republic-of-Macedonia-FYROM", "MKD"),
81: Country(81, "Republic of China (Taiwan)", "Republic-of-China-Taiwan", "TWN"),
82: Country(82, "Cyprus", "Cyprus", "CYP"),
167: Country(167, "Albania", "Albania", "ALB"),
83: Country(83, "Belarus", "Belarus", "BLR"),
84: Country(84, "New Zealand", "New-Zealand", "NZL"),
164: Country(164, "Saudi Arabia", "Saudi-Arabia", "SAU"),
165: Country(165, "Egypt", "Egypt", "EGY"),
166: Country(166, "United Arab Emirates", "United-Arab-Emirates", "UAE"),
168: Country(168, "Georgia", "Georgia", "GEO"),
169: Country(169, "Armenia", "Armenia", "ARM"),
170: Country(170, "Nigeria", "Nigeria", "NGA"),
171: Country(171, "Cuba", "Cuba", "CUB"),
}
FOOD_ENERGY: Dict[str, int] = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
GROUND_RANK_NAMES: Dict[int, str] = {
1: "Recruit",
2: "Private",
3: "Private*",
4: "Private**",
5: "Private***",
6: "Corporal",
7: "Corporal*",
8: "Corporal**",
9: "Corporal***",
10: "Sergeant",
11: "Sergeant*",
12: "Sergeant**",
13: "Sergeant***",
14: "Lieutenant",
15: "Lieutenant*",
16: "Lieutenant**",
17: "Lieutenant***",
18: "Captain",
19: "Captain*",
20: "Captain**",
21: "Captain***",
22: "Major",
23: "Major*",
24: "Major**",
25: "Major***",
26: "Commander",
27: "Commander*",
28: "Commander**",
29: "Commander***",
30: "Lt Colonel",
31: "Lt Colonel*",
32: "Lt Colonel**",
33: "Lt Colonel***",
34: "Colonel",
35: "Colonel*",
36: "Colonel**",
37: "Colonel***",
38: "General",
39: "General*",
40: "General**",
41: "General***",
42: "Field Marshal",
43: "Field Marshal*",
44: "Field Marshal**",
45: "Field Marshal***",
46: "Supreme Marshal",
47: "Supreme Marshal*",
48: "Supreme Marshal**",
49: "Supreme Marshal***",
50: "National Force",
51: "National Force*",
52: "National Force**",
53: "National Force***",
54: "World Class Force",
55: "World Class Force*",
56: "World Class Force**",
57: "World Class Force***",
58: "Legendary Force",
59: "Legendary Force*",
60: "Legendary Force**",
61: "Legendary Force***",
62: "God of War",
63: "God of War*",
64: "God of War**",
65: "God of War***",
66: "Titan",
67: "Titan*",
68: "Titan**",
69: "Titan***",
70: "Legends I",
71: "Legends II",
72: "Legends III",
73: "Legends IV",
74: "Legends V",
75: "Legends VI",
76: "Legends VII",
77: "Legends VIII",
78: "Legends IX",
79: "Legends X",
80: "Legends XI",
81: "Legends XII",
82: "Legends XIII",
83: "Legends XIV",
84: "Legends XV",
85: "Legends XVI",
86: "Legends XVII",
87: "Legends XVIII",
88: "Legends XIX",
89: "Legends XX",
}
GROUND_RANK_POINTS: Dict[int, int] = {
1: 0,
2: 15,
3: 45,
4: 80,
5: 120,
6: 170,
7: 250,
8: 350,
9: 450,
10: 600,
11: 800,
12: 1000,
13: 1400,
14: 1850,
15: 2350,
16: 3000,
17: 3750,
18: 5000,
19: 6500,
20: 9000,
21: 12000,
22: 15500,
23: 20000,
24: 25000,
25: 31000,
26: 40000,
27: 52000,
28: 67000,
29: 85000,
30: 110000,
31: 140000,
32: 180000,
33: 225000,
34: 285000,
35: 355000,
36: 435000,
37: 540000,
38: 660000,
39: 800000,
40: 950000,
41: 1140000,
42: 1350000,
43: 1600000,
44: 1875000,
45: 2185000,
46: 2550000,
47: 3000000,
48: 3500000,
49: 4150000,
50: 4900000,
51: 5800000,
52: 7000000,
53: 9000000,
54: 11500000,
55: 14500000,
56: 18000000,
57: 22000000,
58: 26500000,
59: 31500000,
60: 37000000,
61: 43000000,
62: 50000000,
63: 100000000,
64: 200000000,
65: 500000000,
66: 1000000000,
67: 2000000000,
68: 4000000000,
69: 10000000000,
70: 20000000000,
71: 30000000000,
72: 40000000000,
73: 50000000000,
74: 60000000000,
75: 70000000000,
76: 80000000000,
77: 90000000000,
78: 100000000000,
79: 110000000000,
80: 120000000000,
81: 130000000000,
82: 140000000000,
83: 150000000000,
84: 160000000000,
85: 170000000000,
86: 180000000000,
87: 190000000000,
88: 200000000000,
89: 210000000000,
}
GROUND_RANKS: Dict[int, Rank] = {i: Rank(i, GROUND_RANK_NAMES[i], GROUND_RANK_POINTS[i], False) for i in range(1, 90)}
INDUSTRIES = Industries()
TERRAINS: Dict[int, str] = {
0: "Standard",
1: "Industrial",
2: "Urban",
3: "Suburbs",
4: "Airport",
5: "Plains",
6: "Wasteland",
7: "Mountains",
8: "Beach",
9: "Swamp",
10: "Mud",
11: "Hills",
12: "Jungle",
13: "Forest",
14: "Desert",
}

349
erepublik/utils.py Normal file
View File

@ -0,0 +1,349 @@
import datetime
import os
import re
import sys
import time
import unicodedata
import warnings
from base64 import b64encode
from decimal import Decimal
from logging import Logger
from pathlib import Path
from typing import Any, Dict, List, Union
import pytz
import requests
from requests import Response
from erepublik import __version__, constants
try:
import simplejson as json
except ImportError:
import json
__all__ = [
"ErepublikJSONEncoder",
"VERSION",
"b64json",
"calculate_hit",
"date_from_eday",
"deprecation",
"eday_from_date",
"get_air_hit_dmg_value",
"get_file",
"get_final_hit_dmg",
"get_ground_hit_dmg_value",
"get_sleep_seconds",
"good_timedelta",
"interactive_sleep",
"json",
"json_decode_object_hook",
"json_dump",
"json_dumps",
"json_load",
"json_loads",
"localize_dt",
"localize_timestamp",
"normalize_html_json",
"now",
"silent_sleep",
"slugify",
"write_file",
]
VERSION: str = __version__
def now() -> datetime.datetime:
return datetime.datetime.now(constants.erep_tz).replace(microsecond=0)
def localize_timestamp(timestamp: int) -> datetime.datetime:
return datetime.datetime.fromtimestamp(timestamp, constants.erep_tz)
def localize_dt(dt: Union[datetime.date, datetime.datetime]) -> datetime.datetime:
if isinstance(dt, datetime.datetime):
return constants.erep_tz.localize(dt)
elif isinstance(dt, datetime.date):
return constants.erep_tz.localize(datetime.datetime.combine(dt, datetime.time(0, 0, 0)))
else:
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:
"""Normalize timezone aware datetime object after timedelta to correct jumps over DST switches
:param dt: Timezone aware datetime object
:type dt: datetime.datetime
:param td: timedelta object
:type td: datetime.timedelta
:return: datetime object with correct timezone when jumped over DST
:rtype: datetime.datetime
"""
return constants.erep_tz.normalize(dt + td)
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
def date_from_eday(eday: int) -> datetime.datetime:
return localize_dt(datetime.date(2007, 11, 20)) + datetime.timedelta(days=eday)
def get_sleep_seconds(time_until: datetime.datetime) -> int:
"""time_until aware datetime object Wrapper for sleeping until"""
sleep_seconds = int((time_until - now()).total_seconds())
return sleep_seconds if sleep_seconds > 0 else 0
def interactive_sleep(sleep_seconds: int):
while sleep_seconds > 0:
seconds = sleep_seconds
if (seconds - 1) // 1800:
seconds = seconds % 1800 if seconds % 1800 else 1800
elif (seconds - 1) // 300:
seconds = seconds % 300 if seconds % 300 else 300
elif (seconds - 1) // 60:
seconds = seconds % 60 if seconds % 60 else 60
# elif (seconds - 1) // 30:
# seconds = seconds % 30 if seconds % 30 else 30
else:
seconds = 1
sys.stdout.write(f"\rSleeping for {sleep_seconds:4} more seconds")
sys.stdout.flush()
time.sleep(seconds)
sleep_seconds -= seconds
sys.stdout.write("\r")
silent_sleep = time.sleep
def get_file(filepath: str) -> str:
file = Path(filepath)
if file.exists():
if file.is_dir():
return str(file / "new_file.txt")
else:
version = 1
try:
version = int(file.suffix[1:]) + 1
basename = file.stem
except ValueError:
basename = file.name
version += 1
full_name = file.parent / f"{basename}.{version}"
while full_name.exists():
version += 1
full_name = file.parent / f"{basename}.{version}"
return str(full_name)
else:
os.makedirs(file.parent, exist_ok=True)
return str(file)
def write_file(filename: str, content: str) -> int:
filename = get_file(filename)
with open(filename, "ab") as f:
ret = f.write(content.encode("utf-8"))
return ret
def normalize_html_json(js: str) -> str:
js = re.sub(r" \'(.*?)\'", lambda a: f'"{a.group(1)}"', js)
js = re.sub(r"(\d\d):(\d\d):(\d\d)", r"\1\2\3", js)
js = re.sub(r'([{\s,])(\w+)(:)(?!"})', r'\1"\2"\3', js)
js = re.sub(r",\s*}", "}", js)
return js
def slugify(value, allow_unicode=False) -> str:
"""
Function copied from Django2.2.1 django.utils.text.slugify
Convert to ASCII if 'allow_unicode' is False. Convert spaces to hyphens.
Remove characters that aren't alphanumerics, underscores, or hyphens.
Convert to lowercase. Also strip leading and trailing whitespace.
"""
value = str(value)
if allow_unicode:
value = unicodedata.normalize("NFKC", value)
else:
value = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
value = re.sub(r"[^\w\s-]", "_", value).strip().lower()
return re.sub(r"[-\s]+", "-", value)
def calculate_hit(
strength: float,
rang: int,
tp: bool,
elite: bool,
ne: bool,
booster: int = 0,
weapon: int = 200,
is_deploy: bool = False,
) -> Decimal:
dec = 3 if is_deploy else 0
base_str = 1 + Decimal(str(round(strength, 3))) / 400
base_rnk = 1 + Decimal(str(rang / 5))
base_wpn = 1 + Decimal(str(weapon / 100))
dmg = 10 * base_str * base_rnk * base_wpn
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, booster: int = 0, weapon_power: int = 200
) -> Decimal:
r = requests.get(f"https://www.erepublik.com/en/main/citizen-profile-json/{citizen_id}").json()
rang = r["military"]["militaryData"]["ground"]["rankNumber"]
strength = r["military"]["militaryData"]["ground"]["strength"]
elite = r["citizenAttributes"]["level"] > 100
if natural_enemy:
true_patriot = True
return calculate_hit(strength, rang, true_patriot, elite, natural_enemy, booster, weapon_power)
def get_air_hit_dmg_value(
citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False, booster: int = 0, weapon_power: int = 0
) -> Decimal:
r = requests.get(f"https://www.erepublik.com/en/main/citizen-profile-json/{citizen_id}").json()
rang = r["military"]["militaryData"]["aircraft"]["rankNumber"]
elite = r["citizenAttributes"]["level"] > 100
return calculate_hit(0, rang, true_patriot, elite, natural_enemy, booster, weapon_power)
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 deprecation(message):
warnings.warn(message, DeprecationWarning, stacklevel=2)
def json_decode_object_hook(
o: Union[Dict[str, Any], List[Any], int, float, str]
) -> Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta]:
"""Convert classes.ErepublikJSONEncoder datetime, date and timedelta to their python objects
:param o:
:return: Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta]
"""
if o.get("__type__"):
_type = o.get("__type__")
if _type == "datetime":
dt = datetime.datetime.strptime(f"{o['date']} {o['time']}", "%Y-%m-%d %H:%M:%S")
if o.get("tzinfo"):
dt = pytz.timezone(o["tzinfo"]).localize(dt)
return dt
elif _type == "date":
dt = datetime.datetime.strptime(f"{o['date']}", "%Y-%m-%d")
return dt.date()
elif _type == "timedelta":
return datetime.timedelta(seconds=o["total_seconds"])
return o
def json_load(f, **kwargs):
# kwargs.update(object_hook=json_decode_object_hook)
return json.load(f, **kwargs)
def json_loads(s: str, **kwargs):
# kwargs.update(object_hook=json_decode_object_hook)
return json.loads(s, **kwargs)
def json_dump(obj, fp, *args, **kwargs):
if not kwargs.get("cls"):
kwargs.update(cls=ErepublikJSONEncoder)
return json.dump(obj, fp, *args, **kwargs)
def json_dumps(obj, *args, **kwargs):
if not kwargs.get("cls"):
kwargs.update(cls=ErepublikJSONEncoder)
return json.dumps(obj, *args, **kwargs)
def b64json(obj: Union[Dict[str, Union[int, List[str]]], List[str]]):
if isinstance(obj, list):
return b64encode(json.dumps(obj, separators=(",", ":")).encode("utf-8")).decode("utf-8")
elif isinstance(obj, (int, str)):
return obj
elif isinstance(obj, dict):
for k, v in obj.items():
obj[k] = b64json(v)
else:
from .classes import ErepublikException
raise ErepublikException(f"Unhandled object type! obj is {type(obj)}")
return b64encode(json.dumps(obj, separators=(",", ":")).encode("utf-8")).decode("utf-8")
class ErepublikJSONEncoder(json.JSONEncoder):
def default(self, o):
try:
from erepublik.citizen import Citizen
if isinstance(o, Decimal):
return float(f"{o:.02f}")
elif isinstance(o, datetime.datetime):
return dict(
__type__="datetime",
date=o.strftime("%Y-%m-%d"),
time=o.strftime("%H:%M:%S"),
tzinfo=str(o.tzinfo) if o.tzinfo else None,
)
elif isinstance(o, datetime.date):
return dict(__type__="date", date=o.strftime("%Y-%m-%d"))
elif isinstance(o, datetime.timedelta):
return dict(
__type__="timedelta",
days=o.days,
seconds=o.seconds,
microseconds=o.microseconds,
total_seconds=o.total_seconds(),
)
elif isinstance(o, Response):
return dict(headers=dict(o.__dict__["headers"]), url=o.url, text=o.text, status_code=o.status_code)
elif hasattr(o, "as_dict"):
return o.as_dict
elif isinstance(o, set):
return list(o)
elif isinstance(o, Citizen):
return o.to_json()
elif isinstance(o, Logger):
return str(o)
elif hasattr(o, "__dict__"):
return o.__dict__
else:
return super().default(o)
except Exception as e: # noqa
return str(e)

View File

@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
"""Top-level package for eRepublik script."""
__author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv'
__version__ = '0.1.1'
__all__ = ["Citizen"]
from erepublik_script import classes, utils
from erepublik_script.citizen import Citizen

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,423 +0,0 @@
# -*- coding: utf-8 -*-
"""Console script for erepublik_script."""
__author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv'
__version__ = '0.1.0'
import json
import os
import random
import sys
import threading
from collections import defaultdict
from datetime import timedelta
from typing import List, Tuple
import click
from erepublik_script import classes, utils
from erepublik_script.citizen import Citizen
__all__ = ["Citizen"]
INTERACTIVE = True
CONFIG = defaultdict(bool)
@click.command()
def main(args=None):
player = None
try: # If errors before player is initialized
while True:
player = Citizen(email=CONFIG['email'], password=CONFIG['password'])
if player.logged_in:
break
utils.silent_sleep(2)
player.config.work = CONFIG['work']
player.config.train = CONFIG['train']
player.config.ot = CONFIG['ot']
player.config.wam = bool(CONFIG['wam'])
player.config.employees = bool(CONFIG['employ'])
player.config.auto_sell = CONFIG.get('auto_sell', [])
player.config.auto_sell_all = CONFIG.get('auto_sell_all', False)
player.config.auto_buy_raw = CONFIG.get('auto_buy_raw', False)
player.config.force_wam = CONFIG.get('force_wam', False)
player.config.fight = CONFIG['fight']
player.config.air = CONFIG['air']
player.config.ground = CONFIG['ground']
player.config.all_in = CONFIG['all_in']
player.config.next_energy = CONFIG['next_energy']
player.config.boosters = CONFIG['boosters']
player.config.travel_to_fight = CONFIG['travel_to_fight']
player.config.always_travel = CONFIG.get('always_travel', False)
player.config.epic_hunt = CONFIG['epic_hunt']
player.config.epic_hunt_ebs = CONFIG['epic_hunt_ebs']
player.config.rw_def_side = CONFIG['rw_def_side']
player.config.random_sleep = CONFIG['random_sleep']
player.config.continuous_fighting = CONFIG['continuous_fighting']
player.config.interactive = CONFIG['interactive']
player.reporter.allowed = not CONFIG.get('reporting_is_not_allowed')
player.set_debug(CONFIG.get('debug', False))
while True:
try:
player.update_all()
break
except:
utils.silent_sleep(2)
now = utils.now()
dt_max = now.replace(year=9999)
tasks = {
'eat': now,
}
wam_hour = employ_hour = 14
if player.config.work:
tasks.update({'work': now})
if player.config.train:
tasks.update({'train': now})
if player.config.ot:
tasks.update({'ot': now})
if player.config.fight:
tasks.update({'fight': now})
if player.config.wam:
wam_hour = 14
if not isinstance(CONFIG['wam'], bool):
try:
wam_hour = abs(int(CONFIG['wam'])) % 24
except ValueError:
pass
tasks.update({'wam': now.replace(hour=wam_hour, minute=0, second=0, microsecond=0)})
if player.config.employees:
employ_hour = 8
if not isinstance(CONFIG['employ'], bool):
try:
employ_hour = abs(int(CONFIG['employ'])) % 24
except ValueError:
pass
tasks.update({'employ': now.replace(hour=employ_hour, minute=0, second=0, microsecond=0)})
if player.config.epic_hunt:
tasks['epic_hunt'] = now
if CONFIG.get("renew_houses", True):
tasks['renew_houses'] = now
if CONFIG.get('start_battles'):
""" {'start_battle': {war_id: {'regions': [region_id, ],
'timing': ['at', 'hh:mm' | 'before', 'hh:mm' (before autoattack) |
'auto' (after round for citizenship country's oldest battle or at 00:00)
'rw', (after first round of RW if you are occupying)]}} """
player.allowed_battles = CONFIG.get('start_battles', dict())
raise classes.ErepublikException("Battle starting is not implemented")
if player.reporter.allowed:
report = dict(CONFIG)
report.pop("email", None)
report.pop("password", None)
report.update(
VERSION=utils.VERSION,
COMMIT_ID=utils.COMMIT_ID
)
player.reporter.report_action("ACTIVE_CONFIG", json_val=report)
# -1 because main thread is counted in
name = "{}-state_updater-{}".format(player.name, threading.active_count() - 1)
state_thread = threading.Thread(target=player.state_update_repeater, name=name)
state_thread.start()
if CONFIG.get("congress", True):
tasks['congress'] = now.replace(hour=1, minute=30, second=0)
if CONFIG.get("party_president", False):
tasks['party_president'] = now.replace(hour=1, minute=30, second=0)
contribute_cc = int(CONFIG.get("contribute_cc", 0))
if contribute_cc:
tasks['contribute_cc'] = now.replace(hour=2, minute=0, second=0)
if CONFIG.get("gold_buy"):
tasks['gold_buy'] = now.replace(hour=23, minute=57, second=0, microsecond=0)
error_count = 0
while error_count < 3:
try:
now = utils.now()
player.update_all()
if tasks.get('work', dt_max) <= now:
player.write_log("Doing task: work")
player.update_citizen_info()
player.work()
if player.config.ot:
tasks['ot'] = now
player.collect_daily_task()
next_time = now.replace(hour=0, minute=0, second=0) + timedelta(days=1)
tasks.update({'work': next_time})
if tasks.get('train', dt_max) <= now:
player.write_log("Doing task: train")
player.update_citizen_info()
player.train()
player.collect_daily_task()
next_time = now.replace(hour=0, minute=0, second=0) + timedelta(days=1)
tasks.update({'train': next_time})
if tasks.get('wam', dt_max) <= now:
player.write_log("Doing task: Work as manager")
success = player.work_wam()
player.eat()
if success:
next_time = now.replace(hour=wam_hour, minute=0, second=0, microsecond=0) + timedelta(days=1)
else:
next_time = now.replace(second=0, microsecond=0) + timedelta(minutes=30)
tasks.update({'wam': next_time})
if tasks.get('eat', dt_max) <= now:
player.write_log("Doing task: eat")
player.eat()
if player.energy.food_fights > player.energy.limit // 10:
next_minutes = 12
else:
next_minutes = (player.energy.limit - 5 * player.energy.interval) // player.energy.interval * 6
next_time = player.energy.reference_time + timedelta(minutes=next_minutes)
tasks.update({'eat': next_time})
if tasks.get('fight', dt_max) <= now or player.energy.is_energy_full:
fight_energy_debug_log: List[Tuple[int, str]] = []
player.write_log("Doing task: fight")
player.write_log(player.health_info)
if player.should_fight():
player.find_battle_and_fight()
else:
player.collect_weekly_reward()
energy = classes.EnergyToFight(player.details.xp_till_level_up * 10 - player.energy.limit + 50)
fight_energy_debug_log.append((
energy.i,
f"Levelup reachable {player.details.xp_till_level_up} * 10 - {player.energy.limit} + 50"
))
# Do levelup
energy.check(player.details.xp_till_level_up * 10 + 50)
fight_energy_debug_log.append((
energy.i, f"Levelup {player.details.xp_till_level_up} * 10 + 50"
))
# if levelup is close stop queueing other fighting
if not player.is_levelup_close:
# Obligatory need 75pp
if player.details.pp < 75:
energy.check(75 - player.details.pp)
fight_energy_debug_log.append((energy.i, f"Obligatory need 75pp: 75 - {player.details.pp}"))
if player.config.continuous_fighting and player.has_battle_contribution:
energy.check(player.energy.interval)
fight_energy_debug_log.append((energy.i, f"continuous_fighting: {player.energy.interval}"))
# All-in
if player.config.all_in:
energy.check(player.energy.limit * 2 - 3 * player.energy.interval)
fight_energy_debug_log.append((
energy.i, f"All-in: {player.energy.limit} * 2 - 3 * {player.energy.interval}"
))
elif player.energy.limit * 2 - 3 * player.energy.interval >= player.energy.recovered:
# 1h worth of energy
energy.check(player.energy.limit * 2 - 3 * player.energy.interval)
fight_energy_debug_log.append(
(energy.i, f"1h worth of energy: {player.energy.interval} * 10"
))
# All-in for AIR battles
if all([player.config.air, player.config.all_in,
player.energy.available >= player.energy.limit]):
energy.check(player.energy.limit)
fight_energy_debug_log.append((
energy.i, f"All-in for AIR battles: {player.energy.limit}"
))
# Get to next Energy +1
if player.next_reachable_energy and player.config.next_energy:
energy.check(player.next_reachable_energy * 10)
fight_energy_debug_log.append((
energy.i, f"Get to next Energy +1: {player.next_reachable_energy} * 10"
))
energy = energy.i - player.energy.available
next_minutes = max([6, abs(energy) // player.energy.interval * 6])
# utils.write_silent_log("\n".join([f"{energy} {info}" for energy, info in fight_energy_debug_log]))
next_time = player.energy.reference_time + timedelta(minutes=next_minutes)
tasks.update({'fight': next_time})
if tasks.get('ot', dt_max) <= now:
player.write_log("Doing task: ot")
if now > player.my_companies.next_ot_time:
player.work_ot()
next_time = now + timedelta(minutes=60)
else:
next_time = player.my_companies.next_ot_time
tasks.update({'ot': next_time})
if tasks.get('employ', dt_max) <= now:
player.write_log("Doing task: Employee work")
next_time = utils.now().replace(hour=employ_hour, minute=0, second=0) + timedelta(days=1)
next_time = next_time if player.work_employees() else tasks.get('employ') + timedelta(minutes=30)
tasks.update({'employ': next_time})
if tasks.get('epic_hunt', dt_max) <= now:
player.write_log("Doing task: EPIC check")
player.check_epic_battles()
if player.active_fs:
next_time = now + timedelta(minutes=1)
else:
next_time = tasks.get('eat')
tasks.update({'epic_hunt': next_time})
if tasks.get('gold_buy', dt_max) <= now:
player.write_log("Doing task: auto buy 10g")
for offer in player.get_monetary_offers():
if offer['amount'] >= 10 and player.details.cc >= 20 * offer["price"]:
# TODO: check allowed amount to buy
if player.buy_monetary_market_offer(offer=offer['offer_id'], amount=10, currency=62):
break
next_time = tasks.get('gold_buy') + timedelta(days=1)
tasks.update({'gold_buy': next_time})
if tasks.get('congress', dt_max) <= now:
if 1 <= now.day < 16:
next_time = now.replace(day=16)
elif 16 <= now.day < 24:
player.write_log("Doing task: candidate for congress")
player.candidate_for_congress()
if not now.month == 12:
next_time = now.replace(month=now.month + 1, day=16)
else:
next_time = now.replace(year=now.year + 1, month=1, day=16)
else:
if not now.month == 12:
next_time = now.replace(month=now.month + 1, day=16)
else:
next_time = now.replace(year=now.year + 1, month=1, day=16)
tasks.update({'congress': next_time.replace(hour=1, minute=30, second=0, microsecond=0)})
if tasks.get('party_president', dt_max) <= now:
if not now.day == 15:
player.write_log("Doing task: candidate for party president")
player.candidate_for_party_presidency()
if not now.month == 12:
next_time = now.replace(month=now.month + 1)
else:
next_time = now.replace(year=now.year + 1, month=1)
else:
if not now.month == 12:
next_time = now.replace(month=now.month + 1)
else:
next_time = now.replace(year=now.year + 1, month=1)
tasks.update(party_president=next_time.replace(day=16, hour=0, minute=0, second=0, microsecond=0))
if tasks.get('contribute_cc', dt_max) <= now:
if not now.weekday():
player.update_money()
cc = (player.details.cc // contribute_cc) * contribute_cc
player.write_log("Doing task: Contribute {}cc to Latvia".format(cc))
player.contribute_cc_to_country(cc)
next_time = now + timedelta(days=7 - now.weekday())
next_time = next_time.replace(hour=2, minute=0, second=0)
tasks.update({'contribute_cc': next_time})
if tasks.get('renew_houses', dt_max) <= now:
player.write_log("Doing task: Renew houses")
end_times = player.renew_houses()
if end_times:
tasks.update(renew_houses=min(end_times.values()) - timedelta(hours=24))
else:
player.write_log("No houses found! Forcing q1 usage...")
end_times = player.buy_and_activate_house(1)
if not end_times:
tasks.update(renew_houses=now + timedelta(hours=6))
else:
tasks.update(renew_houses=min(end_times.values()) - timedelta(hours=24))
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))
if next_time < closest_next_time:
closest_next_time = next_time
random_seconds = random.randint(0, 121) if player.config.random_sleep else 0
sleep_seconds = int(utils.get_sleep_seconds(closest_next_time))
if sleep_seconds <= 0:
raise classes.ErepublikException(f"Loop detected! Offending task: '{next_tasks[0]}'")
closest_next_time += timedelta(seconds=random_seconds)
player.write_log("My next Tasks and there time:\n" + "\n".join(sorted(next_tasks)))
player.write_log("Sleeping until (eRep): {} (sleeping for {}s + random {}s)".format(
closest_next_time.strftime("%F %T"), sleep_seconds, random_seconds))
seconds_to_sleep = sleep_seconds + random_seconds if sleep_seconds > 0 else 0
player.sleep(seconds_to_sleep)
except classes.ErepublikNetworkException:
player.write_log('Network ERROR detected. Sleeping for 1min...')
player.sleep(60)
except (KeyboardInterrupt, SystemExit):
sys.exit(1)
except classes.ErepublikException as e:
utils.process_error(f"Known error detected! {e}", player.name, sys.exc_info(), player, utils.COMMIT_ID)
except:
utils.process_error("Unknown error!", player.name, sys.exc_info(), player, utils.COMMIT_ID)
error_count += 1
if error_count < 3:
player.sleep(60)
finally:
if error_count >= 3:
player.stop_threads.set()
player.stop_threads.set()
player.write_log('Too many errors.')
except (KeyboardInterrupt, SystemExit):
sys.exit(1)
except classes.ErepublikException:
utils.process_error("[{}] To many errors.".format(utils.COMMIT_ID), player.name, sys.exc_info(), player,
utils.COMMIT_ID)
except:
if isinstance(player, Citizen):
name = player.name
elif CONFIG.get('email', None):
name = CONFIG['email']
else:
name = "Uninitialized"
utils.process_error("[{}] Fatal error.".format(utils.COMMIT_ID), name, sys.exc_info(), player, utils.COMMIT_ID)
sys.exit(1)
if __name__ == "__main__":
assert sys.version_info >= (3, 7, 1)
write_log = utils.write_silent_log
try:
with open('config.json', 'r') as f:
CONFIG = json.load(f)
write_log('Config file found. Checking...')
CONFIG = utils.parse_config(CONFIG)
except:
CONFIG = utils.parse_config()
with open('config.json', 'w') as f:
json.dump(CONFIG, f, indent=True, sort_keys=True)
if CONFIG['interactive']:
write_log = utils.write_interactive_log
else:
write_log = utils.write_silent_log
write_log('\nTo quit press [ctrl] + [c]', False)
os.chdir(os.path.dirname(os.path.realpath(__file__)))
write_log('Version: ' + utils.VERSION)
while True:
main()
write_log("Restarting after 1h")
utils.interactive_sleep(60 * 60)

View File

@ -1,730 +0,0 @@
import datetime
import inspect
import json
import os
import re
import sys
import time
import traceback
from collections import deque
from decimal import Decimal
from json import JSONEncoder
from pathlib import Path
from typing import Union
import pytz
import requests
from requests import Response
from slugify import slugify
__all__ = ["FOOD_ENERGY", "VERSION", "COMMIT_ID", "COUNTRIES", "erep_tz",
"now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday",
"get_sleep_seconds", "interactive_sleep", "silent_sleep",
"write_silent_log", "write_interactive_log", "get_file", "write_file",
"send_email", "normalize_html_json", "process_error", ]
FOOD_ENERGY = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
VERSION = "v0.14.1"
COMMIT_ID = "7b92e19"
erep_tz = pytz.timezone('US/Pacific')
AIR_RANKS = {1: "Airman", 2: "Airman 1st Class", 3: "Airman 1st Class*", 4: "Airman 1st Class**",
5: "Airman 1st Class***", 6: "Airman 1st Class****", 7: "Airman 1st Class*****",
8: "Senior Airman", 9: "Senior Airman*", 10: "Senior Airman**", 11: "Senior Airman***",
12: "Senior Airman****", 13: "Senior Airman*****",
14: "Staff Sergeant", 15: "Staff Sergeant*", 16: "Staff Sergeant**", 17: "Staff Sergeant***",
18: "Staff Sergeant****", 19: "Staff Sergeant*****",
20: "Aviator", 21: "Aviator*", 22: "Aviator**", 23: "Aviator***", 24: "Aviator****", 25: "Aviator*****",
26: "Flight Lieutenant", 27: "Flight Lieutenant*", 28: "Flight Lieutenant**", 29: "Flight Lieutenant***",
30: "Flight Lieutenant****", 31: "Flight Lieutenant*****",
32: "Squadron Leader", 33: "Squadron Leader*", 34: "Squadron Leader**", 35: "Squadron Leader***",
36: "Squadron Leader****", 37: "Squadron Leader*****",
38: "Chief Master Sergeant", 39: "Chief Master Sergeant*", 40: "Chief Master Sergeant**",
41: "Chief Master Sergeant***", 42: "Chief Master Sergeant****", 43: "Chief Master Sergeant*****",
44: "Wing Commander", 45: "Wing Commander*", 46: "Wing Commander**", 47: "Wing Commander***",
48: "Wing Commander****", 49: "Wing Commander*****",
50: "Group Captain", 51: "Group Captain*", 52: "Group Captain**", 53: "Group Captain***",
54: "Group Captain****", 55: "Group Captain*****",
56: "Air Commodore", 57: "Air Commodore*", 58: "Air Commodore**", 59: "Air Commodore***",
60: "Air Commodore****", 61: "Air Commodore*****", }
GROUND_RANKS = {1: "Recruit", 2: "Private", 3: "Private*", 4: "Private**", 5: "Private***", 6: "Corporal",
7: "Corporal*", 8: "Corporal**", 9: "Corporal***",
10: "Sergeant", 11: "Sergeant*", 12: "Sergeant**", 13: "Sergeant***", 14: "Lieutenant",
15: "Lieutenant*", 16: "Lieutenant**", 17: "Lieutenant***",
18: "Captain", 19: "Captain*", 20: "Captain**", 21: "Captain***", 22: "Major", 23: "Major*",
24: "Major**", 25: "Major***",
26: "Commander", 27: "Commander*", 28: "Commander**", 29: "Commander***", 30: "Lt Colonel",
31: "Lt Colonel*", 32: "Lt Colonel**", 33: "Lt Colonel***",
34: "Colonel", 35: "Colonel*", 36: "Colonel**", 37: "Colonel***", 38: "General", 39: "General*",
40: "General**", 41: "General***",
42: "Field Marshal", 43: "Field Marshal*", 44: "Field Marshal**", 45: "Field Marshal***",
46: "Supreme Marshal", 47: "Supreme Marshal*", 48: "Supreme Marshal**", 49: "Supreme Marshal***",
50: "National Force", 51: "National Force*", 52: "National Force**", 53: "National Force***",
54: "World Class Force", 55: "World Class Force*", 56: "World Class Force**",
57: "World Class Force***", 58: "Legendary Force", 59: "Legendary Force*", 60: "Legendary Force**",
61: "Legendary Force***",
62: "God of War", 63: "God of War*", 64: "God of War**", 65: "God of War***", 66: "Titan", 67: "Titan*",
68: "Titan**", 69: "Titan***",
70: "Legends I", 71: "Legends II", 72: "Legends III", 73: "Legends IV", 74: "Legends V",
75: "Legends VI", 76: "Legends VII", 77: "Legends VIII", 78: "Legends IX", 79: "Legends X",
80: "Legends XI", 81: "Legends XII", 82: "Legends XIII", 83: "Legends XIV", 84: "Legends XV",
85: "Legends XVI", 86: "Legends XVII", 87: "Legends XVIII", 88: "Legends XIX", 89: "Legends XX", }
COUNTRIES = {1: 'Romania', 9: 'Brazil', 10: 'Italy', 11: 'France', 12: 'Germany', 13: 'Hungary', 14: 'China',
15: 'Spain', 23: 'Canada', 24: 'USA', 26: 'Mexico', 27: 'Argentina', 28: 'Venezuela', 29: 'United Kingdom',
30: 'Switzerland', 31: 'Netherlands', 32: 'Belgium', 33: 'Austria', 34: 'Czech Republic', 35: 'Poland',
36: 'Slovakia', 37: 'Norway', 38: 'Sweden', 39: 'Finland', 40: 'Ukraine', 41: 'Russia', 42: 'Bulgaria',
43: 'Turkey', 44: 'Greece', 45: 'Japan', 47: 'South Korea', 48: 'India', 49: 'Indonesia', 50: 'Australia',
51: 'South Africa', 52: 'Republic of Moldova', 53: 'Portugal', 54: 'Ireland', 55: 'Denmark', 56: 'Iran',
57: 'Pakistan', 58: 'Israel', 59: 'Thailand', 61: 'Slovenia', 63: 'Croatia', 64: 'Chile', 65: 'Serbia',
66: 'Malaysia', 67: 'Philippines', 68: 'Singapore', 69: 'Bosnia and Herzegovina', 70: 'Estonia',
71: 'Latvia', 72: 'Lithuania', 73: 'North Korea', 74: 'Uruguay', 75: 'Paraguay', 76: 'Bolivia', 77: 'Peru',
78: 'Colombia', 79: 'Republic of Macedonia (FYROM)', 80: 'Montenegro', 81: 'Republic of China (Taiwan)',
82: 'Cyprus', 83: 'Belarus', 84: 'New Zealand', 164: 'Saudi Arabia', 165: 'Egypt',
166: 'United Arab Emirates', 167: 'Albania', 168: 'Georgia', 169: 'Armenia', 170: 'Nigeria', 171: 'Cuba'}
class MyJSONEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
return float("{:.02f}".format(o))
elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', year=o.year, month=o.month, day=o.day, hour=o.hour, minute=o.minute,
second=o.second, microsecond=o.microsecond)
elif isinstance(o, datetime.date):
return dict(__type__='date', year=o.year, month=o.month, day=o.day)
elif isinstance(o, datetime.timedelta):
return dict(__type__='timedelta', days=o.days, seconds=o.seconds,
microseconds=o.microseconds, total_seconds=o.total_seconds())
elif isinstance(o, Response):
return dict(headers=o.headers.__dict__, url=o.url, text=o.text)
elif hasattr(o, '__dict__'):
return o.__dict__
elif isinstance(o, deque):
return list(o)
return super().default(o)
def now():
return datetime.datetime.now(erep_tz).replace(microsecond=0)
def localize_timestamp(timestamp: int):
return datetime.datetime.fromtimestamp(timestamp, erep_tz)
def localize_dt(dt: Union[datetime.date, datetime.datetime]):
if isinstance(dt, datetime.date):
dt = datetime.datetime.combine(dt, datetime.time(0, 0, 0))
return erep_tz.localize(dt)
def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.datetime:
return erep_tz.normalize(dt + td)
def eday_from_date(date: Union[datetime.date, datetime.datetime] = now()) -> int:
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
def date_from_eday(eday: int) -> datetime.date:
return localize_dt(datetime.date(2007, 11, 20)) + datetime.timedelta(days=eday)
def get_sleep_seconds(time_untill: datetime.datetime) -> int:
""" time_until aware datetime object Wrapper for sleeping until """
sleep_seconds = int((time_untill - now()).total_seconds())
return sleep_seconds if sleep_seconds > 0 else 0
def interactive_sleep(sleep_seconds: int):
while sleep_seconds > 0:
seconds = sleep_seconds
if (seconds - 1) // 1800:
seconds = seconds % 1800 if seconds % 1800 else 1800
elif (seconds - 1) // 300:
seconds = seconds % 300 if seconds % 300 else 300
elif (seconds - 1) // 60:
seconds = seconds % 60 if seconds % 60 else 60
# elif (seconds - 1) // 30:
# seconds = seconds % 30 if seconds % 30 else 30
else:
seconds = 1
sys.stdout.write("\rSleeping for {:4} more seconds".format(sleep_seconds))
sys.stdout.flush()
time.sleep(seconds)
sleep_seconds -= seconds
sys.stdout.write("\r")
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
if not os.path.isdir('log'):
os.mkdir('log')
with open("log/%s.log" % erep_time_now.strftime('%F'), 'a', encoding="utf-8") as f:
f.write("%s\n" % txt)
if should_print:
print(txt)
def write_interactive_log(*args, **kwargs):
_write_log(should_print=True, *args, **kwargs)
def write_silent_log(*args, **kwargs):
_write_log(should_print=False, *args, **kwargs)
def get_file(filepath: str) -> str:
file = Path(filepath)
if file.exists():
if file.is_dir():
return str(file / "new_file.txt")
else:
version = 1
try:
version = int(file.suffix[1:]) + 1
basename = file.stem
except ValueError:
basename = file.name
version += 1
full_name = file.parent / f"{basename}.{version}"
while full_name.exists():
version += 1
full_name = file.parent / f"{basename}.{version}"
return str(full_name)
else:
os.makedirs(file.parent, exist_ok=True)
return str(file)
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"))
def write_request(response: requests.Response, is_error: bool = False):
from erepublik_script import Citizen
# Remove GET args from url name
url = response.url
last_index = url.index("?") if "?" in url else len(response.url)
name = slugify(response.url[len(Citizen.url):last_index])
html = response.text
try:
json.loads(html)
ext = "json"
except json.decoder.JSONDecodeError:
ext = "html"
if not is_error:
filename = "debug/requests/{}_{}.{}".format(now().strftime('%F_%H-%M-%S'), name, ext)
write_file(filename, html)
else:
return {"name": "{}_{}.{}".format(now().strftime('%F_%H-%M-%S'), name, ext),
"content": html.encode('utf-8'),
"mimetype": "application/json" if ext == "json" else "text/html"}
def send_email(name, content: list, player=None, local_vars=dict, promo: bool = False, captcha: bool = False):
from erepublik_script import Citizen
file_content_template = "<html><head><title>{title}</title></head><body>{body}</body></html>"
if isinstance(player, Citizen):
resp = write_request(player.r, is_error=True)
else:
resp = {"name": "None.html", "mimetype": "text/html",
"content": file_content_template.format(body="<br/>".join(content), title="Error"), }
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)
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)
else:
subject = "[eBot][%s] Bug trace: %s" % (now().strftime('%F %T'), name)
body = "".join(traceback.format_stack()) + \
"\n\n" + \
"\n".join(content)
data = dict(send_mail=True, subject=subject, bugtrace=body)
if promo:
data.update({'promo': True})
elif captcha:
data.update({'captcha': True})
else:
data.update({"bug": True})
files = [('file', (resp.get("name"), resp.get("content"), resp.get("mimetype"))), ]
filename = "log/%s.log" % now().strftime('%F')
if os.path.isfile(filename):
files.append(('file', (filename[4:], open(filename, 'rb'), "text/plain")))
if local_vars:
if "state_thread" in local_vars:
local_vars.pop('state_thread', None)
files.append(('file', ("local_vars.json", json.dumps(local_vars, indent=2,
cls=MyJSONEncoder, sort_keys=True), "application/json")))
if isinstance(player, Citizen):
files.append(('file', ("instance.json", player.to_json(indent=True), "application/json")))
requests.post('https://pasts.72.lv', data=data, files=files)
def parse_input(msg: str) -> bool:
msg += " (y|n):"
data = None
while data not in ['', 'y', 'Y', 'n', 'N']:
try:
data = input(msg)
except EOFError:
data = 'n'
return data in ['', 'y', 'Y']
def parse_config(config=None) -> dict:
if config is None:
config = {}
if not config.get('email'):
config['email'] = input("Player email: ")
if not config.get('password'):
config['password'] = input("Player password: ")
if 'wt' in config:
config['work'] = config['wt']
config['train'] = config['wt']
if 'work' not in config:
config['work'] = parse_input('Should I work')
if 'train' not in config:
config['train'] = parse_input('Should I train')
if 'ot' not in config:
config['ot'] = parse_input('Should I work overtime')
if 'wam' not in config:
config['wam'] = parse_input('Should I WAM')
if 'employ' not in config:
config['employ'] = parse_input('Should I employ employees')
if config['wam'] or config['employ']:
if "autosell" in config:
config.pop("autosell")
if "autosell_raw" in config:
config.pop("autosell_raw")
if "autosell_final" in config:
config.pop("autosell_final")
if 'auto_sell' not in config or not isinstance(config['auto_sell'], list):
if parse_input('Should I auto sell produced products'):
config['auto_sell'] = []
if parse_input("Should I auto sell Food products"):
if parse_input("Should I auto sell Food products"):
config['auto_sell'].append("food")
if parse_input("Should I auto sell Weapon products"):
config['auto_sell'].append("weapon")
if parse_input("Should I auto sell House products"):
config['auto_sell'].append("house")
if parse_input("Should I auto sell Aircraft products"):
config['auto_sell'].append("airplane")
if parse_input("Should I auto sell raw products"):
if parse_input("Should I auto sell Food raw"):
config['auto_sell'].append("foodRaw")
if parse_input("Should I auto sell Weapon raw"):
config['auto_sell'].append("weaponRaw")
if parse_input("Should I auto sell House raw"):
config['auto_sell'].append("houseRaw")
if parse_input("Should I auto sell Airplane raw"):
config['auto_sell'].append("airplaneRaw")
if config['auto_sell']:
if 'auto_sell_all' not in config:
print("When selling produced items should I also sell items already in inventory?")
config['auto_sell_all'] = parse_input('Y - sell all, N - only just produced')
else:
config['auto_sell_all'] = False
if 'auto_buy_raw' not in config:
config['auto_buy_raw'] = parse_input('Should I auto buy raw deficit at WAM or employ')
else:
config['auto_sell'] = []
config['auto_sell_all'] = False
config['auto_buy_raw'] = False
if 'fight' not in config:
config['fight'] = parse_input('Should I fight')
if config.get('fight'):
if 'air' not in config:
config['air'] = parse_input('Should I fight in AIR')
if 'ground' not in config:
config['ground'] = parse_input('Should I fight in GROUND')
if 'all_in' not in config:
print("When full energy should I go all in")
config['all_in'] = parse_input('Y - all in, N - 1h worth of energy')
if 'next_energy' not in config:
config['next_energy'] = parse_input('Should I fight when next pp +1 energy available')
if 'boosters' not in config:
config['boosters'] = parse_input('Should I use +50% dmg boosters')
if 'travel_to_fight' not in config:
config['travel_to_fight'] = parse_input('Should I travel to fight')
if 'epic_hunt' not in config:
config['epic_hunt'] = parse_input('Should I check for epic battles')
if not config['epic_hunt']:
config['epic_hunt_ebs'] = False
if not config['epic_hunt']:
config['epic_hunt_ebs'] = False
elif 'epic_hunt_ebs' not in config:
config['epic_hunt_ebs'] = parse_input('Should I eat EBs when fighting in epic battle')
if 'rw_def_side' not in config:
config['rw_def_side'] = parse_input('Should I fight on defenders side in RWs')
if 'continuous_fighting' not in config:
config['continuous_fighting'] = parse_input('If already fought in any battle, \n'
'should I continue to fight all FF in that battle')
else:
config['air'] = False
config['ground'] = False
config['all_in'] = False
config['next_energy'] = False
config['boosters'] = False
config['travel_to_fight'] = False
config['epic_hunt'] = False
config['epic_hunt_ebs'] = False
config['rw_def_side'] = False
config['continuous_fighting'] = False
if 'debug' not in config:
config['debug'] = parse_input('Should I generate debug files')
if 'random_sleep' not in config:
config['random_sleep'] = parse_input('Should I add random amount (0-120sec) to sleep time')
if 'gold_buy' not in config:
config['gold_buy'] = parse_input('Should I auto buy 10g every day')
if 'interactive' not in config:
config['interactive'] = parse_input('Should I print output to console?')
return config
def normalize_html_json(js: str) -> str:
js = re.sub(r' \'(.*?)\'', lambda a: '"%s"' % a.group(1), js)
js = re.sub(r'(\d\d):(\d\d):(\d\d)', r'\1\2\3', js)
js = re.sub(r'([{\s,])(\w+)(:)(?!"})', r'\1"\2"\3', js)
js = re.sub(r',\s*}', '}', js)
return js
def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None,
interactive: bool = False):
"""
Process error logging and email sending to developer
:param error:
:param interactive: Should print interactively
:param log_info: String to be written in output
:param name: String Instance name
:param exc_info: tuple output from sys.exc_info()
:param citizen: Citizen instance
:param commit_id: Code's version by commit id
"""
type_, value_, traceback_ = exc_info
bugtrace = [] if not commit_id else ["Commit id: %s" % commit_id, ]
bugtrace += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))]
if interactive:
write_interactive_log(log_info)
else:
write_silent_log(log_info)
send_email(name, bugtrace, citizen, local_vars=inspect.trace()[-1][0].f_locals)
def aviator_support(citizen, send_food=False, free_food=None):
forbidden_ids = []
if free_food is None:
free_food = {} # {"q1": 0, "q2": 1000, ...}
context = {'PLAYER_COUNT': 0, 'TABLE': "",
'STARTING_ENERGY': sum([amount * FOOD_ENERGY[q] for q, amount in free_food.items()]),
'TOTAL_CC': 0, 'TOTAL_ENERGY': 0, 'END_ENERGY': 0}
from erepublik_script import Citizen
if not isinstance(citizen, Citizen):
from .classes import ErepublikException
raise ErepublikException("\"citizen\" must be instance of erepublik.Citizen")
citizen.config.interactive = True
aviators = dict()
time_string = "%Y-%m-%d %H:%M:%S"
latest_article = requests.get('https://erep.lv/aviator/latest_article/').json()
for quality, amount in latest_article.get('free_food', {}).items():
free_food[quality] = free_food.get(quality, 0) + amount
if not latest_article.get('status'):
from .classes import ErepublikException
raise ErepublikException('Article ID and week problem')
context.update(WEEK=latest_article.get('week', 0) + 1)
comments = citizen.post_article_comments(citizen.token, latest_article.get('article_id'), 1).json()
ranking = citizen.get_leaderboards_kills_aircraft_rankings(71, 1, 0).json()
if not comments.get("comments", {}):
from .classes import ErepublikException
raise ErepublikException("No comments found")
for comment_data in comments.get("comments", {}).values():
if comment_data.get('authorId') == 1954361:
start_dt = localize_dt(datetime.datetime.strptime(comment_data.get('createdAt'), time_string))
days_ahead = 1 - start_dt.weekday()
if days_ahead <= 0:
days_ahead += 7
end_dt = (good_timedelta(start_dt, datetime.timedelta(days_ahead))).replace(hour=0, minute=0, second=0)
if not comment_data.get('replies', {}):
from .classes import ErepublikException
raise ErepublikException("No replies found")
for reply_data in comment_data.get('replies').values():
if localize_dt(datetime.datetime.strptime(reply_data.get('createdAt'), time_string)) > end_dt:
continue
if re.search(r'piesakos', reply_data.get('message'), re.I):
aviators.update({int(reply_data.get('authorId')): dict(
id=reply_data.get('authorId'), name="", kills=0, rank=0, residency=None, health=0, extra=[],
factories=0
)})
context['PLAYER_COUNT'] = len(aviators)
write_interactive_log("{:^9} | {:<28} | {:4} | {:26} | {:6} | {}".format(
"ID", "Vārds", "Kili", "Gaisa rangs", "Energy", "Aktivizētās mājas"
))
for player_top_data in ranking.get('top'):
player_id = int(player_top_data.get('id'))
if player_id in aviators:
aviators[player_id]["kills"] = int(player_top_data['values'])
for aviator_id, aviator_data in aviators.items():
aviator_info = citizen.get_citizen_profile(aviator_id).json()
aviator_data.update({
'rank': aviator_info['military']['militaryData']['aircraft']['rankNumber'],
'name': aviator_info['citizen']['name'],
'residency': aviator_info['city']['residenceCityId']
})
if aviator_info.get("isBanned"):
aviator_data.update({'health': 0, 'extra': ["BANNED", ]})
else:
if aviator_data['rank'] < 44:
if aviator_data['rank'] < 38:
health = aviator_data['kills'] * 30
else:
health = aviator_data['kills'] * 20
has_pp = False
if aviator_info.get("activePacks"):
has_pp = bool(aviator_info.get("activePacks").get("power_pack"))
max_health = 7 * 24 * (500 if has_pp else 300)
if health < max_health:
aviator_data['health'] = health
else:
aviator_data['health'] = max_health
if not aviator_data["residency"]:
aviator_data['health'] = 0
aviator_data['extra'].append("No residency set")
else:
residency = citizen.get_city_data_residents(
aviator_data["residency"], params={"search": aviator_data['name']}
).json()
for resident in residency.get('widgets', {}).get('residents', {}).get('residents'):
if int(resident.get('citizenId')) == aviator_id:
if resident['numFactories']:
aviator_data['factories'] = resident['numFactories']
else:
aviator_data['factories'] = 0
if not resident.get('activeHouses'):
aviator_data['health'] = 0
if resident['numHouses']:
aviator_data['extra'].append(", ".join(resident['activeHouses']))
else:
aviator_data['extra'].append("Nav māja")
aviator_data['health'] = 0
else:
aviator_data['extra'].append("Rank")
write_interactive_log("{id:>9} | {name:<28} | {kills:4} | {:26} | {health:6} | {}".format(
AIR_RANKS[aviator_data['rank']],
", ".join(aviator_data["extra"]),
**aviator_data)
)
db_post_data = []
for aviator_id, aviator_data in aviators.items():
db_post_data.append(dict(id=aviator_id, name=aviator_data['name'],
rank=aviator_data['rank'], factory_count=aviator_data['factories']))
requests.post('https://erep.lv/aviator/set/', json=db_post_data)
for aviator_id, new in aviators.items():
resp = requests.get('https://erep.lv/aviator/check/{}/'.format(aviator_id))
if not resp.json()['status']:
aviators[aviator_id]['health'] = 0
aviators[aviator_id]['extra'] = ["Nav izmaiņas fabriku skaitā", ]
for player_id in forbidden_ids:
if player_id in aviators:
aviators[player_id]['health'] = 0
if "BANNED" not in aviators[player_id]['extra']:
aviators[player_id]['extra'] = ["Aizliegta pieteikšanās", ]
sent_data = []
if send_food:
for aviator_data in sorted(aviators.values(), key=lambda t: (-t["health"], -t['kills'])):
remaining = aviator_data['health']
if not remaining:
sent_data.append({
"player_id": aviator_data['id'], "name": aviator_data['name'], "quality": 0,
"amount": 0, "energy": 0, "price": 0, "cost": 0,
})
while remaining > 0:
o = []
if free_food:
# Reversed because need to start with higher qualities so that q1 stays available
for quality in reversed(list(free_food.keys())):
if free_food[quality]:
o.append((quality, {'price': 0., 'amount': free_food[quality]}))
else:
free_food.pop(quality)
if not free_food:
offers = citizen.get_market_offers(71, product="food")
o += sorted(offers.items(), key=lambda v: (v[1]['price'] / FOOD_ENERGY[v[0]],
-v[1]['amount'] * FOOD_ENERGY[v[0]]))
for _o in o:
q, q_data = _o
if FOOD_ENERGY[q] <= remaining:
break
else:
write_interactive_log(
"{name} needs to receive extra {remaining}hp".format(name=aviator_data['name'],
remaining=remaining))
break
if q_data['amount'] * FOOD_ENERGY[q] <= remaining:
amount = q_data['amount']
else:
amount = remaining // FOOD_ENERGY[q]
if q_data['price']:
# print(f"citizen._buy_market_offer(offer={q_data['offer_id']}, amount={amount})")
citizen.post_economy_marketplace_actions(citizen.token, amount=amount, buy=True,
offer=q_data["offer_id"])
else:
free_food[q] -= amount
# print(f"citizen.donate_items(citizen_id={aviator_data['id']},
# amount={amount}, industry_id=1, quality={int(q[1])})")
citizen.donate_items(citizen_id=aviator_data['id'], amount=amount, industry_id=1, quality=int(q[1]))
remaining -= amount * FOOD_ENERGY[q]
context['TOTAL_CC'] += q_data['price'] * amount
context['TOTAL_ENERGY'] += amount * FOOD_ENERGY[q]
sent_data.append(
{"player_id": aviator_data['id'], "name": aviator_data['name'], "quality": q, "amount": amount,
"energy": amount * FOOD_ENERGY[q], "price": q_data['price'],
"cost": q_data['price'] * amount, })
with open(get_file("{eday}.csv".format(eday=eday_from_date(now()))), 'a') as f:
f.write('PlayerID, Quality, Amount, Energy, Price, Cost\n')
for player_data in sent_data:
f.write('{player_id}, {quality}, {amount}, {energy}, {price}, {cost}\n'.format(**player_data))
columns = ('[columns][b]Spēlētajs[/b]\n'
'{players}[nextcol][b]Kili[/b]\n'
'{kills}\n'
'[nextcol][right][b]Enerģija[/b]\n'
'{health}\n'
'[/right][/columns]')
player_template = '[b][url=https://www.erepublik.com/en/citizen/profile/{id}]{name}[/url][/b]'
players = []
kills = []
health = []
write_interactive_log("\n".join(["{}: {}".format(q, a) for q, a in free_food.items()]))
context['TOTAL_CC'] = round(context['TOTAL_CC'], 2)
context["END_ENERGY"] = sum([amount * FOOD_ENERGY[q] for q, amount in free_food.items()])
data = {}
for row in sent_data:
pid = int(row['player_id'])
if pid not in data:
data.update({pid: dict(id=pid, name=row['name'], energy=0, cost=0, kills=aviators[pid]['kills'])})
data[pid]["energy"] += row['energy']
data[pid]["cost"] += row['cost']
for pid, player_data in sorted(aviators.items(), key=lambda t: (-t[1]["health"], -t[1]['kills'])):
players.append(player_template.format(id=pid, name=player_data['name']))
kills.append(str(player_data['kills']))
health.append(str(player_data['health'] or ", ".join(player_data['extra'])))
else:
context['TABLE'] = columns.format(
players="\n".join(players),
kills="\n".join(kills),
health="\n".join(health)
)
if os.path.isfile("scripts/KM_piloti.txt"):
with open("scripts/KM_piloti.txt") as f:
template = f.read()
article = template.format(**context)
with open(get_file("{eday}.txt".format(eday=eday_from_date(now()))), "w") as f:
f.write(article)
if send_food:
article_data = dict(
title="[KM] Gaisa maizītes [d{} {}]".format(citizen.eday, citizen.now.strftime("%H:%M")),
content=article,
kind=3
)
from_eday = eday_from_date(good_timedelta(now(), - datetime.timedelta(days=now().weekday() + 6)))
till_eday = eday_from_date(good_timedelta(now(), - datetime.timedelta(days=now().weekday())))
comment_data = dict(
message="★★★★ MAIZE PAR NEDĒĻU [DAY {}-{}] IZDALĪTA ★★★★\n★ Apgādei piesakāmies šī komentāra reply "
"komentāros ar saucienu - piesakos! ★".format(from_eday, till_eday))
total_cc = int(round(context['TOTAL_CC']))
wall_body = ("★★★ [ KONGRESA BALSOJUMS ] ★★★\n\nDotācija pilotiem par d{}-{} {}cc apmērā.\n\n"
"Balsot ar Par/Pret\nBalsošanas laiks 24h līdz d{} {}").format(
from_eday, till_eday, total_cc, citizen.eday + 1, citizen.now.strftime("%H:%M"))
citizen.write_log("Publishing info:\n\n### Article ###\n{}\n\n{}\n\n### Wall ###\n{}".format(
article_data['title'], comment_data['message'], wall_body
))
KM_account: Citizen = Citizen("kara-ministrija@erep.lv", "KMPar0le")
KM_account.set_debug(True)
KM_account.update_citizen_info()
resp = KM_account.publish_article(**article_data)
article_id = resp.history[1].url.split("/")[-3]
comment_data.update({"article_id": article_id})
KM_account.write_article_comment(**comment_data)
citizen.write_on_country_wall(wall_body)
requests.post('https://erep.lv/aviator/latest_article/',
data=dict(week=context["WEEK"], article_id=article_id))

106
examples/battle_launcher.py Normal file
View File

@ -0,0 +1,106 @@
import threading
from datetime import timedelta
from erepublik import Citizen, utils
CONFIG = {
"email": "player@email.com",
"password": "Pa$5w0rd!",
"interactive": True,
"fight": True,
"debug": True,
"battle_launcher": {
# War id: {auto_attack: bool (attack asap when region is available), regions: [region_ids allowed to attack]}
121672: {"auto_attack": False, "regions": [661]},
125530: {"auto_attack": False, "regions": [259]},
125226: {"auto_attack": True, "regions": [549]},
124559: {"auto_attack": True, "regions": [176]},
},
}
def _battle_launcher(player: Citizen):
"""Launch battles. Check every 5th minute (0,5,10...45,50,55) if any battle could be started on specified regions
and after launching wait for 90 minutes before starting next attack so that all battles aren't launched at the same
time. If player is allowed to fight, do 100 hits on the first round in players division.
:param player: Logged in Citizen instance
:type player: Citizen
"""
global CONFIG
finished_war_ids = {*[]}
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)
while not player.stop_threads.is_set():
try:
attacked = False
player.update_war_info()
running_wars = {b.war_id for b in player.all_battles.values()}
for war_id in war_ids - finished_war_ids - running_wars:
war = war_data[war_id]
war_regions = set(war.get("regions"))
auto_attack = war.get("auto_attack")
status = player.get_war_status(war_id)
if status.get("ended", False):
CONFIG["battle_launcher"].pop(war_id, None)
finished_war_ids.add(war_id)
continue
elif not status.get("can_attack"):
continue
if auto_attack or (player.now.hour > 20 or player.now.hour < 2):
for reg in war_regions:
if attacked:
break
if reg in status.get("regions", {}).keys():
player.launch_attack(war_id, reg, status.get("regions", {}).get(reg))
attacked = True
hits = 100
if player.energy.food_fights >= hits and player.config.fight:
for _ in range(120):
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:
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:
break
if attacked:
break
war_ids -= finished_war_ids
if attacked:
next_attack_time = utils.good_timedelta(next_attack_time, timedelta(hours=1, minutes=30))
else:
next_attack_time = utils.good_timedelta(next_attack_time, timedelta(minutes=5))
player.stop_threads.wait(utils.get_sleep_seconds(next_attack_time))
except Exception as e:
player.report_error(f"Task battle launcher ran into error {e}")
# noinspection DuplicatedCode
def main():
player = Citizen(email=CONFIG["email"], password=CONFIG["password"], auto_login=False)
player.config.interactive = CONFIG["interactive"]
player.config.fight = CONFIG["fight"]
player.set_debug(CONFIG.get("debug", False))
player.login()
if CONFIG.get("battle_launcher"):
name = f"{player.name}-battle_launcher-{threading.active_count() - 1}"
state_thread = threading.Thread(target=_battle_launcher, args=(player,), name=name)
state_thread.start()
if __name__ == "__main__":
main()

101
examples/eat_work_train.py Normal file
View File

@ -0,0 +1,101 @@
from datetime import timedelta
from erepublik import Citizen, constants, utils
CONFIG = {
"email": "player@email.com",
"password": "Pa$5w0rd!",
"interactive": True,
"debug": True,
"work": True,
"ot": True, # Work OverTime
"wam": True, # WorkAsManager
"train": True,
}
# noinspection DuplicatedCode
def main():
player = Citizen(email=CONFIG["email"], password=CONFIG["password"], auto_login=False)
player.config.interactive = CONFIG["interactive"]
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 = constants.max_datetime
tasks = {}
if player.config.work:
tasks.update({"work": now})
if player.config.train:
tasks.update({"train": now})
if player.config.ot:
tasks.update({"ot": now})
if player.config.wam:
tasks.update({"wam": now.replace(hour=14, minute=0)})
while True:
try:
player.update_all()
if tasks.get("work", dt_max) <= now:
player.write_log("Doing task: work")
player.update_citizen_info()
player.work()
if player.config.ot:
tasks["ot"] = now
player.collect_daily_task()
next_time = utils.good_timedelta(now.replace(hour=0, minute=0, second=0), timedelta(days=1))
tasks.update({"work": next_time})
if tasks.get("train", dt_max) <= now:
player.write_log("Doing task: train")
player.update_citizen_info()
player.train()
player.collect_daily_task()
next_time = utils.good_timedelta(now.replace(hour=0, minute=0, second=0), timedelta(days=1))
tasks.update({"train": next_time})
if tasks.get("wam", dt_max) <= now:
player.write_log("Doing task: Work as manager")
success = player.work_as_manager()
if success:
next_time = utils.good_timedelta(
now.replace(hour=14, minute=0, second=0, microsecond=0), timedelta(days=1)
)
else:
next_time = utils.good_timedelta(now.replace(second=0, microsecond=0), timedelta(minutes=30))
tasks.update({"wam": next_time})
if tasks.get("ot", dt_max) <= now:
player.update_job_info()
player.write_log("Doing task: work overtime")
if now > player.my_companies.next_ot_time:
player.work_ot()
next_time = now + timedelta(minutes=60)
else:
next_time = player.my_companies.next_ot_time
tasks.update({"ot": next_time})
closest_next_time = dt_max
next_tasks = []
for task, next_time in sorted(tasks.items(), key=lambda s: s[1]):
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(
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:
player.report_error(f"Task main loop ran into error: {e}")
if __name__ == "__main__":
main()

11
pyproject.toml Normal file
View File

@ -0,0 +1,11 @@
[tool.black]
line-length = 120
target-version = ['py38', 'py39']
extend-exclude = 'venv'
workers = 4
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 120

16
requirements-dev.txt Normal file
View File

@ -0,0 +1,16 @@
-r requirements.txt
-r requirements-tests.txt
bump2version==1.0.1
coverage==6.3.2
edx-sphinx-theme==3.0.0
flake8==4.0.1
ipython>=8.1.1
jedi!=0.18.0
isort==5.10.1
pre-commit==2.17.0
pur==6.0.1
responses==0.18.0
Sphinx==4.4.0
twine==3.8.0
wheel==0.37.1
black==22.1.0

2
requirements-tests.txt Normal file
View File

@ -0,0 +1,2 @@
-r requirements.txt
pytest==7.0.1

View File

@ -1,6 +1,4 @@
ipython==7.3.0
pycryptodome==3.7.3
PyInstaller==3.4
python-slugify==2.0.1
pytz==2018.9
requests==2.21.0
PySocks==1.7.1
pytz>2021.0
requests>2.25.0,!=2.27.0
requests-toolbelt==0.9.1

View File

@ -1,10 +0,0 @@
pip==19.1.1
bumpversion==0.5.3
wheel==0.32.1
watchdog==0.9.0
flake8==3.5.0
tox==3.5.2
coverage==4.5.1
Sphinx==1.8.1
twine==1.12.1
ipython==7.3.0

View File

@ -1,23 +1,41 @@
[bumpversion]
current_version = 0.1.1
current_version = 0.29.2.3
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
serialize = {major}.{minor}.{patch}.{dev}
{major}.{minor}.{patch}
[bumpversion:file:setup.py]
search = version='{current_version}'
replace = version='{new_version}'
search = version="{current_version}"
replace = version="{new_version}"
[bumpversion:file:erepublik_script/__init__.py]
search = __version__ = '{current_version}'
replace = __version__ = '{new_version}'
[bumpversion:file:erepublik/__init__.py]
search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"
[bdist_wheel]
universal = 1
[flake8]
exclude = docs
exclude = docs,.git,log,debug,venv
line_length = 120
max-line-length = 120
ignore = E722
ignore = D100,D101,D102,D103,E203
[aliases]
[pycodestyle]
line_length = 140
max-line-length = 140
exclude = .git,log,debug,venv, build
[mypy]
python_version = 3.9
check_untyped_defs = True
ignore_missing_imports = False
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
[isort]
multi_line_output = 2
line_length = 120

View File

@ -3,48 +3,52 @@
"""The setup script."""
from setuptools import setup, find_packages
from setuptools import find_packages, setup
with open('README.rst') as readme_file:
with open("README.rst") as readme_file:
readme = readme_file.read()
with open('HISTORY.rst') as history_file:
with open("HISTORY.rst") as history_file:
history = history_file.read()
requirements = ['Click>=6.0', 'pytz==2018.9', 'requests==2.21.0', 'python-slugify==2.0.1']
with open("requirements.txt") as requirements_file:
requirements = requirements_file.read()
requirements = requirements.split()
setup_requirements = [ ]
setup_requirements = []
test_requirements = [ ]
with open("requirements-tests.txt") as test_req_file:
test_requirements = test_req_file.read()
test_requirements = [
line.strip() for line in test_requirements.split() if line.strip()[:2].strip() not in ("#", "-r")
]
setup(
author="Eriks Karls",
author_email='eriks@72.lv',
author_email="eriks@72.lv",
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
],
description="Python package for eRepublik automated playing",
entry_points={
'console_scripts': [
'erepublik_script=erepublik_script.cli:main',
],
},
description="Python package for automated eRepublik playing",
entry_points={},
install_requires=requirements,
license="MIT license",
long_description=readme + '\n\n' + history,
license="GPLv3",
long_description=readme + "\n\n" + history,
include_package_data=True,
keywords='erepublik_script',
name='erepublik_script',
packages=find_packages(include=['erepublik_script']),
keywords="erepublik",
name="eRepublik",
packages=find_packages(include=["erepublik"]),
python_requires=">=3.8, <4",
setup_requires=setup_requirements,
test_suite='tests',
test_suite="tests",
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik_script',
version='0.1.1',
url="https://github.com/eeriks/erepublik/",
version="0.29.2.3",
zip_safe=False,
)

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Unit test package for erepublik_script."""
"""Unit test package for erepublik."""

View File

@ -1,34 +1,139 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for `erepublik_script` package."""
"""Tests for `erepublik` package."""
import unittest
from click.testing import CliRunner
from erepublik_script import Citizen
from erepublik_script import cli
from erepublik import Citizen
class TestErepublik_script(unittest.TestCase):
"""Tests for `erepublik_script` package."""
class TestErepublik(unittest.TestCase):
"""Tests for `erepublik` package."""
def setUp(self):
"""Set up test fixtures, if any."""
self.citizen = Citizen("email", "password", False)
self.citizen.config.interactive = False
def tearDown(self):
"""Tear down test fixtures, if any."""
def test_should_do_levelup(self):
self.citizen.energy.energy = 5950
self.citizen.energy.interval = 30
self.citizen.energy.limit = 6000
self.citizen.details.xp = 14850
self.assertTrue(self.citizen.should_do_levelup)
def test_000_something(self):
"""Test something."""
self.citizen.energy.energy = 1000
self.assertFalse(self.citizen.should_do_levelup)
def test_command_line_interface(self):
"""Test the CLI."""
runner = CliRunner()
result = runner.invoke(cli.main)
assert result.exit_code == 0
assert 'erepublik_script.cli.main' in result.output
help_result = runner.invoke(cli.main, ['--help'])
assert help_result.exit_code == 0
assert '--help Show this message and exit.' in help_result.output
# def deprecated_test_should_travel_to_fight(self):
# self.citizen.config.always_travel = True
# self.assertTrue(self.citizen.should_travel_to_fight())
# self.citizen.config.always_travel = False
# self.assertFalse(self.citizen.should_travel_to_fight())
#
# self.citizen.energy.energy = 5960
# self.citizen.energy.interval = 30
# self.citizen.energy.limit = 6000
# self.citizen.details.xp = 14850
# self.assertTrue(self.citizen.should_travel_to_fight())
# self.citizen.details.xp = 15000
# self.citizen.energy.energy = 5000
# self.assertFalse(self.citizen.should_travel_to_fight())
#
# self.citizen.energy.energy = 5910
# self.assertTrue(self.citizen.should_travel_to_fight())
# self.citizen.energy.energy = 5900
# self.assertFalse(self.citizen.should_travel_to_fight())
#
# # self.citizen.next_reachable_energy and self.citizen.config.next_energy
# self.citizen.config.next_energy = True
# self.citizen.energy.limit = 10000
# self.citizen.details.next_pp = [5000, 5250, 5750, 6250, 6750]
# self.citizen.details.pp = 4900
# self.citizen.energy.energy = 8510
# self.assertEqual(self.citizen.next_reachable_energy, 850)
# self.citizen.energy.energy = 8490
# self.assertTrue(self.citizen.should_travel_to_fight())
# self.assertEqual(self.citizen.next_reachable_energy, 350)
# self.citizen.energy.energy = 250
# self.assertFalse(self.citizen.should_travel_to_fight())
# self.assertEqual(self.citizen.next_reachable_energy, 0)
#
# def deprecated_test_should_fight(self):
# 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))
#
# self.citizen.config.fight = True
#
# # Level up
# self.citizen.energy.limit = 3000
# self.citizen.details.xp = 24705
# if not is_wc_close:
# self.assertEqual(self.citizen.should_fight(), (0, "Level up", False))
#
# self.citizen.energy.energy = 5950
# self.citizen.energy.interval = 30
# self.assertEqual(self.citizen.should_fight(), (900, "Level up", True))
# self.citizen.my_companies.ff_lockdown = 160
# self.assertEqual(self.citizen.should_fight(), (900, "Level up", True))
# self.citizen.my_companies.ff_lockdown = 0
#
# # Level up reachable
# self.citizen.details.xp = 24400
# self.assertEqual(self.citizen.should_fight(), (305, "Fighting for close Levelup. Doing 305 hits", True))
# self.citizen.my_companies.ff_lockdown = 160
# self.assertEqual(self.citizen.should_fight(), (305, "Fighting for close Levelup. Doing 305 hits", True))
# self.citizen.my_companies.ff_lockdown = 0
#
# self.citizen.details.xp = 21000
# self.assertEqual(self.citizen.should_fight(), (75, "Obligatory fighting for at least 75pp", True))
# self.citizen.my_companies.ff_lockdown = 160
# self.assertEqual(self.citizen.should_fight(), (75, "Obligatory fighting for at least 75pp", True))
# self.citizen.my_companies.ff_lockdown = 0
# self.citizen.details.pp = 80
#
# # All-in (type = all-in and full ff)
# self.citizen.config.all_in = True
# self.assertEqual(self.citizen.should_fight(), (595, "Fighting all-in. Doing 595 hits", False))
# self.citizen.my_companies.ff_lockdown = 160
# self.assertEqual(
# self.citizen.should_fight(),
# (435, "Fight count modified (old count: 595 | FF: 595
# | WAM ff_lockdown: 160 | New count: 435)", False),
# )
# self.citizen.my_companies.ff_lockdown = 0
#
# self.citizen.config.air = True
# self.citizen.energy.energy = 4000
# self.assertEqual(self.citizen.should_fight(), (400, "Fighting all-in in AIR. Doing 400 hits", False))
# self.citizen.my_companies.ff_lockdown = 160
# self.assertEqual(
# self.citizen.should_fight(),
# (240, "Fight count modified (old count: 400 | FF: 400
# | WAM ff_lockdown: 160 | New count: 240)", False),
# )
# self.citizen.my_companies.ff_lockdown = 0
# self.citizen.config.all_in = False
#
# self.citizen.config.next_energy = True
# self.citizen.energy.limit = 10000
# self.citizen.details.next_pp = [100, 150, 250, 400, 500]
# self.assertEqual(self.citizen.should_fight(), (320, "Fighting for +1 energy. Doing 320 hits", False))
# self.citizen.my_companies.ff_lockdown = 160
# self.assertEqual(
# self.citizen.should_fight(),
# (160, "Fight count modified (old count: 320 | FF: 400
# | WAM ff_lockdown: 160 | New count: 160)", False),
# )
# self.citizen.my_companies.ff_lockdown = 0
# self.citizen.energy.limit = 3000
# self.citizen.details.next_pp = [19250, 20000]
# self.citizen.config.next_energy = False
#
# # 1h worth of energy
# self.citizen.energy.energy = 5910
# self.assertEqual(self.citizen.should_fight(), (30, "Fighting for 1h energy. Doing 30 hits", True))

19
tox.ini
View File

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