Compare commits

...

46 Commits

Author SHA1 Message Date
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
77170433c2 Bump version: 0.16.1 → 0.17.0 2019-11-21 11:12:49 +02:00
4736f70203 History update 2019-11-21 11:12:43 +02:00
48a27997ac Bump version: 0.16.0 → 0.16.1 2019-11-21 11:10:15 +02:00
90bec82630 12th anniversary minimal methods 2019-11-21 11:06:29 +02:00
aedfbf4465 12th anniversary endpoints 2019-11-21 10:42:55 +02:00
66f459c692 Inventory structure update 2019-10-30 19:42:05 +02:00
ef27960ff1 no message 2019-10-30 19:35:40 +02:00
c48af9a891 Thread stopping 2019-10-30 18:16:18 +02:00
1abfdb71ac Code cleanup and serialization improvements 2019-10-30 16:55:33 +02:00
e060f67666 More inventory structure updates 2019-10-29 16:06:07 +02:00
06d8d1c0b5 Telegram threading queue has been messing with error reporting 2019-10-29 16:05:22 +02:00
adda8dcb54 Structure requests by year/month/date folders, to keep requests in cleaner format.
The same medal kind (Maverick div BHs) can have different reward value - group them by kind-reward.
Citizen.post bugfix (with no data and json arguments) TODO: Must check where post is called without data or json
2019-10-28 14:17:45 +02:00
c7f084436d Inventory structure update 2019-10-18 18:18:39 +03:00
94a87091a4 Allow full energy reports once every half an hour 2019-10-17 19:15:17 +03:00
c0b97f112d TelegramBot.send_message should always append to send queue 2019-10-16 15:10:38 +03:00
3d895bd085 Damage calculation 2019-10-16 15:09:28 +03:00
d548d1bbf1 Hit calculation can be static 2019-10-15 20:03:38 +03:00
b1eefcc662 CSRF Attack Detecked loop on POST requests. 2019-10-15 20:03:22 +03:00
41798c446c Return successfully transfered item count 2019-10-15 11:28:56 +03:00
074da3adbe Telegram reporter queue bug 2019-10-14 19:21:00 +03:00
6c9a9e920d Delay telegram notification sending by appending multiple messages to queue and after minute of inactivity clear the queue by sending all messages 2019-10-14 13:44:31 +03:00
ffa2fc109c Travel for fighting fixed 2019-10-14 13:14:30 +03:00
f7f4028f32 Revert "Travel for fighting"
This reverts commit 07c8881092.
2019-10-14 13:03:36 +03:00
e91705ce90 no message 2019-10-14 13:03:19 +03:00
ca65a1ffe1 iPython indexer infinite loop and crash 2019-10-14 12:54:59 +03:00
07c8881092 Travel for fighting 2019-10-13 23:50:45 +03:00
25e534f783 Always print should_fight message 2019-10-08 09:32:00 +03:00
daa071f0f5 RTD build image url fixed, setup.py typo 2019-10-08 09:31:09 +03:00
2f8120bd0d Telegram formatting 2019-10-01 09:59:37 +03:00
6b2c073abe Damage booster activisation update and bugfix 2019-10-01 09:59:22 +03:00
c298d66086 Don't allow to fight before WeekChange even if force_fight (levelup, 75pp etc) 2019-10-01 09:58:37 +03:00
bf971972bf History update with version number 2019-09-29 09:47:15 +03:00
9 changed files with 717 additions and 406 deletions

View File

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

View File

@ -2,11 +2,26 @@
History History
======= =======
0.17.0 (2019-11-21)
-------------------
* 12th anniversary's endpoints added
* Telegram message queue optimisation
* WC end fighting energy bugfix
* More strict fighting limiting before week change
* Improved and fixed ground damage booster usage
0.16.0 (2019-09-29)
-------------------
* Telegram notification integration * Telegram notification integration
* Improved serialization to JSON * Improved serialization to JSON
* When failing to do WAM because of not enough food - buy food * When failing to do WAM because of not enough food - buy food
* Buy food buys 48h worth instead of 24h energy * Buy food buys 48h worth instead of 24h energy
0.15.3 (2019-08-24) 0.15.3 (2019-08-24)
------------------- -------------------
@ -25,6 +40,7 @@ History
* Wall post comment endpoints updated with comment create endpoints. * Wall post comment endpoints updated with comment create endpoints.
0.1.0 (2019-07-19) 0.1.0 (2019-07-19)
------------------ ------------------

