Compare commits

...

474 Commits

Author SHA1 Message Date
3c316bada3 Bump version: 0.25.0.3 → 0.25.0.4 2021-07-10 01:14:30 +03:00
ea03979943 Energy variable bugfix 2021-07-10 01:13:21 +03:00
9aae685b21 Bump version: 0.25.0.2 → 0.25.0.3 2021-07-10 00:47:51 +03:00
cae94d7aa8 requirement update 2021-07-10 00:47:40 +03:00
fae7b0fd37 Bump version: 0.25.0.1 → 0.25.0.2 2021-07-10 00:37:46 +03:00
b7771b4da2 Update 2021-07-10 00:36:22 +03:00
e3a10af101 Bump version: 0.25.0 → 0.25.0.1 2021-05-25 09:54:44 +03:00
33a5bcacf1 Backward compatability bugfix 2021-05-25 09:54:35 +03:00
342ca2e1cc Bump version: 0.24.2.4 → 0.25.0 2021-05-25 09:49:49 +03:00
580240a015 isort 2021-05-25 09:49:27 +03:00
1517103ba3 Remove deprecated eat task from examples 2021-05-25 09:48:08 +03:00
3dac8c5e74 New eating 2021-05-25 09:43:47 +03:00
8cf86fb9d3 Bump version: 0.24.2.3 → 0.24.2.4 2021-04-27 12:21:54 +03:00
cf927df6e6 bugfix 2021-04-27 12:21:50 +03:00
a6f5dbd05f Bump version: 0.24.2.2 → 0.24.2.3 2021-04-27 12:19:22 +03:00
967afa472f Rank.__str__ 2021-04-27 12:18:56 +03:00
a65568cd0c Bump version: 0.24.2.1 → 0.24.2.2 2021-04-27 11:05:57 +03:00
6e45334d99 Rank comparision 2021-04-27 11:05:51 +03:00
936a1010a6 Bump version: 0.24.2 → 0.24.2.1 2021-04-26 13:12:32 +03:00
acc528cb1d bugfix 2021-04-26 13:12:28 +03:00
614d273104 Bump version: 0.24.1 → 0.24.2 2021-04-23 13:46:51 +03:00
95966764e8 Updated rank constants 2021-04-23 13:45:58 +03:00
f52b078e6a Captcha challange solving simplification 2021-03-05 16:11:52 +02:00
3af27f6512 Cookie dump migration 2021-03-03 13:15:26 +02:00
6276242260 bugfixes 2021-03-03 13:13:19 +02:00
45623de97b Reporter update to queue messages if network error occures 2021-02-06 15:33:08 +02:00
25f932121c Code cleanup and JSONEncoder update to support dumping Logger instances 2021-02-06 15:32:30 +02:00
61be2b1edf Bump version: 0.24.0.5 → 0.24.1 2021-02-06 12:22:37 +02:00
ce9034ad24 Legacy fight support until 2021-02-08 00:00:00 2021-02-06 12:22:23 +02:00
69b2073b74 more fixes 2021-02-06 11:07:23 +02:00
eb048bf9f8 session dump 2021-02-06 11:07:03 +02:00
fe1206dc84 Cookie magick 2021-02-05 13:47:30 +02:00
a2a1ed3dad Cookie magick 2021-02-05 13:37:32 +02:00
39c8f6913e Cookie magick 2021-02-04 20:33:59 +02:00
d7b15b3708 changes 2021-02-04 13:50:24 +02:00
4fe3efa045 fix 2021-02-03 21:01:37 +02:00
14bcb46735 More precisly mimic javascript's JSON.stringify() 2021-02-03 20:13:51 +02:00
b04cc896d8 bugfix 2021-02-03 20:13:15 +02:00
f07062788b LocalVars 2021-02-03 18:45:23 +02:00
4504bdaa97 don't lose image id 2021-02-03 18:15:06 +02:00
ac135614cc LocalVars 2021-02-03 17:27:55 +02:00
41752e1f2e clickMatrix bugfix 2021-02-03 17:08:58 +02:00
a1739e627e refactoring 2021-02-03 16:43:49 +02:00
12ff11deea refactoring 2021-02-03 16:30:52 +02:00
4e3a16b8d4 Don't print stack and exc traces 2021-02-03 16:16:23 +02:00
5f56f59ab8 bugfi 2021-02-03 14:15:32 +02:00
50c66efbda report error update 2021-02-03 14:03:43 +02:00
47b3154c6a Typehints 2021-02-03 13:36:26 +02:00
632e4e8ad2 HttpHandler improvements 2021-02-03 12:02:06 +02:00
7c0d66f126 deploy 2021-02-03 02:03:02 +02:00
842fb64dae deploy 2021-02-03 01:58:18 +02:00
b22349cb1a Disabled oldSchool Shoooooooooot 2021-02-03 01:48:53 +02:00
a9ced91741 deploy 2021-02-03 01:47:50 +02:00
e374562189 Bump version: 0.24.0.4 → 0.24.0.5 2021-02-03 01:09:51 +02:00
e0b64e09b1 bugfix 2021-02-03 01:09:43 +02:00
c51337d249 Bump version: 0.24.0.3 → 0.24.0.4 2021-02-03 01:07:21 +02:00
f4896e0b79 Merge branch 'bugfix-v0.23.4'
# Conflicts:
#	erepublik/__init__.py
#	setup.cfg
#	setup.py
2021-02-03 00:50:03 +02:00
13f5c673ad Migrate Citizen.write_log to self.logger.warning where applicable 2021-02-03 00:48:50 +02:00
e95ffbd505 cleanup 2021-02-03 00:48:13 +02:00
5e638806b5 Switch from manual log creation to Python logging 2021-02-03 00:47:52 +02:00
7860fa3669 Bump version: 0.23.4.15 → 0.23.4.16 2021-02-02 23:57:58 +02:00
e38f603e8b Division switch bugfix for option to switch side 2021-02-02 23:57:54 +02:00
0e1c42a8fb Bump version: 0.23.4.14 → 0.23.4.15 2021-02-02 23:52:38 +02:00
ddc412b348 accesspoint update to change side in RW 2021-02-02 23:52:32 +02:00
7f3bd9b864 Bump version: 0.24.0.2 → 0.24.0.3 2021-01-28 01:46:11 +02:00
9154d2e493 Captcha solving 2021-01-28 01:46:03 +02:00
8e8b882ace Bump version: 0.24.0.1 → 0.24.0.2 2021-01-28 00:37:12 +02:00
d7b020c7ea bugfix 2021-01-28 00:37:10 +02:00
cb567bf5c0 Bump version: 0.24.0 → 0.24.0.1 2021-01-28 00:31:53 +02:00
bb6d1be1b5 bugfix 2021-01-28 00:31:46 +02:00
c546c8f5eb Bump version: 0.23.4.14 → 0.24.0 2021-01-27 23:43:48 +02:00
9acc2d2e65 Lets deploy! Preperation for 8th of February 2021-01-27 23:43:29 +02:00
51a15874f2 Bump version: 0.23.4.13 → 0.23.4.14 2021-01-21 23:12:05 +02:00
af8f764396 Double-quoted strings converted into single-quoted strings 2021-01-21 23:11:39 +02:00
65b555f2bd Minor tweaks and double-quoted strings converted into single-quoted strings 2021-01-21 19:05:00 +02:00
319b4414df Removed unused import 2021-01-21 19:01:03 +02:00
b71cb0c263 Minor tweaks and double-quoted strings converted into single-quoted strings 2021-01-21 19:00:40 +02:00
0936dee06c Bump version: 0.23.4.12 → 0.23.4.13 2021-01-20 22:25:16 +02:00
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
c57bf99976 If citizen is on restricted IP, clear cookies when session times out 2021-01-20 16:42:36 +02:00
995b45464f Bump version: 0.23.4.11 → 0.23.4.12 2021-01-20 16:12:51 +02:00
ebe28c948a requirement update 2021-01-20 16:12:40 +02:00
34d7230faf Response serialization update to include status code. Json encoder and decoder updates 2021-01-20 16:07:19 +02:00
3235991cce User agent updates 2021-01-20 15:15:31 +02:00
3fba1d6b3d Bump version: 0.23.4.10 → 0.23.4.11 2021-01-18 16:35:35 +02:00
88c8d5a9a0 Unified all event setting 2021-01-18 16:35:31 +02:00
382749a8d8 Bump version: 0.23.4.9 → 0.23.4.10 2021-01-18 16:12:31 +02:00
0c877e315b as_dict updates 2021-01-18 16:12:26 +02:00
c95f642fee Citizen.as_dict rewrite 2021-01-18 16:00:29 +02:00
da0276f9a6 Removed unused CitizenMilitary.boosters property
Explicitly updated all Citizen*.as_dict properties
2021-01-18 15:22:53 +02:00
f89f91e969 Concurrency checks placed directly in Citizen class
Added concurrency checks also for citizen updates

