Compare commits

..

10 Commits

Author SHA1 Message Date
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
7 changed files with 124 additions and 97 deletions

View File

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

View File

@ -1,22 +1,22 @@
import json
import re
import sys
from collections import defaultdict
from datetime import datetime, timedelta
from itertools import product
from json import dumps, loads
from threading import Event
from time import sleep
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from requests import RequestException, Response
from erepublik.classes import (Battle, BattleDivision, CitizenAPI, Config,
Details, Energy, ErepublikException,
MyCompanies, MyJSONEncoder, Politics, Reporter,
TelegramBot)
from erepublik.classes import (Battle, BattleDivision, CitizenAPI, Config, Details, Energy, ErepublikException,
MyCompanies, MyJSONEncoder, Politics, Reporter, TelegramBot)
from erepublik.utils import *
try:
import simplejson as json
except ImportError:
import json
class Citizen(CitizenAPI):
division = 0
@ -116,6 +116,7 @@ class Citizen(CitizenAPI):
def __repr__(self):
return self.__str__()
@property
def __dict__(self):
ret = super().__dict__.copy()
ret.pop('stop_threads', None)
@ -155,7 +156,7 @@ class Citizen(CitizenAPI):
raise ErepublikException("Something went wrong! Can't find token in page! Exiting!")
try:
self.update_citizen_info(resp.text)
except:
except (AttributeError, json.JSONDecodeError, ValueError, KeyError):
pass
def _login(self):
@ -211,8 +212,8 @@ class Citizen(CitizenAPI):
return self.get(url, **kwargs)
try:
self.update_citizen_info(html=response.text)
except:
self.update_citizen_info(response.text)
except (AttributeError, json.JSONDecodeError, ValueError, KeyError):
pass
if self._errors_in_response(response):
@ -332,7 +333,7 @@ class Citizen(CitizenAPI):
self._get_main()
return
ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1)
promos = loads(normalize_html_json(ugly_js))
promos = json.loads(normalize_html_json(ugly_js))
if self.promos is None:
self.promos = {}
else:
@ -367,7 +368,7 @@ class Citizen(CitizenAPI):
)
ugly_js = re.search(r"var erepublik = ({.*}),\s+", html).group(1)
citizen_js = loads(ugly_js)
citizen_js = json.loads(ugly_js)
citizen = citizen_js.get("citizen", {})
self.eday = citizen_js.get("settings").get("eDay")
@ -423,14 +424,14 @@ class Citizen(CitizenAPI):
def update_companies(self):
html = self._get_economy_my_companies().text
page_details = loads(re.search(r"var pageDetails\s+= ({.*});", html).group(1))
page_details = json.loads(re.search(r"var pageDetails\s+= ({.*});", html).group(1))
self.my_companies.work_units = int(page_details.get("total_works", 0))
have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html)
have_companies = re.search(r"var companies\s+= ({.*}});", html)
if have_holdings and have_companies:
self.my_companies.prepare_companies(loads(have_companies.group(1)))
self.my_companies.prepare_holdings(loads(have_holdings.group(1)))
self.my_companies.prepare_companies(json.loads(have_companies.group(1)))
self.my_companies.prepare_holdings(json.loads(have_holdings.group(1)))
self.my_companies.update_holding_companies()
def update_inventory(self) -> Dict[str, Any]:
@ -573,7 +574,7 @@ class Citizen(CitizenAPI):
def update_weekly_challenge(self):
data = self._get_main_weekly_challenge_data().json()
self.details.pp = data.get("player", {}).get("prestigePoints", 0)
self.details.next_pp = []
self.details.next_pp.clear()
for reward in data.get("rewards", {}).get("normal", {}):
status = reward.get("status", "")
if status == "rewarded":
@ -2060,33 +2061,32 @@ class Citizen(CitizenAPI):
return {battle.invader.id: r.json().get(str(battle.invader.id)).get("fighterData"),
battle.defender.id: r.json().get(str(battle.defender.id)).get("fighterData")}
def contribute_cc_to_country(self, amount=0.) -> bool:
def contribute_cc_to_country(self, amount=0., country_id: int = 71) -> bool:
self.update_money()
amount = int(amount)
if self.details.cc < amount or amount < 20:
return False
data = dict(country=71, action='currency', value=amount)
self.telegram.send_message(f"Donated {amount}cc to {COUNTRIES[71]}")
data = dict(country=country_id, action='currency', value=amount)
self.reporter.report_action("CONTRIBUTE_CC", data, str(amount))
r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error')
def contribute_food_to_country(self, amount: int = 0, quality: int = 1) -> bool:
def contribute_food_to_country(self, amount: int = 0, quality: int = 1, country_id: int = 71) -> bool:
self.update_inventory()
amount = amount // 1
if self.food["q" + str(quality)] < amount or amount < 10:
return False
data = dict(country=71, action='food', value=amount, quality=quality)
data = dict(country=country_id, action='food', value=amount, quality=quality)
self.reporter.report_action("CONTRIBUTE_FOOD", data, FOOD_ENERGY[quality] * amount)
r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error')
def contribute_gold_to_country(self, amount: int) -> bool:
def contribute_gold_to_country(self, amount: int, country_id: int = 71) -> bool:
self.update_money()
if self.details.cc < amount:
return False
data = dict(country=71, action='gold', value=amount)
data = dict(country=country_id, action='gold', value=amount)
self.reporter.report_action("CONTRIBUTE_GOLD", data, str(amount))
r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error')
@ -2120,7 +2120,7 @@ class Citizen(CitizenAPI):
# return ret
def to_json(self, indent: bool = False) -> str:
return dumps(self.__dict__, cls=MyJSONEncoder, indent=4 if indent else None, sort_keys=True)
return json.dumps(self.__dict__, cls=MyJSONEncoder, indent=4 if indent else None, sort_keys=True)
def get_game_token_offers(self):
r = self._post_economy_game_tokens_market('retrieve').json()
@ -2195,11 +2195,14 @@ class Citizen(CitizenAPI):
while not isinstance(available_weapons, list):
available_weapons = self._get_military_show_weapons(battle_id).json()
weapon_quality = -1
weapon_damage = 0
if not battle.is_air:
for weapon in available_weapons:
if weapon['weaponId'] == 7 and weapon['weaponQuantity'] > 30:
weapon_quality = 7
break
try:
if weapon['weaponQuantity'] > 30 and weapon['damage'] > weapon_damage:
weapon_quality = int(weapon['weaponId'])
except ValueError:
pass
return self.change_weapon(battle_id, weapon_quality)
def change_weapon(self, battle_id: int, weapon_quality: int) -> int:

View File

@ -5,13 +5,17 @@ import random
import threading
import time
from collections import defaultdict, deque
from json import JSONDecodeError, JSONEncoder, loads
from typing import Any, Dict, Iterable, List, Mapping, Tuple, Union
from requests import Response, Session, post
from erepublik import utils
try:
import simplejson as json
except ImportError:
import json
class ErepublikException(Exception):
def __init__(self, message):
@ -281,9 +285,9 @@ class SlowRequests(Session):
}
try:
loads(resp.text)
json.loads(resp.text)
file_data.update({"ext": "json"})
except JSONDecodeError:
except json.JSONDecodeError:
file_data.update({"ext": "html"})
filename = 'debug/requests/{time}_{name}{extra}.{ext}'.format(**file_data)
@ -356,6 +360,18 @@ class Config:
self.telegram_chat_id = 0
self.telegram_token = ""
@property
def __dict__(self):
return dict(email=self.email, work=self.work, train=self.train, wam=self.wam,
auto_sell=self.auto_sell, auto_sell_all=self.auto_sell_all, employees=self.employees,
fight=self.fight, air=self.air, ground=self.ground, all_in=self.all_in,
next_energy=self.next_energy, boosters=self.boosters, travel_to_fight=self.travel_to_fight,
always_travel=self.always_travel, epic_hunt=self.epic_hunt, epic_hunt_ebs=self.epic_hunt_ebs,
rw_def_side=self.rw_def_side, interactive=self.interactive,
continuous_fighting=self.continuous_fighting, auto_buy_raw=self.auto_buy_raw,
force_wam=self.force_wam, sort_battles_time=self.sort_battles_time, force_travel=self.force_travel,
telegram=self.telegram, telegram_chat_id=self.telegram_chat_id, telegram_token=self.telegram_token)
class Energy:
limit = 500 # energyToRecover
@ -1045,16 +1061,16 @@ class Reporter:
self.__to_update.append(json_data)
class MyJSONEncoder(JSONEncoder):
class MyJSONEncoder(json.JSONEncoder):
def default(self, o):
from erepublik.citizen import Citizen
if isinstance(o, decimal.Decimal):
return float("{:.02f}".format(o))
elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', year=o.year, month=o.month, day=o.day, hour=o.hour, minute=o.minute,
second=o.second, microsecond=o.microsecond, tzinfo=o.tzinfo.zone if o.tzinfo else None)
return dict(__type__='datetime', date=o.strftime("%Y-%m-%d"), time=o.strftime("%H:%M:%S"),
tzinfo=o.tzinfo.zone if o.tzinfo else None)
elif isinstance(o, datetime.date):
return dict(__type__='date', year=o.year, month=o.month, day=o.day)
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())
@ -1131,42 +1147,31 @@ class Battle:
def is_air(self) -> bool:
return not bool(self.zone_id % 4)
def __init__(self, battle: Dict[str, Any] = None):
def __init__(self, battle: Dict[str, Any]):
"""Object representing eRepublik battle.
:param battle: Dict object for single battle from '/military/campaignsJson/list' response's 'battles' object
"""
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.invader = BattleSide(
battle.get('inv', {}).get('id'), battle.get('inv', {}).get('points'),
[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.id = int(battle.get('id'))
self.war_id = int(battle.get('war_id'))
self.zone_id = int(battle.get('zone_id'))
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'),
[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.invader = BattleSide(
battle.get('inv', {}).get('id'), battle.get('inv', {}).get('points'),
[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 = defaultdict(BattleDivision)
for div, data in battle.get('div', {}).items():
@ -1195,13 +1200,14 @@ class Battle:
now = utils.now()
is_started = self.start < utils.now()
if is_started:
time_part = "{}".format(now - self.start)
time_part = " {}".format(now - self.start)
else:
time_part = "- {}".format(self.start - now)
time_part = "-{}".format(self.start - now)
return f"Battle {self.id} | " \
f"{utils.COUNTRIES[self.invader.id]:>21.21}:{utils.COUNTRIES[self.defender.id]:<21.21} | " \
f"{utils.ISO_CC[self.invader.id]} : {utils.ISO_CC[self.defender.id]} | " \
f"Round {self.zone_id:2} | " \
f"Time since start {time_part}"
f"Round time {time_part}"
class EnergyToFight:
@ -1236,11 +1242,11 @@ class EnergyToFight:
class TelegramBot:
__initialized = False
__initialized: bool = False
__queue: List[str]
chat_id = 0
api_url = ""
player_name = ""
chat_id: int = 0
api_url: str = ""
player_name: str = ""
__thread_stopper: threading.Event
_last_time: datetime.datetime
_last_full_energy_report: datetime.datetime
@ -1251,11 +1257,13 @@ class TelegramBot:
self._threads = []
self.__queue = []
self.__thread_stopper = threading.Event() if stop_event is None else stop_event
self._last_full_energy_report = self._next_time = self._last_time = utils.good_timedelta(utils.now(), datetime.timedelta(hours=1))
@property
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)))
return {'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

View File

@ -1,18 +1,24 @@
import datetime
import inspect
import json
import os
import re
import sys
import time
import traceback
import unicodedata
from decimal import Decimal
from pathlib import Path
from typing import Any, List, Mapping, NoReturn, Optional, Union
import pytz
import requests
try:
import simplejson as json
except ImportError:
import json
__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",
@ -97,6 +103,15 @@ COUNTRY_LINK = {1: 'Romania', 9: 'Brazil', 11: 'France', 12: 'Germany', 13: 'Hun
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', }
ISO_CC = {1: 'ROU', 9: 'BRA', 10: 'ITA', 11: 'FRA', 12: 'DEU', 13: 'HUN', 14: 'CHN', 15: 'ESP', 23: 'CAN', 24: 'USA',
26: 'MEX', 27: 'ARG', 28: 'VEN', 29: 'GBR', 30: 'CHE', 31: 'NLD', 32: 'BEL', 33: 'AUT', 34: 'CZE', 35: 'POL',
36: 'SVK', 37: 'NOR', 38: 'SWE', 39: 'FIN', 40: 'UKR', 41: 'RUS', 42: 'BGR', 43: 'TUR', 44: 'GRC', 45: 'JPN',
47: 'KOR', 48: 'IND', 49: 'IDN', 50: 'AUS', 51: 'ZAF', 52: 'MDA', 53: 'PRT', 54: 'IRL', 55: 'DNK', 56: 'IRN',
57: 'PAK', 58: 'ISR', 59: 'THA', 61: 'SVN', 63: 'HRV', 64: 'CHL', 65: 'SRB', 66: 'MYS', 67: 'PHL', 68: 'SGP',
69: 'BiH', 70: 'EST', 71: 'LVA', 72: 'LTU', 73: 'PRK', 74: 'URY', 75: 'PRY', 76: 'BOL', 77: 'PER', 78: 'COL',
79: 'MKD', 80: 'MNE', 81: 'TWN', 82: 'CYP', 83: 'BLR', 84: 'NZL', 164: 'SAU', 165: 'EGY', 166: 'UAE',
167: 'ALB', 168: 'GEO', 169: 'ARM', 170: 'NGA', 171: 'CUB'}
def now() -> datetime.datetime:
return datetime.datetime.now(erep_tz).replace(microsecond=0)
@ -379,27 +394,28 @@ 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
if elite:
dmg = dmg * 11 / 10
elite = 1.1 if elite else 1
elite_dmg = dmg * elite
dmg = int(elite_dmg)
if tp and rang >= 70:
dmg = dmg * (1 + Decimal((rang - 69) / 10))
legend = 1 if (not tp or rang < 70) else 1 + (rang - 69) / 10
legend_dmg = dmg * legend
dmg = int(legend_dmg)
dmg = dmg * (100 + booster) / 100
return dmg * (1.1 if ne else 1)
if ne:
dmg = dmg * 11 / 10
return 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:
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']
@ -410,8 +426,8 @@ 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:
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

View File

@ -5,7 +5,7 @@ flake8==3.7.9
ipython==7.11.1
isort==4.3.21
pip==19.3.1
PyInstaller==3.5
PyInstaller==3.6
pytz==2019.3
requests==2.22.0
setuptools==44.0.0

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.19.0
current_version = 0.19.2
commit = True
tag = True

View File

@ -43,6 +43,6 @@ setup(
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/',
version='0.19.0',
version='0.19.2',
zip_safe=False,
)