View File

@ -4,7 +4,7 @@
__author__ = """Eriks Karls""" __author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv' __email__ = 'eriks@72.lv'
__version__ = '0.16.0' __version__ = '0.18.0'
from erepublik import classes, utils from erepublik import classes, utils
from erepublik.citizen import Citizen from erepublik.citizen import Citizen

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,8 @@ import datetime
import decimal import decimal
import hashlib import hashlib
import random import random
import sys import threading
import time import time
import traceback
from collections import deque from collections import deque
from json import JSONDecodeError, loads, JSONEncoder from json import JSONDecodeError, loads, JSONEncoder
from typing import Any, Dict, List, Union, Mapping, Iterable, Tuple from typing import Any, Dict, List, Union, Mapping, Iterable, Tuple
@ -19,7 +18,7 @@ class ErepublikException(Exception):
super().__init__(message) super().__init__(message)
class ErepublikNetworkException(Exception): class ErepublikNetworkException(ErepublikException):
def __init__(self, message, request): def __init__(self, message, request):
super().__init__(message) super().__init__(message)
self.request = request self.request = request
@ -161,13 +160,13 @@ class MyCompanies:
raise ErepublikException("Wrong function call") raise ErepublikException("Wrong function call")
@property # @property
def __dict__(self): # def __dict__(self):
ret = {} # ret = {}
for key in dir(self): # for key in dir(self):
if not key.startswith('_'): # if not key.startswith('_'):
ret[key] = getattr(self, key) # ret[key] = getattr(self, key)
return ret # return ret
class SlowRequests(Session): class SlowRequests(Session):
@ -202,7 +201,7 @@ class SlowRequests(Session):
@property @property
def __dict__(self): def __dict__(self):
return dict(last_time=self.last_time, timeout=self.timeout, user_agent=self.headers['User-Agent'], return dict(last_time=self.last_time, timeout=self.timeout, user_agent=self.headers['User-Agent'],
request_log_name=self.request_log_name) request_log_name=self.request_log_name, debug=self.debug)
def request(self, method, url, *args, **kwargs): def request(self, method, url, *args, **kwargs):
self._slow_down_requests() self._slow_down_requests()
@ -221,7 +220,10 @@ class SlowRequests(Session):
def _log_request(self, url, method, data=None, json=None, params=None, **kwargs): def _log_request(self, url, method, data=None, json=None, params=None, **kwargs):
if self.debug: if self.debug:
args = {} args = {}
args.update({'kwargs': kwargs}) kwargs.pop('allow_redirects', None)
if kwargs:
args.update({'kwargs': kwargs})
if data: if data:
args.update({"data": data}) args.update({"data": data})
@ -245,10 +247,9 @@ class SlowRequests(Session):
self._log_request(hist_resp.request.url, "REDIRECT") self._log_request(hist_resp.request.url, "REDIRECT")
self._log_response(hist_resp.request.url, hist_resp, redirect=True) self._log_response(hist_resp.request.url, hist_resp, redirect=True)
# TODO: Must thoroughly check response writing on windows systems
file_data = { file_data = {
"path": 'debug/requests', "path": 'debug/requests',
"time": self.last_time.strftime('%Y-%m-%d_%H-%M-%S'), "time": self.last_time.strftime('%Y/%m/%d/%H-%M-%S'),
"name": utils.slugify(url[len(Citizen.url):]), "name": utils.slugify(url[len(Citizen.url):]),
"extra": "_REDIRECT" if redirect else "" "extra": "_REDIRECT" if redirect else ""
} }
@ -270,7 +271,7 @@ class Config:
work = True work = True
train = True train = True
wam = False wam = False
auto_sell: List[str] = list() auto_sell: List[str] = None
auto_sell_all = False auto_sell_all = False
employees = False employees = False
fight = False fight = False
@ -294,17 +295,40 @@ class Config:
telegram_chat_id = 0 telegram_chat_id = 0
telegram_token = "" telegram_token = ""
def __init__(self):
self.auto_sell = []
@property @property
def wt(self): def wt(self):
return self.work and self.train return self.work and self.train
@property def reset(self):
def __dict__(self) -> Dict[str, Union[bool, str, List[str]]]: self.work = True
ret = {} self.train = True
for key in dir(self): self.wam = False
if not key.startswith('_') and key not in ['email', 'password']: self.auto_sell = list()
ret[key] = getattr(self, key) self.auto_sell_all = False
return ret self.employees = False
self.fight = False
self.air = False
self.ground = False
self.all_in = False
self.next_energy = False
self.boosters = False
self.travel_to_fight = False
self.always_travel = False
self.epic_hunt = False
self.epic_hunt_ebs = False
self.rw_def_side = False
self.interactive = True
self.continuous_fighting = False
self.auto_buy_raw = False
self.force_wam = False
self.sort_battles_time = True
self.force_travel = False
self.telegram = True
self.telegram_chat_id = 0
self.telegram_token = ""
class Energy: class Energy:
@ -351,22 +375,14 @@ class Energy:
def available(self): def available(self):
return self.recovered + self.recoverable return self.recovered + self.recoverable
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class Details:
class Details(object):
xp = 0 xp = 0
cc = 0 cc = 0
pp = 0 pp = 0
pin = None pin = None
gold = 0 gold = 0
next_pp: List[int] = [] next_pp: List[int] = None
citizen_id = 0 citizen_id = 0
citizenship = 0 citizenship = 0
current_region = 0 current_region = 0
@ -377,6 +393,9 @@ class Details(object):
daily_task_reward = False daily_task_reward = False
mayhem_skills = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, } mayhem_skills = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, }
def __init__(self):
self.next_pp = []
@property @property
def xp_till_level_up(self): def xp_till_level_up(self):
if self.xp >= 10000: if self.xp >= 10000:
@ -403,14 +422,6 @@ class Details(object):
next_level_up = (1 + (self.xp // 10)) * 10 next_level_up = (1 + (self.xp // 10)) * 10
return next_level_up - self.xp return next_level_up - self.xp
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class Politics: class Politics:
is_party_member: bool = False is_party_member: bool = False
@ -421,16 +432,8 @@ class Politics:
is_congressman: bool = False is_congressman: bool = False
is_country_president: bool = False is_country_president: bool = False
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class House:
class House(object):
quality = None quality = None
unactivated_count = 0 unactivated_count = 0
active_untill = utils.good_timedelta(utils.now(), -datetime.timedelta(days=1)) active_untill = utils.good_timedelta(utils.now(), -datetime.timedelta(days=1))
@ -455,8 +458,8 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
""" """
self._req = SlowRequests() self._req = SlowRequests()
def post(self, url: str, *args, **kwargs) -> Response: def post(self, url: str, data=None, json=None, **kwargs) -> Response:
return self._req.post(url, *args, **kwargs) return self._req.post(url, data, json, **kwargs)
def get(self, url: str, **kwargs) -> Response: def get(self, url: str, **kwargs) -> Response:
return self._req.get(url, **kwargs) return self._req.get(url, **kwargs)
@ -467,6 +470,9 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_military_battlefield_choose_side(self, battle: int, side: int) -> Response: def _get_military_battlefield_choose_side(self, battle: int, side: int) -> Response:
return self.get("{}/military/battlefield-choose-side/{}/{}".format(self.url, battle, side)) return self.get("{}/military/battlefield-choose-side/{}/{}".format(self.url, battle, side))
def _get_military_show_weapons(self, battle: int) -> Response:
return self.get("{}/military/show-weapons".format(self.url), params={'_token': self.token, 'battleId': battle})
def _get_candidate_party(self, party_slug: str) -> Response: def _get_candidate_party(self, party_slug: str) -> Response:
return self.post("{}/candidate/{}".format(self.url, party_slug)) return self.post("{}/candidate/{}".format(self.url, party_slug))
@ -532,6 +538,9 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_military_campaigns(self) -> Response: def _get_military_campaigns(self) -> Response:
return self.get("{}/military/campaigns-new/".format(self.url)) return self.get("{}/military/campaigns-new/".format(self.url))
def _get_military_campaigns_json_list(self) -> Response:
return self.get("{}/military/campaignsJson/list".format(self.url))
def _get_military_show_weapons(self, battle_id: int) -> Response: def _get_military_show_weapons(self, battle_id: int) -> Response:
params = {"_token": self.token, "battleId": battle_id} params = {"_token": self.token, "battleId": battle_id}
return self.get("{}/military/show-weapons".format(self.url), params=params) return self.get("{}/military/show-weapons".format(self.url), params=params)
@ -590,6 +599,10 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(_token=self.token, sideCountryId=side_id, battleId=battle_id) data = dict(_token=self.token, sideCountryId=side_id, battleId=battle_id)
return self.post("{}/main/battlefieldTravel".format(self.url), data=data) return self.post("{}/main/battlefieldTravel".format(self.url), data=data)
def _post_main_battlefield_change_division(self, battle_id: int, division_id: int) -> Response:
data = dict(_token=self.token, battleZoneId=division_id, battleId=battle_id)
return self.post("{}/main/battlefieldTravel".format(self.url), data=data)
def _post_main_buy_gold_items(self, currency: str, item: str, amount: int) -> Response: 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) data = dict(itemId=item, currency=currency, amount=amount, _token=self.token)
return self.post("{}/main/buyGoldItems".format(self.url), data=data) return self.post("{}/main/buyGoldItems".format(self.url), data=data)
@ -760,6 +773,10 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(type=kind, quality=quality, duration=duration, battleId=battle, _token=self.token) data = dict(type=kind, quality=quality, duration=duration, battleId=battle, _token=self.token)
return self.post("{}/military/fight-activateBooster".format(self.url), data=data) return self.post("{}/military/fight-activateBooster".format(self.url), data=data)
def _post_military_change_weapon(self, battle: int, battle_zone: int, weapon_level: int,) -> Response:
data = dict(battleId=battle, _token=self.token, battleZoneId=battle_zone, customizationLevel=weapon_level)
return self.post("{}/military/change-weapon".format(self.url), data=data)
def _post_login(self, email: str, password: str) -> Response: def _post_login(self, email: str, password: str) -> Response:
data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember='on') data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember='on')
return self.post("{}/login".format(self.url), data=data) return self.post("{}/login".format(self.url), data=data)
@ -783,21 +800,16 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data.update(page=page) data.update(page=page)
return self.post("{}/military/battle-console".format(self.url), data=data) return self.post("{}/military/battle-console".format(self.url), data=data)
def _post_military_change_weapon(self, battle_id: int, battle_zone_id: int, customization_level: int) -> Response:
data = dict(_token=self.token, battleZoneId=battle_zone_id, battleId=battle_id,
customizationLevel=customization_level)
return self.post("{}/military/change-weapon".format(self.url), data=data)
def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response: def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response:
data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token) data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token)
return self.post("{}/military/deploy-bomb".format(self.url), data=data) return self.post("{}/military/deploy-bomb".format(self.url), data=data)
def _post_military_fight_air(self, battle_id: int, side_id: int) -> Response: 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) data = dict(sideId=side_id, battleId=battle_id, _token=self.token, battleZoneId=zone_id)
return self.post("{}/military/fight-shoooot/{}".format(self.url, battle_id), data=data) return self.post("{}/military/fight-shoooot/{}".format(self.url, battle_id), data=data)
def _post_military_fight_ground(self, battle_id: int, side_id: int) -> Response: 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) data = dict(sideId=side_id, battleId=battle_id, _token=self.token, battleZoneId=zone_id)
return self.post("{}/military/fight-shooot/{}".format(self.url, battle_id), data=data) return self.post("{}/military/fight-shooot/{}".format(self.url, battle_id), data=data)
def _post_military_group_missions(self) -> Response: def _post_military_group_missions(self) -> Response:
@ -897,29 +909,61 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = {"_token": self.token, "post_message": body} data = {"_token": self.token, "post_message": body}
return self.post("{}/main/wall-post/create/json".format(self.url), data=data) return self.post("{}/main/wall-post/create/json".format(self.url), data=data)
def _post_main_wall_post_automatic(self, **kwargs) -> Response:
kwargs.update(_token=self.token)
return self.post("{}/main/wall-post/create/json".format(self.url), data=kwargs)
def _post_main_wall_post_retrieve(self) -> Response: def _post_main_wall_post_retrieve(self) -> Response:
data = {"_token": self.token, "page": 1, "switchedFrom": False} data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post("{}/main/wall-post/retrieve/json".format(self.url), data=data) return self.post("{}/main/wall-post/retrieve/json".format(self.url), data=data)
# 12th anniversary endpoints
def _get_anniversary_quest_data(self) -> Response:
return self.get("{}/main/anniversaryQuestData".format(self.url))
def _post_map_rewards_unlock(self, node_id: int) -> Response:
data = {'nodeId': node_id, '_token': self.token}
return self.post("{}/main/map-rewards-unlock".format(self.url), 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("{}/main/map-rewards-speedup".format(self.url), data=data)
def _post_map_rewards_claim(self, node_id: int) -> Response:
data = {'nodeId': node_id, '_token': self.token}
return self.post("{}/main/map-rewards-claim".format(self.url), 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=utils.COUNTRY_LINK[attack_country_id])
return self.post("{}/{}/new-war".format(self.url, utils.COUNTRY_LINK[self_country_id]), 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("{}/{}/new-donation".format(self.url, utils.COUNTRY_LINK[country_id]), data=data)
class Reporter: class Reporter:
__to_update: List[Dict[Any, Any]] = [] __to_update: List[Dict[Any, Any]] = None
name: str = "" name: str = ""
email: str = "" email: str = ""
citizen_id: int = 0 citizen_id: int = 0
key: str = "" key: str = ""
allowed: bool = False allowed: bool = False
@property
def __dict__(self):
return dict(name=self.name, email=self.email, citizen_id=self.citizen_id, key=self.key, allowed=self.allowed,
queue=self.__to_update)
def __init__(self): def __init__(self):
self._req = Session() self._req = Session()
self.url = "https://api.erep.lv" self.url = "https://api.erep.lv"
self._req.headers.update({"user-agent": "Bot reporter v2"}) self._req.headers.update({"user-agent": "Bot reporter v2"})
self.__to_update = []
self.__registered: bool = False self.__registered: bool = False
@property
def __dict__(self):
return dict(allowed=self.allowed, __to_update=self.__to_update)
def do_init(self, name: str = "", email: str = "", citizen_id: int = 0): def do_init(self, name: str = "", email: str = "", citizen_id: int = 0):
self.name: str = name self.name: str = name
self.email: str = email self.email: str = email
@ -996,7 +1040,7 @@ class MyJSONEncoder(JSONEncoder):
return dict(headers=o.headers.__dict__, url=o.url, text=o.text) return dict(headers=o.headers.__dict__, url=o.url, text=o.text)
elif hasattr(o, '__dict__'): elif hasattr(o, '__dict__'):
return o.__dict__ return o.__dict__
elif isinstance(o, deque): elif isinstance(o, (deque, set)):
return list(o) return list(o)
elif isinstance(o, Citizen): elif isinstance(o, Citizen):
return o.to_json() return o.to_json()
@ -1015,71 +1059,84 @@ class BattleSide:
self.allies = [int(ally) for ally in allies] self.allies = [int(ally) for ally in allies]
self.deployed = [int(ally) for ally in deployed] self.deployed = [int(ally) for ally in deployed]
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class BattleDivision: class BattleDivision:
end: datetime.datetime end: datetime.datetime
epic: bool epic: bool
dom_pts: Dict[str, int] = None dom_pts: Dict[str, int]
wall: Dict[str, Union[int, float]] = None wall: Dict[str, Union[int, float]]
battle_zone_id: int
def_medal: Dict[str, int]
inv_medal: Dict[str, int]
@property @property
def div_end(self) -> bool: def div_end(self) -> bool:
return utils.now() >= self.end return utils.now() >= self.end
def __init__(self, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int, wall_for: int, wall_dom: float): def __init__(
self, div_id: int, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int,
wall_for: int, wall_dom: float, def_medal: Tuple[int, int], inv_medal: Tuple[int, int]
):
self.battle_zone_id = div_id
self.end = end self.end = end
self.epic = epic self.epic = epic
self.dom_pts = dict({"inv": inv_pts, "def": def_pts}) self.dom_pts = dict({"inv": inv_pts, "def": def_pts})
self.wall = dict({"for": wall_for, "dom": wall_dom}) self.wall = dict({"for": wall_for, "dom": wall_dom})
self.def_medal = {"id": def_medal[0], "dmg": def_medal[1]}
@property self.inv_medal = {"id": inv_medal[0], "dmg": inv_medal[1]}
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class Battle(object): class Battle:
id: int = 0 id: int
war_id: int = 0 war_id: int
zone_id: int = 0 zone_id: int
is_rw: bool = False is_rw: bool
is_dict_lib: bool = False is_dict_lib: bool
start: datetime.datetime = None start: datetime.datetime
invader: BattleSide = None invader: BattleSide
defender: BattleSide = None defender: BattleSide
div: Dict[int, BattleDivision] = None div: Dict[int, BattleDivision]
@property @property
def is_air(self) -> bool: def is_air(self) -> bool:
return not bool(self.zone_id % 4) return not bool(self.zone_id % 4)
def __init__(self, battle: Dict[str, Any]): def __init__(self, battle: Dict[str, Any] = None):
self.id = int(battle.get('id', 0)) """Object representing eRepublik battle.
self.war_id = int(battle.get('war_id', 0))
self.zone_id = int(battle.get('zone_id', 0))
self.is_rw = bool(battle.get('is_rw'))
self.is_as = bool(battle.get('is_as'))
self.is_dict_lib = bool(battle.get('is_dict')) or bool(battle.get('is_lib'))
self.start = datetime.datetime.fromtimestamp(int(battle.get('start', 0)), tz=utils.erep_tz)
self.invader = BattleSide(battle.get('inv', {}).get('id'), battle.get('inv', {}).get('points'), :param battle: Dict object for single battle from '/military/campaignsJson/list' response's 'battles' object
[row.get('id') for row in battle.get('inv', {}).get('ally_list')], """
[row.get('id') for row in battle.get('inv', {}).get('ally_list') if row['deployed']]) if battle is None:
battle = {}
self.id = 0
self.war_id = 0
self.zone_id = 0
self.is_rw = False
self.is_as = False
self.is_dict_lib = False
self.start = utils.now().min
self.invader = BattleSide(0, 0, [], [])
self.defender = BattleSide(0, 0, [], [])
else:
self.id = int(battle.get('id', 0))
self.war_id = int(battle.get('war_id', 0))
self.zone_id = int(battle.get('zone_id', 0))
self.is_rw = bool(battle.get('is_rw'))
self.is_as = bool(battle.get('is_as'))
self.is_dict_lib = bool(battle.get('is_dict')) or bool(battle.get('is_lib'))
self.start = datetime.datetime.fromtimestamp(int(battle.get('start', 0)), tz=utils.erep_tz)
self.defender = BattleSide(battle.get('def', {}).get('id'), battle.get('def', {}).get('points'), self.invader = BattleSide(
[row.get('id') for row in battle.get('def', {}).get('ally_list')], battle.get('inv', {}).get('id'), battle.get('inv', {}).get('points'),
[row.get('id') for row in battle.get('def', {}).get('ally_list') if row['deployed']]) [row.get('id') for row in battle.get('inv', {}).get('ally_list')],
[row.get('id') for row in battle.get('inv', {}).get('ally_list') if row['deployed']]
)
self.defender = BattleSide(
battle.get('def', {}).get('id'), battle.get('def', {}).get('points'),
[row.get('id') for row in battle.get('def', {}).get('ally_list')],
[row.get('id') for row in battle.get('def', {}).get('ally_list') if row['deployed']]
)
self.div = {} self.div = {}
for div, data in battle.get('div', {}).items(): for div, data in battle.get('div', {}).items():
@ -1089,11 +1146,18 @@ class Battle(object):
else: else:
end = utils.localize_dt(datetime.datetime.max - datetime.timedelta(days=1)) end = utils.localize_dt(datetime.datetime.max - datetime.timedelta(days=1))
battle_div = BattleDivision( if not data['stats']['def']:
end=end, epic=data.get('epic_type') in [1, 5], def_medal = (0, 0)
inv_pts=data.get('dom_pts').get("inv"), def_pts=data.get('dom_pts').get("def"), else:
wall_for=data.get('wall').get("for"), wall_dom=data.get('wall').get("dom") def_medal = (data['stats']['def']['citizenId'], data['stats']['def']['damage'])
) if not data['stats']['inv']:
inv_medal = (0, 0)
else:
inv_medal = (data['stats']['inv']['citizenId'], data['stats']['inv']['damage'])
battle_div = BattleDivision(end=end, epic=data.get('epic_type') in [1, 5], div_id=data.get('id'),
inv_pts=data.get('dom_pts').get("inv"), def_pts=data.get('dom_pts').get("def"),
wall_for=data.get('wall').get("for"), wall_dom=data.get('wall').get("dom"),
def_medal=def_medal, inv_medal=inv_medal)
self.div.update({div: battle_div}) self.div.update({div: battle_div})
@ -1104,17 +1168,10 @@ class Battle(object):
time_part = "{}".format(now - self.start) time_part = "{}".format(now - self.start)
else: else:
time_part = "- {}".format(self.start - now) time_part = "- {}".format(self.start - now)
return "Battle {} | {:>21.21}:{:<21.21} | Round {:2} | Start {}".format( return f"Battle {self.id} | " \
self.id, utils.COUNTRIES[self.invader.id], utils.COUNTRIES[self.defender.id], self.zone_id, time_part f"{utils.COUNTRIES[self.invader.id]:>21.21}:{utils.COUNTRIES[self.defender.id]:<21.21} | " \
) f"Round {self.zone_id:2} | " \
f"Time since start {time_part}"
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class EnergyToFight: class EnergyToFight:
@ -1150,33 +1207,49 @@ class EnergyToFight:
class TelegramBot: class TelegramBot:
__initialized = False __initialized = False
__queue: List[str] = [] __queue: List[str]
chat_id = 0 chat_id = 0
api_url = "" api_url = ""
player_name = "" player_name = ""
__last_time: datetime.datetime = None __thread_stopper: threading.Event
_last_time: datetime.datetime
_last_full_energy_report: datetime.datetime
_next_time: datetime.datetime
_threads: List[threading.Thread]
def __init__(self, stop_event: threading.Event = None):
self._threads = []
self.__queue = []
self.__thread_stopper = threading.Event() if stop_event is None else stop_event
def __dict__(self):
return dict(chat_id=self.chat_id, api_url=self.api_url, player=self.player_name, last_time=self._last_time,
next_time=self._next_time, queue=self.__queue, initialized=self.__initialized,
has_threads=bool(len(self._threads)))
def do_init(self, chat_id: int, token: str, player_name: str = ""): def do_init(self, chat_id: int, token: str, player_name: str = ""):
self.chat_id = chat_id self.chat_id = chat_id
self.api_url = "https://api.telegram.org/bot{}/sendMessage".format(token) self.api_url = "https://api.telegram.org/bot{}/sendMessage".format(token)
self.player_name = player_name self.player_name = player_name
self.__initialized = True self.__initialized = True
self.__last_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-5)) self._last_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-5))
self._last_full_energy_report = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-30))
if self.__queue: if self.__queue:
self.send_message("\n\n\n\n".join(self.__queue)) self.send_message("\n\n\n\n".join(self.__queue))
def send_message(self, message: str) -> bool: def send_message(self, message: str) -> bool:
self.__queue.append(message)
if not self.__initialized: if not self.__initialized:
self.__queue.append(message)
return True return True
if self.player_name: self._threads = [t for t in self._threads if t.is_alive()]
message = f"Player *{self.player_name}*\n" + message self._next_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=1))
if utils.good_timedelta(utils.now(), datetime.timedelta(seconds=-1)) <= self.__last_time: if not self._threads:
tb = traceback.extract_stack() name = "telegram_{}send".format(f"{self.player_name}_" if self.player_name else "")
message += "\n\n```\n{}\n```".format("\n".join([' File "{}", line {}, in {}\n'.format(l.filename, l.lineno, l.name) for l in tb])) send_thread = threading.Thread(target=self.__send_messages, name=name)
response = post(self.api_url, json=dict(chat_id=self.chat_id, text=message, parse_mode="Markdown")) send_thread.start()
self.__last_time = utils.now() self._threads.append(send_thread)
return response.json().get('ok')
return True
def report_free_bhs(self, battles: List[Tuple[int, int, int, int, datetime.timedelta]]): def report_free_bhs(self, battles: List[Tuple[int, int, int, int, datetime.timedelta]]):
battle_links = [] battle_links = []
@ -1195,8 +1268,26 @@ class TelegramBot:
self.send_message("Free BHs:\n" + "\n".join(battle_links)) self.send_message("Free BHs:\n" + "\n".join(battle_links))
def report_full_energy(self, available: int, limit: int, interval: int): def report_full_energy(self, available: int, limit: int, interval: int):
message = f"Full energy ({available}hp/{limit}hp +{interval}hp/6min)" if (utils.now() - self._last_full_energy_report).total_seconds() >= 30 * 60:
self.send_message(message) self._last_full_energy_report = utils.now()
message = f"Full energy ({available}hp/{limit}hp +{interval}hp/6min)"
self.send_message(message)
def report_medal(self, msg): def report_medal(self, msg):
self.send_message(f"New award: *{msg}*") self.send_message(f"New award: *{msg}*")
def __send_messages(self):
while self._next_time > utils.now():
if self.__thread_stopper.is_set():
break
self.__thread_stopper.wait(utils.get_sleep_seconds(self._next_time))
message = "\n\n\n\n".join(self.__queue)
if self.player_name:
message = f"Player *{self.player_name}*\n" + message
response = post(self.api_url, json=dict(chat_id=self.chat_id, text=message, parse_mode="Markdown"))
self._last_time = utils.now()
if response.json().get('ok'):
self.__queue = []
return True
return False