Sentry: EREPUBLIK-BOT-77
2021-01-18 15:11:40 +02:00
56c2ca1b6e House renewal price check bugfix 2021-01-12 08:05:57 +02:00
3cee2d1f0f Bump version: 0.23.4.8 → 0.23.4.9 2021-01-11 21:05:22 +02:00
0ed03877ce Damage booster's name change 2021-01-11 21:05:12 +02:00
491c9f5efe CitizenMilitary.countries were never used - all countries are available at constants.COUNTIRES, but allies and deployed allies are also available on every Battle 2021-01-11 16:27:23 +02:00
8445b556e7 Bump version: 0.23.4.7 → 0.23.4.8 2021-01-11 16:21:14 +02:00
d02c5c2969 dev requirement update 2021-01-11 16:20:58 +02:00
bff4183cb6 Added option to fight with energy bars, count small energy bars more precisely 2021-01-11 16:19:10 +02:00
47f5142837 Bump version: 0.23.4.6 → 0.23.4.7 2021-01-08 11:09:39 +02:00
a32fd039dd Report details about fighting also on telegram 2021-01-08 11:08:50 +02:00
311e684c0c Add telegram fight reporting API, Fix message duplication on TelegramReporter initialization 2021-01-08 11:07:59 +02:00
52038a86d5 PackBooster icon update 2021-01-07 17:00:59 +02:00
c35a107641 Bump version: 0.23.4.5 → 0.23.4.6 2021-01-07 15:55:02 +02:00
b53b2f0fae Update loop 2021-01-07 15:54:56 +02:00
8da9b6221a Bump version: 0.23.4.4 → 0.23.4.5 2021-01-07 15:36:33 +02:00
caa41c85f6 minor tweaks 2021-01-07 15:36:27 +02:00
da3b5d5768 Update inventory on booster activation 2021-01-07 15:31:46 +02:00
f96d0233f9 CSRF bugfix 2021-01-07 15:31:10 +02:00
c693a5182e Merge branch 'master' of github.com:eeriks/erepublik 2021-01-07 15:14:17 +02:00
0dfba6a9ff Find battle and fight - reuse already calculated hit count 2021-01-07 15:14:11 +02:00
ee3eca9658 Bump version: 0.23.4.3 → 0.23.4.4 2021-01-06 23:37:05 +02:00
0e8680daca bugfix 2021-01-06 23:35:25 +02:00
8e3606f1a3 Doc update 2021-01-05 19:47:43 +02:00
bd0bcc9ac7 Bump version: 0.23.4.2 → 0.23.4.3 2021-01-05 19:35:00 +02:00
c6f2226e64 . 2021-01-05 19:34:43 +02:00
308807d800 isort, pre-commit 2021-01-05 19:29:20 +02:00
91565d840e Added endpoint for collect all WC rewards 2021-01-05 19:18:20 +02:00
b4a9dd88f8 config generator bugdix 2021-01-05 19:17:50 +02:00
8435aa23ba Bump version: 0.23.4.1 → 0.23.4.2 2021-01-05 16:30:49 +02:00
09cd275a69 type bugfix 2021-01-05 16:30:42 +02:00
e23a67231e Bump version: 0.23.4 → 0.23.4.1 2021-01-05 16:19:43 +02:00
6a03d99abf type bugfix 2021-01-05 16:19:27 +02:00
2e344749a6 Bump version: 0.23.3.4 → 0.23.4 2021-01-05 15:50:55 +02:00
5aecefbd9d Protect those precious air boosters and 100% ground boosters 2021-01-05 15:50:38 +02:00
8b9ee5042d Bugfixes and inventory redone 2021-01-05 15:41:51 +02:00
1e93006c3d History updates 2020-12-17 18:18:46 +02:00
b13bfcdbf3 Bump version: 0.23.3.3 → 0.23.3.4 2020-12-17 18:09:43 +02:00
771dbdf826 With invalid bomb (bomb not in inventory) quit after first try 2020-12-17 18:03:04 +02:00
9646d112d2 If exception occures - set concurrency as available 2020-12-17 18:02:14 +02:00
a09c37a065 Bump version: 0.23.3.2 → 0.23.3.3 2020-12-17 15:23:39 +02:00
ba75e961fa Updated config generator 2020-12-17 15:20:21 +02:00
3b5780dbd6 Updated config generator 2020-12-17 15:18:22 +02:00
fccd0134b5 Merge branch 'remove-tox' 2020-12-17 14:07:46 +02:00
b9010fa856 Fixed MRO error 2020-12-17 14:07:29 +02:00
3e5410289e . 2020-12-17 14:03:45 +02:00
661a019b0a Broken MRO 2020-12-17 14:03:30 +02:00
23d682959d Party presidency and congress election bugfix if player is not part of a party 2020-12-17 12:13:44 +02:00
5806ccb6ca Buy food if unable to work/train because of food shortage 2020-12-14 13:51:16 +02:00
a9bc78b701 Sleep accepts floats and decimals, not only integers. Bomb deploy should use Citizen.sleep instead of utils.sleep 2020-12-11 17:22:45 +02:00
c458eb4b1c Bump version: 0.23.3.1 → 0.23.3.2 2020-12-11 11:12:34 +02:00
4af4d284c9 Updated dev requirement versions 2020-12-11 11:12:27 +02:00
104c1a0b16 Bump required python version 2020-12-11 11:12:01 +02:00
86f820771b Hits done bugfix 2020-12-11 11:11:25 +02:00
2a7af0cb7d Bump version: 0.23.3 → 0.23.3.1 2020-12-10 14:37:12 +02:00
94de509026 __all__ definitions 2020-12-10 14:36:34 +02:00
82d913bc47 Bump version: 0.23.2.11 → 0.23.3 2020-12-10 13:26:11 +02:00
c462eac369 Bomb deploy update 2020-12-10 13:25:47 +02:00
d522a4faeb Bump version: 0.23.2.10 → 0.23.2.11 2020-12-09 13:33:26 +02:00
084a47b07a TelegramBot renamed to TelegramReporter 2020-12-09 13:30:18 +02:00
5082855440 Default Telegram's token moved to TelegramReporter 2020-12-09 13:29:28 +02:00
c9971f3570 str.format() -> f-string 2020-12-09 13:28:56 +02:00
c0122eccdf Bump version: 0.23.2.9 → 0.23.2.10 2020-12-08 18:05:21 +02:00
2524173da0 Telegram bot's token can be reused 2020-12-08 18:05:18 +02:00
4a4b991faf Telegram bot's token can be reused 2020-12-08 18:05:01 +02:00
e87c48124c Bump version: 0.23.2.8 → 0.23.2.9 2020-12-07 14:10:52 +02:00
f0f47566a0 Bugfix: Don't travel to renew house if You can't afford the house 2020-12-07 14:10:40 +02:00
f88eaccf67 Bump version: 0.23.2.7 → 0.23.2.8 2020-12-07 14:01:29 +02:00
7be129a781 Quickfix for forbidding Dictatorship/Liberation wars 2020-12-07 14:01:22 +02:00
32546505b9 Bump version: 0.23.2.6 → 0.23.2.7 2020-12-04 13:13:17 +02:00
00ceabf8e3 cleanup 2020-12-04 13:13:10 +02:00
19113da8e6 cleanup 2020-12-04 13:11:11 +02:00
3ad7172925 cleanup 2020-12-04 13:08:42 +02:00
179f1a0892 Only init TelegramReporter if both chat id and token have been supplied 2020-12-04 13:08:16 +02:00
65adf707e2 Only init TelegramReporter if both chat id and token have been supplied 2020-12-04 13:08:04 +02:00
29f9ce5ccc Bump version: 0.23.2.5 → 0.23.2.6 2020-12-02 18:40:41 +02:00
fa3881bf10 Updated classes.OfferItem default price to be higher considerably higher than max price for cheapest most expensive items on sale (Q5 houses), Updated house renewal 2020-12-02 18:40:33 +02:00
c1e8e94cba Bump version: 0.23.2.4 → 0.23.2.5 2020-12-02 13:00:51 +02:00
8133461fd7 pep8 2020-12-02 13:00:33 +02:00
c87333411a Hotfix 2020-12-02 12:56:06 +02:00
d679006b34 small fix 2020-12-01 18:56:14 +02:00
fa308db074 Bump version: 0.23.2.3 → 0.23.2.4 2020-12-01 18:49:35 +02:00
a080163af9 Bugfix 2020-12-01 18:49:33 +02:00
8aa159edf7 Bump version: 0.23.2.2 → 0.23.2.3 2020-12-01 18:49:01 +02:00
1211a98227 Bugfix 2020-12-01 18:48:52 +02:00
734a5ef2b6 Bump version: 0.23.2.1 → 0.23.2.2 2020-12-01 18:11:09 +02:00
5c3b405ca8 Added utils.wait_for_lock decorator to wait for concurrency release 2020-12-01 18:11:00 +02:00
75de8fce96 File closing before return 2020-12-01 15:09:45 +02:00
01e5e44350 File closing before return 2020-12-01 15:07:17 +02:00
500409f74a Bump version: 0.23.2 → 0.23.2.1 2020-12-01 15:01:00 +02:00
9d79bb713c naming bugfix 2020-12-01 15:00:55 +02:00
684b2a6ba4 Bump version: 0.23.1.2 → 0.23.2 2020-12-01 14:22:40 +02:00
447aee8134 Concurrency flag to guard against 2020-12-01 14:22:26 +02:00
64249fc3d9 History 2020-12-01 13:11:45 +02:00
95a78b6e7e Bump version: 0.23.1.1 → 0.23.1.2 2020-12-01 13:02:29 +02:00
b338ea598a Seperated battle finding logic from CitizenMilitary.find_battle_and_fight method 2020-12-01 13:02:10 +02:00
b88e7973e8 Bump version: 0.23.1 → 0.23.1.1 2020-11-30 18:54:44 +02:00
c671425531 Base dmg calculations 2020-11-30 18:54:35 +02:00
08937d06e4 Bump version: 0.23.0 → 0.23.1 2020-11-30 18:20:48 +02:00
cec4510831 Requirement update 2020-11-30 18:20:42 +02:00
cfb9501647 PEP8 2020-11-30 18:16:13 +02:00
d419211955 Get max hit value for divisions on current side 2020-11-30 18:12:36 +02:00
0ea144db17 Added method to get division stats 2020-11-30 17:44:23 +02:00
1418f580cd Spin wheel of fortune updates 2020-11-30 17:43:42 +02:00
49575bddf5 History update for v0.23.0 2020-11-26 18:39:25 +02:00
c874247335 Bump version: 0.22.3.3 → 0.23.0 2020-11-26 18:32:46 +02:00
6e9def4394 Added .get_article(int) and .delete_article(int) methods to CitizenMedia class 2020-11-26 18:32:31 +02:00
d6fbaa7945 Maverick fighting should be explicitly enabled through Citizen.config 2020-11-26 16:16:15 +02:00
eb740c60c7 Bump version: 0.22.3.2 → 0.22.3.3 2020-11-20 18:54:20 +02:00
240c409739 Travel to region bugfix 2020-11-20 18:54:12 +02:00
f348a315c5 Bump version: 0.22.3.1 → 0.22.3.2 2020-11-20 16:57:43 +02:00
7a09eea2b4 CitizenAnniversary.collect_map_quest_node() now accepts argument extra, to collect extra reward 2020-11-20 16:57:36 +02:00
81b1069cf0 Bump version: 0.22.3 → 0.22.3.1 2020-11-20 14:46:04 +02:00
429d43df15 CitizenTasks.work() bugfix: When employer out of money - resign 2020-11-20 14:45:58 +02:00
c81986d65e CitizenEconomy.post_market_offer() bugfix 2020-11-20 14:31:16 +02:00
7bb988f716 PEP8 2020-11-20 14:24:11 +02:00
16cae24712 Bump version: 0.22.2.1 → 0.22.3 2020-11-16 12:12:30 +02:00
3e051fe906 History update 2020-11-16 12:07:20 +02:00
aa9cda9314 eRepublik temp technical difficulties 2020-11-16 12:06:59 +02:00
fc66db8cab Config file generator update 2020-11-16 11:56:37 +02:00
6bbc4f8768 eRepublik has both airplaneRaw and aircraftRaw. Possible future bugs because of this! 2020-11-16 11:56:13 +02:00
160b32a914 Buy market offer directly 2020-11-16 11:52:55 +02:00
a51c3c620e Python round to even bugfix.
`{"stock":10.494533,"consume":10.5}` rounds to 0
2020-11-16 11:52:27 +02:00
a1c26468eb UA updates 2020-11-12 11:47:20 +02:00
4895ae3663 Bump version: 0.22.2 → 0.22.2.1 2020-11-10 15:39:15 +02:00
b8d7cc8d7c Example updates 2020-11-10 15:38:30 +02:00
1d0645b490 Bump version: 0.22.1.5 → 0.22.2 2020-11-09 16:34:52 +02:00
30cf6203b7 History update 2020-11-09 16:34:41 +02:00
a32e88218d CitizenEconomy.get_market_offers now accepts searching tickets and the new aircraft qualities 2020-11-09 16:28:21 +02:00
a031da0ee7 Bump version: 0.22.1.4 → 0.22.1.5 2020-11-04 17:22:44 +02:00
bdb13fa4ae History update, WAM actions are now more verbose 2020-11-04 17:22:35 +02:00
e1e3b33d46 requirement update 2020-11-03 18:10:43 +02:00
e09ca143b1 Bump version: 0.22.1.3 → 0.22.1.4 2020-11-03 14:31:43 +02:00
61d0599295 House renewal bugfix 2020-11-03 14:31:37 +02:00
1ef600492a House renewal bugfix 2020-11-03 14:30:20 +02:00
377eda6445 Inventory booster bugfix 2020-11-03 14:29:42 +02:00
fd13667ca8 Bump version: 0.22.1.2 → 0.22.1.3 2020-10-30 12:58:26 +02:00
0479afcabe Report if out of money to work as manager 2020-10-30 12:58:18 +02:00
486f022f35 Unified Item namig from constants.INDUSTRIES 2020-10-30 12:43:45 +02:00
638373e452 Bump version: 0.22.1.1 → 0.22.1.2 2020-10-27 17:04:40 +02:00
04f357cc70 Bugfix 2020-10-27 17:04:37 +02:00
ed4ffe5af6 Bump version: 0.22.1 → 0.22.1.1 2020-10-22 18:04:13 +03:00
8aa90a7dbf Lint 2020-10-22 18:04:06 +03:00
e798859105 Bump version: 0.22.0 → 0.22.1 2020-10-22 16:27:09 +03:00
241f1642ce Session dump/load, Citizen.inventory update, memory and network optimization, python3.6 support 2020-10-22 16:26:59 +03:00
a4128b5d89 Bump version: 0.21.5.8 → 0.22.0 2020-10-21 14:46:24 +03:00
22c2a0ffd2 New Features!
Dump session to file!
Load session from file!
2020-10-21 14:45:29 +03:00
38f0335354 Bump version: 0.21.5.7 → 0.21.5.8 2020-10-07 09:22:01 +03:00
889435b94e House renewal bugfix 2020-10-07 09:21:36 +03:00
bb16c27674 Bump version: 0.21.5.6 → 0.21.5.7 2020-10-06 12:35:19 +03:00
963d7ca11a bugfix 2020-10-06 12:35:14 +03:00
36c7fefdf7 Bump version: 0.21.5.5 → 0.21.5.6 2020-09-30 08:40:14 +03:00
d9fa30b06e bugfix in default weapon switch 2020-09-30 08:35:35 +03:00
b53dc447f4 switch to deploy 2020-09-30 08:13:36 +03:00
233d8d83f8 Bump version: 0.21.5.4 → 0.21.5.5 2020-09-29 18:02:52 +03:00
ec62d90aa2 wheeloffortune bugfix 2020-09-29 18:02:52 +03:00
0c433a56da Bump version: 0.21.5.3 → 0.21.5.4 2020-09-29 17:38:44 +03:00
ad24338f4d wheeloffortune bugfix 2020-09-29 17:38:26 +03:00
6f4bc65d1b Bump version: 0.21.5.2 → 0.21.5.3 2020-09-29 17:21:00 +03:00
cc09ba7ee7 wheeloffortune argument bugfix 2020-09-29 17:20:53 +03:00
9e1166a460 Bump version: 0.21.5.1 → 0.21.5.2 2020-09-29 17:17:07 +03:00
fb0042c00d wheeloffortune url bugfix 2020-09-29 17:17:00 +03:00
bb800578e7 Bump version: 0.21.5 → 0.21.5.1 2020-09-29 15:06:39 +03:00
7025f750dc PySocks as requirement 2020-09-29 15:06:30 +03:00
bf77f21b60 MyCompanies export as dict optimised 2020-09-29 15:04:51 +03:00
6b7639d7fb Bump version: 0.21.4.8 → 0.21.5 2020-09-29 10:52:36 +03:00
3b1c1928fd Added proxy support 😉 2020-09-29 10:52:14 +03:00
2e26c2db79 Bump version: 0.21.4.7 → 0.21.4.8 2020-09-25 10:10:33 +03:00
78c055fee2 bugfix 2020-09-25 10:10:27 +03:00
fe41c4cdc6 Bump version: 0.21.4.6 → 0.21.4.7 2020-09-23 13:22:38 +03:00
123b6cf4ed Fight reporting unified and moved to Reporter.report_fighting 2020-09-23 13:22:31 +03:00
f652b02443 Fight reporting duplicate 2020-09-23 13:17:46 +03:00
73537e4742 Bump version: 0.21.4.5 → 0.21.4.6 2020-09-23 11:15:32 +03:00
955432e0d2 Check also for maintenance in BaseCitizen._errors_in_response() 2020-09-23 11:15:18 +03:00
1d93864dca Bump version: 0.21.4.4 → 0.21.4.5 2020-09-22 16:30:03 +03:00
c472d688be error logging 2020-09-22 16:29:50 +03:00
bff9a2bec9 Bump version: 0.21.4.3 → 0.21.4.4 2020-09-21 20:40:40 +03:00
973ea40e00 bugfix 2020-09-21 20:40:24 +03:00
52c85fdf28 Bump version: 0.21.4.2 → 0.21.4.3 2020-09-21 20:18:02 +03:00
a889e9efa1 bugfix 2020-09-21 20:17:56 +03:00
a9a0cdc6d5 Bump version: 0.21.4.1 → 0.21.4.2 2020-09-21 12:39:44 +03:00
1c102488b6 Test fix to not run if WC end is near 2020-09-21 12:39:27 +03:00
c38acef2a0 Bump version: 0.21.4 → 0.21.4.1 2020-09-21 11:34:59 +03:00
48b5e473aa doc generation bugfixes 2020-09-21 11:34:50 +03:00
7fadeb1a49 Bump version: 0.21.3 → 0.21.4 2020-09-21 11:26:51 +03:00
b723660f23 Fixups and type checks 2020-09-21 11:26:32 +03:00
f10eeec498 requirement update 2020-09-21 11:24:01 +03:00
230167f93d mypy bugfix 2020-09-21 11:23:43 +03:00
d5ed989e80 Bump version: 0.21.2.2 → 0.21.3 2020-08-18 16:37:27 +03:00
6fc24b8adf requirements 2020-08-18 16:37:07 +03:00
cf797f2f60 Fixes and updates 2020-08-18 13:14:41 +03:00
ad29045ace Bump version: 0.21.2.1 → 0.21.2.2 2020-07-29 11:40:41 +03:00
c919e46af5 PoliticsAPI extended and bugfixed 2020-07-29 11:40:32 +03:00
644b4d70e1 Bump version: 0.21.2 → 0.21.2.1 2020-07-29 11:23:31 +03:00
6dbbd054ba bugfix 2020-07-29 09:27:02 +03:00
0ee952e504 bugfix 2020-07-29 09:21:49 +03:00
bb9b198a53 Bump version: 0.21.1 → 0.21.2 2020-07-28 19:34:19 +03:00
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
c43e20c8f6 Return all Non-Terrain divisions and their bh damage 2020-07-28 19:33:30 +03:00
c8f41b97af Company cleanup optimisation 2020-07-28 19:25:22 +03:00
d483bcbcb9 JSON.dump sort_keys parameter throwing mysterious errors 2020-07-28 18:29:25 +03:00
a316f277fb Fixed memory leak in Battle and MyCompanies classes 2020-07-28 18:28:03 +03:00
e8c81d17e6 Weapons kind should be singular - 'weapon' 2020-07-19 07:56:15 +03:00
edb4d8851b Bump version: 0.21.0.3 → 0.21.1 2020-07-16 11:15:04 +03:00
76edd6bb7d Update traveling to incorporate travel cooldown (1travel / 15sec) 2020-07-16 11:14:35 +03:00
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
b6b0be7442 Minimize error reporting locals to not duplicate instance.json data 2020-07-16 11:11:35 +03:00
ed434b605d Refactor '.format()' calls to f-strings 2020-07-16 11:08:31 +03:00
2bd311def6 Dev requirement update 2020-07-14 15:37:59 +03:00
4b437c2ba6 Update README.rst 2020-07-14 14:11:20 +03:00
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
0c9f3756ea Add Codacy badge 2020-07-14 11:07:36 +00:00
9de07950b8 Bump version: 0.21.0.2 → 0.21.0.3 2020-07-13 11:24:29 +03:00
766b7b2d95 bugfix 2020-07-12 10:45:44 +03:00
c730981865 Bump version: 0.21.0.1 → 0.21.0.2 2020-07-11 10:21:21 +03:00
d70c3e2c9e Typos 2020-07-11 10:21:12 +03:00
d044af6d2f Bump version: 0.21.0 → 0.21.0.1 2020-07-11 10:08:09 +03:00
dd75b10d2f missing __hash__ 2020-07-11 09:54:23 +03:00
a45dd7e0c3 Bump version: 0.20.4 → 0.21.0 2020-07-11 09:39:25 +03:00
316a826c93 Merge branch 'master' of github.com:eeriks/erepublik 2020-07-11 09:38:20 +03:00
c9710733e2 Typo 2020-07-11 09:37:22 +03:00
9e678d6b51 Bump version: 0.20.3.11 → 0.20.4 2020-07-10 17:13:15 +03:00
88517cb076 Bugfix 2020-07-10 17:09:28 +03:00
01c8aef012 Bugfix: South Africa link 2020-07-10 17:08:58 +03:00
d7ac66bd69 Bump version: 0.20.3.10 → 0.20.3.11 2020-07-10 12:16:53 +03:00
e8739caca1 fix 2020-07-10 12:16:18 +03:00
9df9c1cd87 Bump version: 0.20.3.9 → 0.20.3.10 2020-07-10 00:47:42 +03:00
1b004f163a bugfix 2020-07-10 00:47:36 +03:00
72bd2787d3 Bump version: 0.20.3.8 → 0.20.3.9 2020-07-09 19:11:04 +03:00
bfa6cb1e78 bugfix 2020-07-09 19:10:55 +03:00
1db7c98b47 Bump version: 0.20.3.7 → 0.20.3.8 2020-07-09 18:51:09 +03:00
1f21a71c74 bugfix 2020-07-09 18:51:00 +03:00
0531c8997b Bump version: 0.20.3.6 → 0.20.3.7 2020-07-09 17:42:24 +03:00
5a8a0a3920 mavericks 2020-07-09 17:42:15 +03:00
d938908986 Bump version: 0.20.3.5 → 0.20.3.6 2020-07-09 14:42:51 +03:00
ffbbd25e54 Code cleanup 2020-07-09 14:42:40 +03:00
81bd09e13e Bump version: 0.20.3.4 → 0.20.3.5 2020-07-09 08:53:45 +03:00
fc4295d8bd bugix 2020-07-09 08:53:32 +03:00
7bbf7cb34a Bump version: 0.20.3.3 → 0.20.3.4 2020-07-09 08:46:19 +03:00
8ce56a31f7 bugix 2020-07-09 08:46:12 +03:00
ef23b3b8db Bump version: 0.20.3.2 → 0.20.3.3 2020-07-09 08:34:41 +03:00
cd861ea29b bugfix 2020-07-09 08:34:38 +03:00
5b580f7c79 Bump version: 0.20.3.1 → 0.20.3.2 2020-07-09 08:27:55 +03:00
0061503581 passed int instead of Country to travel 2020-07-09 08:27:48 +03:00
c78dbae925 Merge branch 'master' of github.com:eeriks/erepublik 2020-07-09 08:25:10 +03:00
6a9b574454 class serialization 2020-07-09 08:09:58 +03:00
cde97c6259 Bump version: 0.20.3 → 0.20.3.1 2020-07-08 19:30:47 +03:00
7fa02be7d3 bugfix 2020-07-08 19:30:41 +03:00
098bfe5f3f Bump version: 0.20.2.2 → 0.20.3 2020-07-08 18:28:56 +03:00
c26689a8fa Start hunting empty medals from oldest battles 2020-07-08 18:28:20 +03:00
94c416faa5 Battle, BattleSide, BattleDivision repr and str definitions 2020-07-08 18:27:48 +03:00
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
3927b9ba6b Bump version: 0.20.2.1 → 0.20.2.2 2020-07-06 15:52:42 +03:00
cd5c0b161f Switch to Fight mode if deployed is active 2020-07-06 15:52:37 +03:00
39defc0fd7 Bump version: 0.20.2 → 0.20.2.1 2020-07-04 12:28:09 +03:00
6c8dbcd99e Updated fighting division chooser 2020-07-04 12:28:02 +03:00
d07334f602 Bump version: 0.20.1.7 → 0.20.2 2020-07-04 00:31:33 +03:00
bcb27da54f Summer challange shanged battle list API 2020-07-04 00:31:26 +03:00
7ac22b5e11 Bump version: 0.20.1.6 → 0.20.1.7 2020-07-03 23:56:07 +03:00
5bd3d72a63 Quickfix for summer terrains 2020-07-03 23:54:32 +03:00
33d2c641df Config.json creator HTML 2020-07-03 15:32:13 +03:00
d1e078e443 Bump version: 0.20.1.5 → 0.20.1.6 2020-06-29 14:58:27 +03:00
71c64b0cf5 minor tweaks 2020-06-29 14:57:40 +03:00
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
d077e10f15 Bump version: 0.20.1.4 → 0.20.1.5 2020-06-26 17:27:11 +03:00
8eb5235f12 Deploy bombs in RWs bugfix 2020-06-26 17:27:02 +03:00
a873d223c1 Bump version: 0.20.1.3 → 0.20.1.4 2020-06-25 14:18:41 +03:00
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
b49e4ab594 Bump version: 0.20.1.2 → 0.20.1.3 2020-06-19 13:44:39 +03:00
2f69090c03 bugfix 2020-06-19 13:44:33 +03:00
df170048af Bump version: 0.20.1.1 → 0.20.1.2 2020-06-19 13:37:02 +03:00
8ca845cf17 Add damage amount to inventory bomb 2020-06-19 13:36:45 +03:00
ce7874fbf5 Bump version: 0.20.1 → 0.20.1.1 2020-06-18 10:14:50 +03:00
6abfc98fbd Test requirements 2020-06-18 10:13:55 +03:00
66f0e648df Citizen.to_json() bugfixed and optimised 2020-06-18 10:10:22 +03:00
7cf6cf0e12 Bump version: 0.20.0 → 0.20.1 2020-06-16 17:00:09 +03:00
a825917a98 WAM/Employ bugfix
Company sorting bugfix
2020-06-16 16:59:56 +03:00
603604213d Requirement update 2020-06-15 16:22:53 +03:00
f83df449ae Merge commit '98947e6bbe6feda9f80d630b54c132fa2d5a5949' into v0.20.0
* commit '98947e6bbe6feda9f80d630b54c132fa2d5a5949':
  Update pythonpackage.yml
  Create pythonpackage.yml
