Compare commits

...

32 Commits

Author SHA1 Message Date
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
8 changed files with 449 additions and 309 deletions

View File

@ -2,11 +2,26 @@
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
* Improved serialization to JSON
* When failing to do WAM because of not enough food - buy food
* Buy food buys 48h worth instead of 24h energy
0.15.3 (2019-08-24)
-------------------
@ -25,6 +40,7 @@ History
* Wall post comment endpoints updated with comment create endpoints.
0.1.0 (2019-07-19)
------------------

View File

@ -4,7 +4,7 @@
__author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv'
__version__ = '0.16.0'
__version__ = '0.17.0'
from erepublik import classes, utils
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 hashlib
import random
import sys
import threading
import time
import traceback
from collections import deque
from json import JSONDecodeError, loads, JSONEncoder
from typing import Any, Dict, List, Union, Mapping, Iterable, Tuple
@ -19,7 +18,7 @@ class ErepublikException(Exception):
super().__init__(message)
class ErepublikNetworkException(Exception):
class ErepublikNetworkException(ErepublikException):
def __init__(self, message, request):
super().__init__(message)
self.request = request
@ -161,13 +160,13 @@ class MyCompanies:
raise ErepublikException("Wrong function call")
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
# @property
# def __dict__(self):
# ret = {}
# for key in dir(self):
# if not key.startswith('_'):
# ret[key] = getattr(self, key)
# return ret
class SlowRequests(Session):
@ -202,7 +201,7 @@ class SlowRequests(Session):
@property
def __dict__(self):
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):
self._slow_down_requests()
@ -221,7 +220,10 @@ class SlowRequests(Session):
def _log_request(self, url, method, data=None, json=None, params=None, **kwargs):
if self.debug:
args = {}
args.update({'kwargs': kwargs})
kwargs.pop('allow_redirects', None)
if kwargs:
args.update({'kwargs': kwargs})
if data:
args.update({"data": data})
@ -245,10 +247,9 @@ class SlowRequests(Session):
self._log_request(hist_resp.request.url, "REDIRECT")
self._log_response(hist_resp.request.url, hist_resp, redirect=True)
# TODO: Must thoroughly check response writing on windows systems
file_data = {
"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):]),
"extra": "_REDIRECT" if redirect else ""
}
@ -298,13 +299,33 @@ class Config:
def wt(self):
return self.work and self.train
@property
def __dict__(self) -> Dict[str, Union[bool, str, List[str]]]:
ret = {}
for key in dir(self):
if not key.startswith('_') and key not in ['email', 'password']:
ret[key] = getattr(self, key)
return ret
def reset(self):
self.work = True
self.train = True
self.wam = False
self.auto_sell = list()
self.auto_sell_all = False
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:
@ -351,16 +372,8 @@ class Energy:
def available(self):
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(object):
class Details:
xp = 0
cc = 0
pp = 0
@ -403,14 +416,6 @@ class Details(object):
next_level_up = (1 + (self.xp // 10)) * 10
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:
is_party_member: bool = False
@ -421,16 +426,8 @@ class Politics:
is_congressman: 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(object):
class House:
quality = None
unactivated_count = 0
active_untill = utils.good_timedelta(utils.now(), -datetime.timedelta(days=1))
@ -901,6 +898,22 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = {"_token": self.token, "page": 1, "switchedFrom": False}
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)
class Reporter:
__to_update: List[Dict[Any, Any]] = []
@ -910,16 +923,17 @@ class Reporter:
key: str = ""
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):
self._req = Session()
self.url = "https://api.erep.lv"
self._req.headers.update({"user-agent": "Bot reporter v2"})
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):
self.name: str = name
self.email: str = email
@ -1015,14 +1029,6 @@ class BattleSide:
self.allies = [int(ally) for ally in allies]
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:
end: datetime.datetime
@ -1040,16 +1046,8 @@ class BattleDivision:
self.dom_pts = dict({"inv": inv_pts, "def": def_pts})
self.wall = dict({"for": wall_for, "dom": wall_dom})
@property
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
war_id: int = 0
zone_id: int = 0
@ -1104,17 +1102,10 @@ class Battle(object):
time_part = "{}".format(now - self.start)
else:
time_part = "- {}".format(self.start - now)
return "Battle {} | {:>21.21}:{:<21.21} | Round {:2} | Start {}".format(
self.id, utils.COUNTRIES[self.invader.id], utils.COUNTRIES[self.defender.id], self.zone_id, time_part
)
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
return f"Battle {self.id} | " \
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}"
class EnergyToFight:
@ -1154,29 +1145,43 @@ class TelegramBot:
chat_id = 0
api_url = ""
player_name = ""
__last_time: datetime.datetime = None
__thread_stopper: threading.Event = None
_last_time: datetime.datetime = None
_last_full_energy_report: datetime.datetime = None
_next_time: datetime.datetime = None
_threads: List[threading.Thread] = []
def __init__(self, stop_event: threading.Event = None):
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 = ""):
self.chat_id = chat_id
self.api_url = "https://api.telegram.org/bot{}/sendMessage".format(token)
self.player_name = player_name
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:
self.send_message("\n\n\n\n".join(self.__queue))
def send_message(self, message: str) -> bool:
self.__queue.append(message)
if not self.__initialized:
self.__queue.append(message)
return True
if self.player_name:
message = f"Player *{self.player_name}*\n" + message
if utils.good_timedelta(utils.now(), datetime.timedelta(seconds=-1)) <= self.__last_time:
tb = traceback.extract_stack()
message += "\n\n```\n{}\n```".format("\n".join([' File "{}", line {}, in {}\n'.format(l.filename, l.lineno, l.name) for l in tb]))
response = post(self.api_url, json=dict(chat_id=self.chat_id, text=message, parse_mode="Markdown"))
self.__last_time = utils.now()
return response.json().get('ok')
self._threads = [t for t in self._threads if t.is_alive()]
self._next_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=1))
if not self._threads:
name = "telegram_{}send".format(f"{self.player_name}_" if self.player_name else "")
send_thread = threading.Thread(target=self.__send_messages, name=name)
send_thread.start()
self._threads.append(send_thread)
return True
def report_free_bhs(self, battles: List[Tuple[int, int, int, int, datetime.timedelta]]):
battle_links = []
@ -1195,8 +1200,26 @@ class TelegramBot:
self.send_message("Free BHs:\n" + "\n".join(battle_links))
def report_full_energy(self, available: int, limit: int, interval: int):
message = f"Full energy ({available}hp/{limit}hp +{interval}hp/6min)"
self.send_message(message)
if (utils.now() - self._last_full_energy_report).total_seconds() >= 30 * 60:
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):
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 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",
"get_sleep_seconds", "interactive_sleep", "silent_sleep",
"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)
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 = re.sub(r'[^\w\s-]', '_', value).strip().lower()
return re.sub(r'[-\s]+', '-', value)
def calculate_hit(strength: float, rang: int, tp: bool, elite: bool, ne: bool, booster: int = 0,
weapon: int = 200) -> 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]
current_version = 0.16.0
current_version = 0.17.0
commit = True
tag = True

View File

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

View File

@ -79,9 +79,9 @@ class TestErepublik(unittest.TestCase):
self.citizen.energy.recovered = 3000
self.citizen.energy.recoverable = 2950
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.assertEqual(self.citizen.should_fight(), 895)
self.assertEqual(self.citizen.should_fight(), 900)
self.citizen.my_companies.ff_lockdown = 0
# Level up reachable