View File

@ -13,11 +13,11 @@ from typing import Union, Any, List, NoReturn, Mapping
import pytz import pytz
import requests import requests
__all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", __all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", 'COUNTRY_LINK',
"now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday", "now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday",
"get_sleep_seconds", "interactive_sleep", "silent_sleep", "get_sleep_seconds", "interactive_sleep", "silent_sleep",
"write_silent_log", "write_interactive_log", "get_file", "write_file", "write_silent_log", "write_interactive_log", "get_file", "write_file",
"send_email", "normalize_html_json", "process_error", ] "send_email", "normalize_html_json", "process_error", 'report_promo', 'calculate_hit']
FOOD_ENERGY = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20) FOOD_ENERGY = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
COMMIT_ID = "7b92e19" COMMIT_ID = "7b92e19"
@ -332,3 +332,43 @@ def slugify(value, allow_unicode=False) -> str:
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii') value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
value = re.sub(r'[^\w\s-]', '_', value).strip().lower() value = re.sub(r'[^\w\s-]', '_', value).strip().lower()
return re.sub(r'[-\s]+', '-', value) return re.sub(r'[-\s]+', '-', value)
def calculate_hit(strength: float, rang: int, tp: bool, elite: bool, ne: bool, booster: int = 0,
weapon: int = 200) -> float:
base_dmg = 10 * (1 + strength / 400) * (1 + rang / 5) * (1 + weapon / 100)
dmg = int(base_dmg * 10 + 5) // 10
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)
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()
rang = r['military']['militaryData']['ground']['rankNumber']
strength = r['military']['militaryData']['ground']['strength']
elite = r['citizenAttributes']['level'] > 100
if natural_enemy:
true_patriot = True
return calculate_hit(strength, rang, true_patriot, elite, natural_enemy, booster, weapon_power)
def 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()
rang = r['military']['militaryData']['aircraft']['rankNumber']
elite = r['citizenAttributes']['level'] > 100
return calculate_hit(0, rang, true_patriot, elite, natural_enemy, booster, weapon_power)

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.16.0 current_version = 0.18.0
commit = True commit = True
tag = True tag = True