2020-06-15 16:03:02 +03:00
b480ed7230 Companies and holdings created as python objects from Dicts 2020-06-15 16:02:36 +03:00
67677f356f eRepublik updated contributions endpoint 2020-06-15 15:59:03 +03:00
ff869e0403 Bomb deploy bugfix 2020-06-15 15:47:48 +03:00
845cd8d174 Bugfix - auto buy raw bought all available items in offer, not the required amount 2020-06-01 11:11:48 +03:00
4e337177f2 Restricted IP check 2020-05-25 12:30:03 +03:00
eed244deb5 Article commenting bugfix 2020-05-19 14:03:41 +03:00
1e7c9a395e Article publishing bugfix 2020-05-19 13:56:27 +03:00
7aa353bc06 minor bugfix 2020-05-15 09:52:49 +03:00
6642839af5 WAM failed when added employee work unit count is less than available 2020-05-15 09:26:10 +03:00
c216d98287 'dict' object has no attribute 'json' 2020-05-15 09:17:56 +03:00
588475d554 NoneType object has no method get 2020-05-14 17:15:11 +03:00
ab3ce2b8c3 tuple indices must be integers or slices, not str 2020-05-14 16:51:55 +03:00
a208c8bcf0 Object of type Response is not JSON serializable 2020-05-14 16:41:18 +03:00
09c6255b84 Tests updated 2020-05-14 15:01:06 +03:00
ff2a0e02dc Example updates 2020-05-14 14:54:25 +03:00
5712007e3f Typos 2020-05-14 14:30:02 +03:00
f64a9dc709 eRepublik has moved citizen contributions to different campaign list endpoint 2020-05-14 14:07:30 +03:00
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
c094ef26b4 Flake8 2020-05-14 12:45:57 +03:00
3cac1cf041 Script will log actions done more verbosely to be more transparent about done actions 2020-05-14 12:29:14 +03:00
ac4fc9baab Less intrusive reporting 2020-05-14 12:11:39 +03:00
b760a2f66c TypeHinting 2020-05-14 11:12:34 +03:00
5c47b70ea6 Wrong offer assignment, resulting in returned data of newest offers not cheapest 2020-05-12 14:19:10 +03:00
05964f6c58 CitizenEconomy.get_market_offers() fixed 2020-05-12 13:45:32 +03:00
d95c472ede Donation rate limit 2020-04-30 15:11:55 +03:00
49726b8cec __all__ list extended, created function for error catching and processing, error processing minor fix 2020-04-29 10:53:38 +03:00
9a0cbf77da Citizen.work_as_manager return types normalized and documented 2020-04-29 10:52:07 +03:00
904fd4efc8 Moved utils.report_promo to classes.Reporter.report_promo 2020-04-23 14:39:42 +03:00
1bbe72f3e1 Fixed imports 2020-04-23 14:32:13 +03:00
2aa1cbd79e PEP8 2020-04-23 14:28:26 +03:00
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
2efc9496a0 Fighting also available in other divisions for Maverick players 2020-04-14 18:56:45 +03:00
1b5b5f736f Multiple achievements are joined under one notification - use achievementAmount for count 2020-03-27 10:31:39 +02:00
4b4ed18cdb Award posting bugfix 2020-03-23 14:38:54 +02:00
d928ffc8df log message formatting 2020-03-05 13:05:37 +02:00
260344bcbe fix 2020-03-05 10:00:22 +02:00
d4a3719645 telegram queue never cleared 2020-03-04 11:34:03 +02:00
98947e6bbe Update pythonpackage.yml 2020-03-03 19:18:33 +02:00
24d81bbadf Create pythonpackage.yml 2020-03-03 19:16:19 +02:00
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
7ec15a9645 New endpoints opened 2020-02-27 18:25:35 +02:00
e0c09672b1 call to super must include email and password 2020-02-27 18:25:04 +02:00
d6a0d5a704 BaseCitizen should have email and password at initialization to be able to login 2020-02-27 18:23:59 +02:00
22dc18d80d Leaderboards are now directly available from Citizen instance 2020-02-27 16:03:58 +02:00
772c09a2af Citizen.eat() and Citizen.eat_eb() were lost after restructuring 2020-02-27 08:23:27 +02:00
bf5f3d74b3 Remove depricated Config.wt property
Add Config.ot for overtime enabling/disabling
2020-02-26 18:43:27 +02:00
70e78023eb Sorted inheritance 2020-02-26 18:42:42 +02:00
8dcebdecd2 no message 2020-02-26 18:42:25 +02:00
77bcfb3df6 Move commit id to __init__ 2020-02-26 18:41:56 +02:00
a01b85154f Bump version: 0.19.4.2 → 0.20.0 2020-02-26 18:29:26 +02:00
04bb0be837 Modularity improvements 2020-02-26 18:19:56 +02:00
62e265e7e1 COMMIT_ID updater 2020-02-26 18:18:47 +02:00
dd2c20cc41 no message 2020-02-26 12:09:45 +02:00
b76ea2c4ae Medal notifications 2020-02-26 12:09:36 +02:00
b7211b7c75 Notification parsing and deleting 2020-02-26 12:09:03 +02:00
07ba1795d3 WIP: Splitting Citizen class into logical pieces for encapsulation, modularity and easier maintenance 2020-02-24 17:20:16 +02:00
ce02a39158 Move eRepublik API helper class to seperate file and split up class into logical pieces 2020-02-24 17:19:06 +02:00
a5dfc07018 Bump version: 0.19.4.1 → 0.19.4.2 2020-02-20 12:43:51 +02:00
e6ce02fc09 requirement update 2020-02-20 12:37:15 +02:00
149071ae86 Bump version: 0.19.4 → 0.19.4.1 2020-02-20 12:32:13 +02:00
72375f40ca Dev versioning 2020-02-20 09:58:03 +02:00
fd1880c50f Some notifications are still being displayed the old way 2020-02-19 18:52:40 +02:00
f73f2b7b9f Bump version: 0.19.3 → 0.19.4 2020-02-18 20:12:02 +02:00
f6433908b4 New notifications API 2020-02-18 20:11:34 +02:00
2fd317153f Bump version: 0.19.2 → 0.19.3 2020-01-27 01:54:03 +02:00
256a180bd6 if error occures on main thread - simplify error logging 2020-01-27 01:53:52 +02:00
c7dbeb2078 Bump version: 0.19.1 → 0.19.2 2020-01-26 20:45:18 +02:00
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
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
75b43fc455 Merge pull request #2 from eeriks/eeriks-patch-1
GHSA-7fcj-pq9j-wh2r
2020-01-17 15:41:02 +02:00
2362dc51e8 GHSA-7fcj-pq9j-wh2r
https://github.com/advisories/GHSA-7fcj-pq9j-wh2r
2020-01-17 15:40:34 +02:00
a2cf479135 Some TelegramBot tweaks 2020-01-16 13:49:24 +02:00
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
0dd1ae9ac5 Too broad exception was cought without notifying about actual error - when Telegram isn't enabled 2020-01-14 13:42:34 +02:00
76bd40c655 Bump version: 0.19.0 → 0.19.1 2020-01-13 21:33:58 +02:00
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
69d0e7df0a Bump version: 0.18.3 → 0.19.0 2020-01-13 10:40:33 +02:00
4f92894ab6 DocUpdate 2020-01-13 10:40:22 +02:00
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
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
71d204843d Python 3.8, isort, requirement update 2020-01-09 12:03:11 +02:00
d9305214eb Representation of Citizen class 2020-01-07 19:55:31 +02:00
5556d5f772 Created method for current products on sale. Updated inventory to also include products on sale 2020-01-07 16:28:42 +02:00
1c47d169d2 Bump version: 0.18.2 → 0.18.3 2020-01-07 11:32:59 +02:00
ef44787bad make clean-pyc removes log/ and debug/ run 'artifacts' 2020-01-07 11:32:48 +02:00
42431134e1 UA update 2020-01-07 11:30:40 +02:00
bedaeeefd1 Battle division update 2020-01-07 11:15:40 +02:00
bbf304aa99 Bump version: 0.18.1 → 0.18.2 2020-01-05 10:53:39 +02:00
a2447959e7 fight must receive battle id as int, added warnings support 2020-01-05 10:53:26 +02:00
700bd8d98e Bump version: 0.18.0 → 0.18.1 2020-01-02 22:43:01 +02:00
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
6ba727a781 promo spam loop 2020-01-02 18:49:38 +02:00
7f1829a5d7 Bump version: 0.17.3 → 0.18.0 2019-12-18 16:26:10 +02:00
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
7edfa3b004 Bump version: 0.17.2 → 0.17.3 2019-12-18 11:45:02 +02:00
12aee23739 Variable and method redeclaration 2019-12-18 11:44:18 +02:00
b7f8182ef5 Bump version: 0.17.1 → 0.17.2 2019-12-13 19:32:29 +02:00
39093accd0 Type hinting. Class parameter defined lists where shared accross instances. 2019-12-13 19:30:43 +02:00
aba8c15fd3 AutoPost medals 2019-12-03 15:22:35 +02:00
f294506a2d Updated wars list, added default weapon choosing (q7 - ground, bare hands - air) 2019-12-03 09:52:53 +02:00
fd56c6c389 By default sort battles by time 2019-12-03 09:44:14 +02:00
4f613ee5ac remvoed unused variables 2019-12-03 09:43:55 +02:00
a7dd528597 Citizen get() and post() signature update, check if server isn't compaining about request flooding 2019-12-03 09:42:51 +02:00
24c755d414 code style 2019-12-03 09:41:07 +02:00
13b639dc5a Bump version: 0.17.0 → 0.17.1 2019-11-21 14:04:49 +02:00
ec1141a46e set serialization 2019-11-21 14:04:43 +02:00
30 changed files with 6062 additions and 2997 deletions

View File

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

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

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

@ -0,0 +1,33 @@
name: Python package
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8]
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 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

5
.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,4 @@ ENV/
debug/
log/
docs/
*dump.json

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

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

View File

@ -1,14 +0,0 @@
# Config file for automatic testing at travis-ci.org
language: python
python:
- 3.7
- 3.6
# 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

@ -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
~~~~~~~~~~~~~~~~~~
@ -59,7 +53,7 @@ Get Started!
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.git
@ -76,14 +70,14 @@ Ready to contribute? Here's how to set up `erepublik` for local development.
Now you can make your changes locally.
5. When you're done making changes, check that your changes pass flake8 and the
tests, including testing other Python versions with tox::
5. When you're done making changes, check that your changes pass flake8, isort and the
tests::
$ flake8 erepublik tests
$ python setup.py test or py.test
$ tox
$ isort erepublik
$ python setup.py test
To get flake8 and tox, just pip install them into your virtualenv.
To get flake8 and isort, just pip install them into your virtualenv.
6. Commit your changes and push your branch to GitHub::
@ -103,7 +97,7 @@ Before you submit a pull request, check that it meets these guidelines:
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 3.7.1. Check
https://travis-ci.org/eeriks/erepublik_script/pull_requests
https://travis-ci.org/eeriks/erepublik/pull_requests
and make sure that the tests pass for all supported Python versions.
Tips
@ -112,7 +106,7 @@ Tips
To run a subset of tests::
$ python -m unittest tests.test_erepublik_script
$ python -m unittest tests.test_erepublik
Deploying
---------