View File

@ -13,9 +13,9 @@ with open('HISTORY.rst') as history_file:
requirements = ['pytz>=2019.2', 'requests>=2.22'] requirements = ['pytz>=2019.2', 'requests>=2.22']
setup_requirements = [ ] setup_requirements = []
test_requirements = [ ] test_requirements = []
setup( setup(
author="Eriks Karls", author="Eriks Karls",
@ -42,6 +42,6 @@ setup(
test_suite='tests', test_suite='tests',
tests_require=test_requirements, tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/', url='https://github.com/eeriks/erepublik/',
version='0.16.0', version='0.18.0',
zip_safe=False, zip_safe=False,
) )

View File

@ -79,9 +79,9 @@ class TestErepublik(unittest.TestCase):
self.citizen.energy.recovered = 3000 self.citizen.energy.recovered = 3000
self.citizen.energy.recoverable = 2950 self.citizen.energy.recoverable = 2950
self.citizen.energy.interval = 30 self.citizen.energy.interval = 30
self.assertEqual(self.citizen.should_fight(), 895) self.assertEqual(self.citizen.should_fight(), 900)
self.citizen.my_companies.ff_lockdown = 160 self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 895) self.assertEqual(self.citizen.should_fight(), 900)
self.citizen.my_companies.ff_lockdown = 0 self.citizen.my_companies.ff_lockdown = 0
# Level up reachable # Level up reachable