View File

@ -2,6 +2,101 @@
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)
-------------------

View File

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

View File

@ -43,9 +43,10 @@ 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
@ -56,9 +57,6 @@ lint: ## check style with flake8
test: ## run tests quickly with the default Python
python setup.py test
test-all: ## run tests on every Python version with tox
tox
coverage: ## check code coverage quickly with the default Python
coverage run --source erepublik setup.py test
coverage report -m

View File

@ -10,6 +10,10 @@ eRepublik script
:target: https://erepublik.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. image:: https://api.codacy.com/project/badge/Grade/eaa7ae43d23f4c0abab65c3bde89475a
:target: https://app.codacy.com/manual/eeriks/erepublik?utm_source=github.com&utm_medium=referral&utm_content=eeriks/erepublik&utm_campaign=Badge_Grade_Dashboard
:alt: Codacy Badge
Python package for automated eRepublik playing

61
docs/erepublik.rst Normal file
View File

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

508
docs/index.html Normal file
View File

@ -0,0 +1,508 @@
<!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.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<!-- JS, Popper.js, and jQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<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>Fighting</h3>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="fight" checked>
<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" checked>
<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">
<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">
<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">
<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" checked>
<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">
<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" checked>
<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" checked>
<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" checked>
<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">
<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">
<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">
<pre id="json-output"></pre>
</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
}
-->

7
docs/modules.rst Normal file
View File

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

View File

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

View File

@ -4,7 +4,8 @@
__author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv'
__version__ = '0.17.0'
__version__ = '0.25.0.4'
from erepublik import classes, utils
from erepublik.citizen import Citizen
__all__ = ['Citizen', '__version__']

201
erepublik/_logging.py Normal file
View File

@ -0,0 +1,201 @@
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:
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]]]]

762
erepublik/access_points.py Normal file
View File

@ -0,0 +1,762 @@
import datetime
import hashlib
import random
import time
from typing import Any, Dict, List, Mapping, Union
from requests import Response, Session
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)
_uas: List[str] = [
# Chrome
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
# FireFox
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0',
]
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 = random.choice(self._uas)
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)
resp = super().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'))
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)
cookies = dict(sh=hashlib.sha256(','.join(env['l']+env['s']).encode('utf8')).hexdigest(),
ch=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', cookies['sh'], **cookie_kwargs)
self._req.cookies.set('ch', cookies['ch'], **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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

253
erepublik/constants.py Normal file
View File

@ -0,0 +1,253 @@
import datetime
from typing import Dict, Optional, Union
import pytz
__all__ = ['erep_tz', 'min_datetime', 'max_datetime', 'Country', 'AIR_RANKS', 'COUNTRIES', 'FOOD_ENERGY',
'GROUND_RANKS', 'GROUND_RANK_POINTS', 'INDUSTRIES', 'TERRAINS']
erep_tz = pytz.timezone('US/Pacific')
min_datetime = erep_tz.localize(datetime.datetime(2007, 11, 20))
max_datetime = erep_tz.localize(datetime.datetime(2281, 9, 4))
class Country:
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',
17: 'houseRaw', 18: 'houseRaw', 19: 'HRM q2', 20: 'HRM q3', 21: 'HRM q4', 22: 'HRM q5',
24: 'aircraftRaw', 25: 'ARM q2', 26: 'ARM q3', 27: 'ARM q4', 28: 'ARM q5'}
def __getitem__(self, item) -> Optional[Union[int, str]]:
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)
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'}

View File

@ -1,122 +1,70 @@
import datetime
import inspect
import json
import os
import re
import sys
import time
import traceback
import unicodedata
import warnings
from base64 import b64encode
from decimal import Decimal
from logging import Logger
from pathlib import Path
from typing import Union, Any, List, NoReturn, Mapping
from typing import Any, Dict, List, Union
import pytz
import requests
from requests import Response
__all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", 'COUNTRY_LINK',
"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", 'report_promo', 'calculate_hit']
from erepublik import __version__, constants
FOOD_ENERGY = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
COMMIT_ID = "7b92e19"
try:
import simplejson as json
except ImportError:
import json
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*****", }
__all__ = [
'VERSION', 'calculate_hit', 'date_from_eday', 'eday_from_date', 'deprecation', 'get_final_hit_dmg', 'write_file',
'get_air_hit_dmg_value', 'get_file', 'get_ground_hit_dmg_value', 'get_sleep_seconds', 'good_timedelta', 'slugify',
'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now', 'silent_sleep',
'json_decode_object_hook', 'json_load', 'json_loads', 'json_dump', 'json_dumps', 'b64json', 'ErepublikJSONEncoder',
]
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'}
COUNTRY_LINK = {1: 'Romania', 9: 'Brazil', 11: 'France', 12: 'Germany', 13: 'Hungary', 82: 'Cyprus', 168: 'Georgia',
15: 'Spain', 23: 'Canada', 26: 'Mexico', 27: 'Argentina', 28: 'Venezuela', 80: 'Montenegro', 24: 'USA',
29: 'United-Kingdom', 50: 'Australia', 47: 'South-Korea', 171: 'Cuba',
79: 'Republic-of-Macedonia-FYROM',
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', 48: 'India', 49: 'Indonesia', 78: 'Colombia', 68: 'Singapore',
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', 70: 'Estonia', 165: 'Egypt', 14: 'China', 77: 'Peru', 10: 'Italy',
71: 'Latvia', 72: 'Lithuania', 73: 'North-Korea', 74: 'Uruguay', 75: 'Paraguay', 76: 'Bolivia',
81: 'Republic-of-China-Taiwan', 166: 'United-Arab-Emirates', 167: 'Albania', 69: 'Bosnia-Herzegovina',
169: 'Armenia', 83: 'Belarus', 84: 'New-Zealand', 164: 'Saudi-Arabia', 170: 'Nigeria', }
VERSION: str = __version__
def now() -> datetime.datetime:
return datetime.datetime.now(erep_tz).replace(microsecond=0)
return datetime.datetime.now(constants.erep_tz).replace(microsecond=0)
def localize_timestamp(timestamp: int) -> datetime.datetime:
return datetime.datetime.fromtimestamp(timestamp, erep_tz)
return datetime.datetime.fromtimestamp(timestamp, constants.erep_tz)
def localize_dt(dt: Union[datetime.date, datetime.datetime]) -> datetime.datetime:
try:
try:
return erep_tz.localize(dt)
except AttributeError:
return erep_tz.localize(datetime.datetime.combine(dt, datetime.time(0, 0, 0)))
except ValueError:
return dt.astimezone(erep_tz)
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:
return erep_tz.normalize(dt + td)
"""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] = now()) -> int:
def eday_from_date(date: Union[datetime.date, datetime.datetime] = None) -> int:
if date is None:
date = now()
if isinstance(date, datetime.date):
date = datetime.datetime.combine(date, datetime.time(0, 0, 0))
return (date - datetime.datetime(2007, 11, 20, 0, 0, 0)).days
@ -126,9 +74,9 @@ 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:
def get_sleep_seconds(time_until: datetime.datetime) -> int:
""" time_until aware datetime object Wrapper for sleeping until """
sleep_seconds = int((time_untill - now()).total_seconds())
sleep_seconds = int((time_until - now()).total_seconds())
return sleep_seconds if sleep_seconds > 0 else 0
@ -145,7 +93,7 @@ def interactive_sleep(sleep_seconds: int):
# seconds = seconds % 30 if seconds % 30 else 30
else:
seconds = 1
sys.stdout.write("\rSleeping for {:4} more seconds".format(sleep_seconds))
sys.stdout.write(f"\rSleeping for {sleep_seconds:4} more seconds")
sys.stdout.flush()
time.sleep(seconds)
sleep_seconds -= seconds
@ -155,30 +103,11 @@ def interactive_sleep(sleep_seconds: int):
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")
return str(file / 'new_file.txt')
else:
version = 1
try:
@ -201,123 +130,18 @@ def get_file(filepath: str) -> str:
def write_file(filename: str, content: str) -> int:
filename = get_file(filename)
with open(filename, 'ab') as f:
return f.write(content.encode("utf-8"))
def write_request(response: requests.Response, is_error: bool = False):
from erepublik import Citizen
# Remove GET args from url name
url = response.url
last_index = url.index("?") if "?" in url else len(response.url)
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: str, content: List[Any], player=None, local_vars: Mapping[Any, Any] = None,
promo: bool = False, captcha: bool = False):
if local_vars is None:
local_vars = {}
from erepublik 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)
from erepublik.classes import MyJSONEncoder
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)
ret = f.write(content.encode("utf-8"))
return ret
def normalize_html_json(js: str) -> str:
js = re.sub(r' \'(.*?)\'', lambda a: '"%s"' % a.group(1), js)
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 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 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)
trace = inspect.trace()
if trace:
trace = trace[-1][0].f_locals
else:
trace = dict()
send_email(name, bugtrace, citizen, local_vars=trace)
def report_promo(kind: str, time_untill: datetime.datetime) -> NoReturn:
requests.post('https://api.erep.lv/promos/add/', data=dict(kind=kind, time_untill=time_untill))
def slugify(value, allow_unicode=False) -> str:
"""
Function copied from Django2.2.1 django.utils.text.slugify
@ -335,28 +159,20 @@ def slugify(value, allow_unicode=False) -> str:
def calculate_hit(strength: float, rang: int, tp: bool, elite: bool, ne: bool, booster: int = 0,
weapon: int = 200) -> float:
base_dmg = 10 * (1 + strength / 400) * (1 + rang / 5) * (1 + weapon / 100)
dmg = int(base_dmg * 10 + 5) // 10
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
booster_multiplier = (100 + booster) / 100
booster_dmg = dmg * booster_multiplier
dmg = int(booster_dmg * 10 + 5) // 10
elite = 1.1 if elite else 1
elite_dmg = dmg * elite
dmg = int(elite_dmg)
legend = 1 if (not tp or rang < 70) else 1 + (rang - 69) / 10
legend_dmg = dmg * legend
dmg = int(legend_dmg)
return dmg * (1.1 if ne else 1)
dmg = get_final_hit_dmg(dmg, rang, tp=tp, elite=elite, ne=ne, booster=booster)
return Decimal(round(dmg, dec))
def ground_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False,
booster: int = 0, weapon_power: int = 200) -> float:
r = requests.get(f"https://www.erepublik.com/en/main/citizen-profile-json/{citizen_id}").json()
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
@ -366,9 +182,118 @@ def ground_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_patr
return calculate_hit(strength, rang, true_patriot, elite, natural_enemy, booster, weapon_power)
def air_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False, booster: int = 0,
weapon_power: int = 0) -> float:
r = requests.get(f"https://www.erepublik.com/en/main/citizen-profile-json/{citizen_id}").json()
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)

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()

View File

@ -0,0 +1,99 @@
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()

View File

@ -1,14 +1,21 @@
pip==19.1.1
bumpversion==0.5.3
wheel==0.33.4
watchdog==0.9.0
flake8==3.7.8
tox==3.13.2
coverage==4.5.3
Sphinx==2.2.0
twine==2.0.0
ipython
PyInstaller
pytz==2019.1
requests==2.22.0
edx-sphinx-theme
bump2version==1.0.1
coverage==5.5
edx-sphinx-theme==3.0.0
flake8==3.9.2
ipython>=7.25.0
jedi!=0.18.0
isort==5.9.2
pip==21.1.3
pre-commit==2.13.0
pur==5.4.2
PyInstaller==4.3
PySocks==1.7.1
pytest==6.2.4
pytz==2021.1
requests==2.25.1
requests-toolbelt==0.9.1
responses==0.13.3
setuptools==57.1.0
Sphinx==4.0.3
twine==3.4.1
wheel==0.36.2

View File

@ -1,7 +1,10 @@
[bumpversion]
current_version = 0.17.0
current_version = 0.25.0.4
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}'
@ -15,9 +18,22 @@ replace = __version__ = '{new_version}'
universal = 1
[flake8]
exclude = docs
max-line-length = 120
ignore = E722 F401
exclude = docs,.git,log,debug,venv
max-line-length = 240
ignore = D100,D101,D102,D103
[aliases]
[pycodestyle]
max-line-length = 240
exclude = .git,log,debug,venv, build
[mypy]
python_version = 3.8
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 = 240

View File

@ -3,7 +3,7 @@
"""The setup script."""
from setuptools import setup, find_packages
from setuptools import find_packages, setup
with open('README.rst') as readme_file:
readme = readme_file.read()
@ -11,11 +11,19 @@ with open('README.rst') as readme_file:
with open('HISTORY.rst') as history_file:
history = history_file.read()
requirements = ['pytz>=2019.2', 'requests>=2.22']
requirements = [
'PySocks>=1.7.1',
'pytz>=2021.1',
'requests>=2.25.0',
'requests-toolbelt>=0.9.0',
]
setup_requirements = []
test_requirements = []
test_requirements = [
"pytest==6.2.4",
"responses==0.13.3"
]
setup(
author="Eriks Karls",
@ -26,7 +34,8 @@ setup(
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
description="Python package for automated eRepublik playing",
entry_points={},
@ -37,11 +46,11 @@ setup(
keywords='erepublik',
name='eRepublik',
packages=find_packages(include=['erepublik']),
python_requires='>=3.7.*, <4',
python_requires='>=3.8, <4',
setup_requires=setup_requirements,
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/',
version='0.17.0',
version='0.25.0.4',
zip_safe=False,
)

View File

@ -3,11 +3,10 @@
"""Tests for `erepublik` package."""
from erepublik import Citizen
import unittest
from erepublik import Citizen
class TestErepublik(unittest.TestCase):
"""Tests for `erepublik` package."""
@ -66,65 +65,73 @@ class TestErepublik(unittest.TestCase):
self.assertEqual(self.citizen.next_reachable_energy, 0)
def 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)
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
self.assertEqual(self.citizen.should_fight(), 0)
if not is_wc_close:
self.assertEqual(self.citizen.should_fight(), (0, 'Level up', False))
self.citizen.energy.recovered = 3000
self.citizen.energy.recoverable = 2950
self.citizen.energy.interval = 30
self.assertEqual(self.citizen.should_fight(), 900)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 900)
self.citizen.my_companies.ff_lockdown = 0
self.citizen.energy.recovered = 3000
self.citizen.energy.recoverable = 2950
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)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 305)
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)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 75)
self.citizen.my_companies.ff_lockdown = 0
self.citizen.details.pp = 80
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)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 435)
self.citizen.my_companies.ff_lockdown = 0
# 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.recoverable = 1000
self.assertEqual(self.citizen.should_fight(), 400)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 240)
self.citizen.my_companies.ff_lockdown = 0
self.citizen.config.all_in = False
self.citizen.config.air = True
self.citizen.energy.recoverable = 1000
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 = 5000
self.citizen.details.next_pp = [100, 150, 250, 400, 500]
self.assertEqual(self.citizen.should_fight(), 320)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 160)
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.recoverable = 2910
self.assertEqual(self.citizen.should_fight(), 30)
self.citizen.config.next_energy = True
self.citizen.energy.limit = 5000
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.recoverable = 2910
self.assertEqual(self.citizen.should_fight(), (30, 'Fighting for 1h energy. Doing 30 hits', True))

18
tox.ini
View File

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