Compare commits

...

49 Commits

Author SHA1 Message Date
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
10 changed files with 1271 additions and 872 deletions

View File

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

View File

@ -13,43 +13,43 @@ __all__ = ['SlowRequests', 'CitizenAPI']
class SlowRequests(Session):
last_time: datetime.datetime
timeout: datetime.timedelta = datetime.timedelta(milliseconds=500)
uas: List[str] = [
_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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 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',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 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 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.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',
'Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',
]
debug: bool = False
def __init__(self, proxies: Dict[str, str] = None):
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': random.choice(self.uas)
})
self.headers.update({'User-Agent': user_agent})
@property
def as_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, debug=self.debug)
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()
@ -73,31 +73,29 @@ class SlowRequests(Session):
args.update({'kwargs': kwargs})
if data:
args.update({"data": data})
args.update({'data': data})
if json:
args.update({"json": json})
args.update({'json': json})
if params:
args.update({"params": 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"))
pass
def _log_response(self, url, resp, redirect: bool = False):
from erepublik import Citizen
if self.debug:
if resp.history and not redirect:
for hist_resp in resp.history:
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)
fd_path = 'debug/requests'
fd_time = self.last_time.strftime('%Y/%m/%d/%H-%M-%S')
fd_name = utils.slugify(url[len(Citizen.url):])
fd_extra = "_REDIRECT" if redirect else ""
fd_name = utils.slugify(url[len(CitizenBaseAPI.url):])
fd_extra = '_REDIRECT' if redirect else ""
try:
utils.json.loads(resp.text)
@ -107,7 +105,6 @@ class SlowRequests(Session):
filename = f'{fd_path}/{fd_time}_{fd_name}{fd_extra}.{fd_ext}'
utils.write_file(filename, resp.text)
pass
class CitizenBaseAPI:
@ -120,6 +117,10 @@ class CitizenBaseAPI:
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)
@ -137,10 +138,31 @@ class CitizenBaseAPI:
url = f'http://{username}:{password}@{host}:{port}' if username and password else f'socks5://{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:
env = dict(l=['tets', ], s=[], c=["l_chathwe", "l_chatroom"], 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: int, image: str, challenge: str, coords: List[Dict[str, int]], src: str
) -> Response:
env = dict(l=['tets', ], s=[], c=["l_chathwe", "l_chatroom"], m=0)
data = dict(_token=self.token, captchaId=captcha, imageId=image, challengeId=challenge,
clickMatrix=coords, isMobile=0, env=utils.b64json(env), src=src)
return self.post(f'{self.url}/main/sessionUnlock', data=data)
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})
return self.post(f"{self.url}/main/collect-anniversary-reward", data={'_token': self.token})
# 12th anniversary endpoints
def _get_anniversary_quest_data(self) -> Response:
@ -151,7 +173,7 @@ class ErepublikAnniversaryAPI(CitizenBaseAPI):
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}
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:
@ -161,7 +183,7 @@ class ErepublikAnniversaryAPI(CitizenBaseAPI):
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})
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})
@ -183,7 +205,7 @@ class ErepublikArticleAPI(CitizenBaseAPI):
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})
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:
@ -202,13 +224,13 @@ class ErepublikArticleAPI(CitizenBaseAPI):
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)
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}
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"})
headers={'Referer': f"{self.url}/economy/create-company"})
def _get_economy_inventory_items(self) -> Response:
return self.get(f"{self.url}/economy/inventory-items/")
@ -229,12 +251,12 @@ class ErepublikCompanyAPI(CitizenBaseAPI):
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)
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 action_type == 'production':
if employ is None:
employ = {}
if wam is None:
@ -257,7 +279,7 @@ class ErepublikCompanyAPI(CitizenBaseAPI):
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)
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:
@ -267,16 +289,16 @@ class ErepublikCompanyAPI(CitizenBaseAPI):
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"})
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"})
data.update({'sell': 'sell'})
else:
data.update({"dissolve": factory_id})
data.update({'dissolve': factory_id})
return self.post(f"{self.url}/economy/sell-company/{factory_id}",
data=data, headers={"Referer": self.url})
data=data, headers={'Referer': self.url})
class ErepublikCountryAPI(CitizenBaseAPI):
@ -287,7 +309,7 @@ class ErepublikCountryAPI(CitizenBaseAPI):
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"})
headers={'Referer': f"{self.url}/country/economy/Latvia"})
class ErepublikEconomyAPI(CitizenBaseAPI):
@ -309,20 +331,20 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
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)
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}"})
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}"})
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)
@ -340,19 +362,19 @@ class ErepublikEconomyAPI(CitizenBaseAPI):
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)
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)
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'],
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')
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)
@ -380,7 +402,7 @@ 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})
return self.get(f"{self.url}/main/city-data/{city_id}/residents", params={'currentPage': page, **params})
class ErepublikMilitaryAPI(CitizenBaseAPI):
@ -403,7 +425,7 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
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}
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:
@ -414,8 +436,10 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
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) -> Response:
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:
@ -431,10 +455,10 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
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":
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)
@ -451,9 +475,20 @@ class ErepublikMilitaryAPI(CitizenBaseAPI):
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):
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", json=data)
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)
class ErepublikPoliticsAPI(CitizenBaseAPI):
@ -514,10 +549,10 @@ class ErepublikProfileAPI(CitizenBaseAPI):
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})
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})
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}")
@ -537,16 +572,16 @@ class ErepublikProfileAPI(CitizenBaseAPI):
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"})
data.update({'action': 'addFriend'})
else:
data.update({"action": "removeFriend"})
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}
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:
@ -558,7 +593,7 @@ class ErepublikProfileAPI(CitizenBaseAPI):
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")
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:
@ -566,19 +601,19 @@ class ErepublikProfileAPI(CitizenBaseAPI):
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"}
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}
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}
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}
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:
@ -588,7 +623,7 @@ class ErepublikProfileAPI(CitizenBaseAPI):
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)
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:
@ -600,7 +635,7 @@ class ErepublikProfileAPI(CitizenBaseAPI):
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}
data = {'action': action, 'params': params, '_token': self.token}
return self.post(f"{self.url}/main/profile-update", data=data)
@ -617,73 +652,73 @@ class ErepublikWallPostAPI(CitizenBaseAPI):
# ## Country
def _post_main_country_comment_retrieve(self, post_id: int) -> Response:
data = {"_token": self.token, "postId": post_id}
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}
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}
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}
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}
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}
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}
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}
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}
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}
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}
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}
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}
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}
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}
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}
data = {'_token': self.token, 'page': 1, 'switchedFrom': False}
return self.post(f"{self.url}/main/wall-post/retrieve/json", data=data)
# ## Medal posting

File diff suppressed because it is too large Load Diff

View File

@ -3,16 +3,15 @@ import hashlib
import threading
import weakref
from decimal import Decimal
from typing import Any, Dict, Generator, Iterable, List, NamedTuple, NoReturn, Tuple, Union
from typing import Any, Dict, Generator, Iterable, List, NamedTuple, NoReturn, Union
from requests import Response, Session, post
from . import constants, types, utils
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
'ErepublikNetworkException', 'EnergyToFight',
'Holding', 'MyCompanies', 'MyJSONEncoder', 'OfferItem', 'Politics', 'Reporter', 'TelegramReporter',
'Inventory']
'ErepublikNetworkException', 'EnergyToFight', 'Holding', 'Inventory', 'MyCompanies', 'OfferItem', 'Politics',
'Reporter', 'TelegramReporter', ]
class ErepublikException(Exception):
@ -29,7 +28,7 @@ class ErepublikNetworkException(ErepublikException):
class Holding:
id: int
region: int
companies: List["Company"]
companies: List['Company']
name: str
_citizen = weakref.ReferenceType
@ -37,16 +36,16 @@ class Holding:
self._citizen = weakref.ref(citizen)
self.id: int = _id
self.region: int = region
self.companies: List["Company"] = list()
self.companies: List['Company'] = list()
if name:
self.name = name
else:
comp_sum = len(self.companies)
name = f"Holding (#{self.id}) with {comp_sum} "
if comp_sum == 1:
name += "company"
name += 'company'
else:
name += "companies"
name += 'companies'
self.name = name
@property
@ -54,20 +53,20 @@ class Holding:
return len([1 for company in self.companies if company.wam_enabled and not company.already_worked])
@property
def wam_companies(self) -> Iterable["Company"]:
def wam_companies(self) -> Iterable['Company']:
return [company for company in self.companies if company.wam_enabled]
@property
def employable_companies(self) -> Iterable["Company"]:
def employable_companies(self) -> Iterable['Company']:
return [company for company in self.companies if company.preset_works]
def add_company(self, company: "Company") -> NoReturn:
def add_company(self, company: 'Company') -> NoReturn:
self.companies.append(company)
self.companies.sort()
def get_wam_raw_usage(self) -> Dict[str, Decimal]:
frm = Decimal("0.00")
wrm = Decimal("0.00")
frm = Decimal('0.00')
wrm = Decimal('0.00')
for company in self.wam_companies:
if company.industry in [1, 7, 8, 9, 10, 11]:
frm += company.raw_usage
@ -75,11 +74,11 @@ class Holding:
wrm += company.raw_usage
return dict(frm=frm, wrm=wrm)
def get_wam_companies(self, raw_factory: bool = None) -> List["Company"]:
def get_wam_companies(self, raw_factory: bool = None) -> List['Company']:
raw = []
factory = []
for company in self.wam_companies:
if not company.already_worked and not company.cannot_wam_reason == "war":
if not company.already_worked and not company.cannot_wam_reason == 'war':
if company.is_raw:
raw.append(company)
else:
@ -93,9 +92,9 @@ class Holding:
comp = len(self.companies)
name = f"Holding (#{self.id}) with {comp} "
if comp == 1:
name += "company"
name += 'company'
else:
name += "companies"
name += 'companies'
return name
def __repr__(self):
@ -147,10 +146,10 @@ class Company:
self.raw_usage = - self.products_made * raw_usage
def _get_real_quality(self, quality) -> int:
# 7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
# 12: "WRM q1", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
# 18: "HRM q1", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
# 24: "ARM q1", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5",
# 7: 'FRM q1', 8: 'FRM q2', 9: 'FRM q3', 10: 'FRM q4', 11: 'FRM q5',
# 12: 'WRM q1', 13: 'WRM q2', 14: 'WRM q3', 15: 'WRM q4', 16: 'WRM q5',
# 18: 'HRM q1', 19: 'HRM q2', 20: 'HRM q3', 21: 'HRM q4', 22: 'HRM q5',
# 24: 'ARM q1', 25: 'ARM q2', 26: 'ARM q3', 27: 'ARM q4', 28: 'ARM q5',
if 7 <= self.industry <= 11:
return self.industry % 6
elif 12 <= self.industry <= 16:
@ -164,10 +163,10 @@ class Company:
@property
def _internal_industry(self) -> int:
# 7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
# 12: "WRM q1", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
# 18: "HRM q1", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
# 24: "ARM q1", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5",
# 7: 'FRM q1', 8: 'FRM q2', 9: 'FRM q3', 10: 'FRM q4', 11: 'FRM q5',
# 12: 'WRM q1', 13: 'WRM q2', 14: 'WRM q3', 15: 'WRM q4', 16: 'WRM q5',
# 18: 'HRM q1', 19: 'HRM q2', 20: 'HRM q3', 21: 'HRM q4', 22: 'HRM q5',
# 24: 'ARM q1', 25: 'ARM q2', 26: 'ARM q3', 27: 'ARM q4', 28: 'ARM q5',
if 7 <= self.industry <= 11:
return 7
elif 12 <= self.industry <= 16:
@ -186,22 +185,22 @@ class Company:
def __hash__(self):
return hash(self._sort_keys)
def __lt__(self, other: "Company"):
def __lt__(self, other: 'Company'):
return self._sort_keys < other._sort_keys
def __le__(self, other: "Company"):
def __le__(self, other: 'Company'):
return self._sort_keys <= other._sort_keys
def __gt__(self, other: "Company"):
def __gt__(self, other: 'Company'):
return self._sort_keys > other._sort_keys
def __ge__(self, other: "Company"):
def __ge__(self, other: 'Company'):
return self._sort_keys >= other._sort_keys
def __eq__(self, other: "Company"):
def __eq__(self, other: 'Company'):
return self._sort_keys == other._sort_keys
def __ne__(self, other: "Company"):
def __ne__(self, other: 'Company'):
return self._sort_keys != other._sort_keys
def __str__(self):
@ -468,6 +467,7 @@ class Details:
pp: int = 0
pin: str = None
gold: float = 0
level: int = 0
next_pp: List[int] = None
citizen_id: int = 0
citizenship: constants.Country
@ -482,6 +482,8 @@ class Details:
def __init__(self):
self.next_pp = []
self.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}
_default_country = constants.Country(0, 'Unknown', 'Unknown', 'XX')
self.citizenship = self.current_country = self.residence_country = _default_country
@property
def xp_till_level_up(self):
@ -512,11 +514,15 @@ class Details:
@property
def as_dict(self) -> Dict[str, Union[int, float, str, constants.Country, bool]]:
return dict(xp=self.xp, cc=self.cc, pp=self.pp, pin=self.pin, gold=self.gold, next_pp=self.next_pp,
citizen_id=self.citizen_id, citizenship=self.citizenship, current_region=self.current_region,
current_country=self.current_country, residence_region=self.residence_region,
residence_country=self.residence_country, daily_task_done=self.daily_task_done,
daily_task_reward=self.daily_task_reward, mayhem_skills=self.mayhem_skills,
xp_till_level_up=self.xp_till_level_up)
level=self.level, citizen_id=self.citizen_id, citizenship=self.citizenship,
current_region=self.current_region, current_country=self.current_country,
residence_region=self.residence_region, residence_country=self.residence_country,
daily_task_done=self.daily_task_done, daily_task_reward=self.daily_task_reward,
mayhem_skills=self.mayhem_skills, xp_till_level_up=self.xp_till_level_up)
@property
def is_elite(self):
return self.level > 100
class Politics:
@ -575,7 +581,7 @@ class Reporter:
self._citizen = weakref.ref(citizen)
self._req = Session()
self.url = "https://api.erep.lv"
self._req.headers.update({"user-agent": "eRepublik Script Reporter v3",
self._req.headers.update({"user-agent": 'eRepublik Script Reporter v3',
'erep-version': utils.__version__,
'erep-user-id': str(self.citizen_id),
'erep-user-name': self.citizen.name})
@ -599,10 +605,10 @@ class Reporter:
if self.__to_update:
for unreported_data in self.__to_update:
unreported_data.update(player_id=self.citizen_id, key=self.key)
unreported_data = utils.json.loads(utils.json.dumps(unreported_data, cls=MyJSONEncoder))
unreported_data = utils.json_loads(utils.json_dumps(unreported_data))
self._req.post(f"{self.url}/bot/update", json=unreported_data)
self.__to_update.clear()
data = utils.json.loads(utils.json.dumps(data, cls=MyJSONEncoder))
data = utils.json.loads(utils.json_dumps(data))
r = self._req.post(f"{self.url}/bot/update", json=data)
return r
@ -610,13 +616,13 @@ class Reporter:
if not self.__registered:
try:
r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id))
if not r.json().get("status"):
if not r.json().get('status'):
self._req.post(f"{self.url}/bot/register", json=dict(name=self.name, email=self.email,
player_id=self.citizen_id))
finally:
self.__registered = True
self.allowed = True
self.report_action("STARTED", value=utils.now().strftime("%F %T"))
self.report_action('STARTED', value=utils.now().strftime("%F %T"))
def send_state_update(self, xp: int, cc: float, gold: float, inv_total: int, inv: int,
hp_limit: int, hp_interval: int, hp_available: int, food: int, pp: int):
@ -644,16 +650,26 @@ class Reporter:
else:
self.__to_update.append(json_data)
def report_fighting(self, battle: "Battle", invader: bool, division: "BattleDivision", damage: float, hits: int):
def report_fighting(self, battle: 'Battle', invader: bool, division: 'BattleDivision', damage: float, hits: int):
side = battle.invader if invader else battle.defender
self.report_action('FIGHT', dict(battle_id=battle.id, side=side, dmg=damage,
air=battle.has_air, hits=hits,
round=battle.zone_id, extra=dict(battle=battle, side=side, division=division)))
def report_money_donation(self, citizen_id: int, amount: float, is_currency: bool = True):
cur = 'cc' if is_currency else 'gold'
self.report_action('DONATE_MONEY', dict(citizen_id=citizen_id, amount=amount, currency=cur),
f"Successfully donated {amount}{cur} to citizen with id {citizen_id}!")
def report_item_donation(self, citizen_id: int, amount: float, quality: int, industry: str):
self.report_action('DONATE_ITEMS',
dict(citizen_id=citizen_id, amount=amount, quality=quality, industry=industry),
f"Successfully donated {amount} x {industry} q{quality} to citizen with id {citizen_id}!")
def report_promo(self, kind: str, time_until: datetime.datetime):
self._req.post(f"{self.url}/promos/add/", data=dict(kind=kind, time_untill=time_until))
def fetch_battle_priorities(self, country: constants.Country) -> List["Battle"]:
def fetch_battle_priorities(self, country: constants.Country) -> List['Battle']:
try:
battle_response = self._req.get(f'{self.url}/api/v1/battles/{country.id}')
return [self.citizen.all_battles[bid] for bid in battle_response.json().get('battle_ids', []) if
@ -661,52 +677,28 @@ class Reporter:
except: # noqa
return []
def fetch_tasks(self) -> List[Union[str, Tuple[Any]]]:
def fetch_tasks(self) -> List[Dict[str, Any]]:
try:
task_response = self._req.get(f'{self.url}/api/v1/command',
params=dict(citizen=self.citizen_id, key=self.key))
return task_response.json().get('task_collection')
task_response = self._req.post(
f'{self.url}/api/v1/command', data=dict(citizen=self.citizen_id, key=self.key)).json()
if task_response.get('status'):
return task_response.get('data')
else:
return []
except: # noqa
return []
class MyJSONEncoder(utils.json.JSONEncoder):
def default(self, o):
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=o.headers.__dict__, url=o.url, text=o.text)
elif hasattr(o, 'as_dict'):
return o.as_dict
elif isinstance(o, set):
return list(o)
elif isinstance(o, Citizen):
return o.to_json()
try:
return super().default(o)
except Exception as e: # noqa
return 'Object is not JSON serializable'
class BattleSide:
points: int
deployed: List[constants.Country]
allies: List[constants.Country]
battle: "Battle"
battle: 'Battle'
_battle: weakref.ReferenceType
country: constants.Country
is_defender: bool
def __init__(self, battle: "Battle", country: constants.Country, points: int, allies: List[constants.Country],
def __init__(self, battle: 'Battle', country: constants.Country, points: int, allies: List[constants.Country],
deployed: List[constants.Country], defender: bool):
self._battle = weakref.ref(battle)
self.country = country
@ -720,11 +712,11 @@ class BattleSide:
return self.country.id
def __repr__(self):
side_text = "Defender" if self.is_defender else "Invader "
side_text = 'Defender' if self.is_defender else 'Invader '
return f"<BattleSide: {side_text} {self.country.name}|{self.points:>2d}p>"
def __str__(self):
side_text = "Defender" if self.is_defender else "Invader "
side_text = 'Defender' if self.is_defender else 'Invader '
return f"{side_text} {self.country.name} - {self.points:>2d} points"
def __format__(self, format_spec):
@ -750,7 +742,7 @@ class BattleDivision:
inv_medal: Dict[str, int]
terrain: int
div: int
battle: "Battle"
battle: 'Battle'
_battle: weakref.ReferenceType
@property
@ -766,7 +758,7 @@ class BattleDivision:
def div_end(self) -> bool:
return utils.now() >= self.end
def __init__(self, battle: "Battle", div_id: int, end: datetime.datetime, epic: bool, div: int, wall_for: int,
def __init__(self, battle: 'Battle', div_id: int, end: datetime.datetime, epic: bool, div: int, wall_for: int,
wall_dom: float, terrain_id: int = 0):
"""Battle division helper class
@ -782,7 +774,7 @@ class BattleDivision:
self.id = div_id
self.end = end
self.epic = epic
self.wall = dict({"for": wall_for, "dom": wall_dom})
self.wall = {'for': wall_for, 'dom': wall_dom}
self.terrain = terrain_id
self.div = div
@ -795,7 +787,7 @@ class BattleDivision:
if self.terrain:
base_name += f" ({self.terrain_display})"
if self.div_end:
base_name += " Ended"
base_name += ' Ended'
return base_name
def __repr__(self):
@ -887,8 +879,8 @@ class Battle:
battle_div = BattleDivision(self, div_id=data.get('id'), div=data.get('div'), end=end,
epic=data.get('epic_type') in [1, 5],
wall_for=data.get('wall').get("for"),
wall_dom=data.get('wall').get("dom"),
wall_for=data.get('wall').get('for'),
wall_dom=data.get('wall').get('dom'),
terrain_id=data.get('terrain', 0))
self.div.update({div: battle_div})
@ -974,7 +966,7 @@ class TelegramReporter:
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))
self.send_message('Telegram initialized')
def send_message(self, message: str) -> bool:
self.__queue.append(message)
@ -1002,16 +994,30 @@ class TelegramReporter:
new_line = '\n' if multiple else ''
self.send_message(f"New award: {new_line}*{msg}*")
def report_fight(self, battle: 'Battle', invader: bool, division: 'BattleDivision', damage: float, hits: int):
side_txt = (battle.invader if invader else battle.defender).country.iso
self.send_message(f"*Fight report*:\n{int(damage):,d} dmg ({hits} hits) in"
f" [battle {battle.id} for {battle.region_name[:16]}]({battle.link}) in d{division.div} on "
f"{side_txt} side")
def report_item_donation(self, citizen_id: int, amount: float, product: str):
self.send_message(f"*Donation*: {amount} x {product} to citizen "
f"[{citizen_id}](https://www.erepublik.com/en/citizen/profile/{citizen_id})")
def report_money_donation(self, citizen_id: int, amount: float, is_currency: bool = True):
self.send_message(f"*Donation*: {amount}{'cc' if is_currency else 'gold'} to citizen "
f"[{citizen_id}](https://www.erepublik.com/en/citizen/profile/{citizen_id})")
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)
message = "\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"))
message = f"Player *{self.player_name}*\n\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.clear()

View File

@ -3,8 +3,8 @@ 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"]
__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))
@ -60,11 +60,11 @@ class Industries:
'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"}
__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):
@ -82,21 +82,21 @@ class Industries:
AIR_RANKS: 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*****",
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*****',
}
COUNTRIES: Dict[int, Country] = {
@ -145,27 +145,27 @@ COUNTRIES: Dict[int, Country] = {
FOOD_ENERGY: Dict[str, int] = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
GROUND_RANKS: 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",
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] = {
@ -186,6 +186,6 @@ GROUND_RANK_POINTS: Dict[int, int] = {
INDUSTRIES = Industries()
TERRAINS: Dict[int, str] = {0: "Standard", 1: 'Industrial', 2: 'Urban', 3: 'Suburbs', 4: 'Airport', 5: 'Plains',
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'}

157
erepublik/logging.py Normal file
View File

@ -0,0 +1,157 @@
import base64
import datetime
import inspect
import logging
import os
import sys
import weakref
from pathlib import Path
import requests
from logging import handlers, LogRecord
from typing import Union, Dict, Any
from erepublik.classes import Reporter
from erepublik.constants import erep_tz
from erepublik.utils import slugify, json_loads, json, now, json_dumps
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, *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.utcfromtimestamp(timestamp).astimezone(erep_tz)
def format(self, record: logging.LogRecord) -> str:
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)
return super().format(record)
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
class ErepublikErrorHTTTPHandler(handlers.HTTPHandler):
def __init__(self, reporter: Reporter):
logging.Handler.__init__(self, level=logging.ERROR)
self._reporter = weakref.ref(reporter)
self.host = 'localhost:5000'
self.url = '/ebot/error/'
self.method = 'POST'
self.secure = False
self.credentials = (str(reporter.citizen_id), reporter.key)
self.context = None
@property
def reporter(self):
return self._reporter()
def mapLogRecord(self, record: logging.LogRecord) -> Dict[str, Any]:
data = super().mapLogRecord(record)
# Log last response
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'))
resp = dict(name=f"{resp_time}_{name}.{ext}", content=html.encode('utf-8'),
mimetype="application/json" if ext == 'json' else "text/html")
files = [('file', (resp.get('name'), resp.get('content'), resp.get('mimetype'))), ]
filename = f'log/{now().strftime("%F")}.log'
if os.path.isfile(filename):
files.append(('file', (filename[4:], open(filename, 'rb'), 'text/plain')))
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'))
if local_vars:
if 'state_thread' in local_vars:
local_vars.pop('state_thread', None)
if isinstance(local_vars.get('self'), self.reporter.citizen.__class__):
local_vars['self'] = repr(local_vars['self'])
if isinstance(local_vars.get('player'), self.reporter.citizen.__class__):
local_vars['player'] = repr(local_vars['player'])
if isinstance(local_vars.get('citizen'), self.reporter.citizen.__class__):
local_vars['citizen'] = repr(local_vars['citizen'])
files.append(('file', ('local_vars.json', json_dumps(local_vars), "application/json")))
files.append(('file', ('instance.json', self.reporter.citizen.to_json(indent=True), "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)

View File

@ -1,18 +1,18 @@
import datetime
import inspect
import os
import re
import sys
import textwrap
import time
import traceback
import unicodedata
import warnings
from base64 import b64encode
from decimal import Decimal
from pathlib import Path
from typing import Any, Dict, List, Union
import pytz
import requests
from requests import Response
from . import __version__, constants
@ -21,15 +21,12 @@ try:
except ImportError:
import json
__all__ = ['VERSION', 'calculate_hit', 'caught_error', 'date_from_eday', 'eday_from_date', 'deprecation',
'get_air_hit_dmg_value', 'get_file', 'get_ground_hit_dmg_value', 'get_sleep_seconds', 'good_timedelta',
'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now',
'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file', 'write_request',
'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock']
if not sys.version_info >= (3, 6):
raise AssertionError('This script requires Python version 3.6 and higher\n'
'But Your version is v{}.{}.{}'.format(*sys.version_info))
__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',
]
VERSION: str = __version__
@ -105,33 +102,32 @@ 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 = f"[{erep_time_now.strftime('%F %T')}] {msg}" if timestamp else msg
txt = "\n".join(["\n".join(textwrap.wrap(line, 120)) for line in txt.splitlines()])
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):
kwargs.pop("should_print", None)
_write_log(should_print=True, *args, **kwargs)
def write_silent_log(*args, **kwargs):
kwargs.pop("should_print", None)
_write_log(should_print=False, *args, **kwargs)
# def _write_log(msg, timestamp: bool = True, should_print: bool = False):
# erep_time_now = now()
# txt = f"[{erep_time_now.strftime('%F %T')}] {msg}" if timestamp else msg
# if not os.path.isdir('log'):
# os.mkdir('log')
# with open(f'log/{erep_time_now.strftime("%F")}.log', 'a', encoding='utf-8') as f:
# f.write(f'{txt}\n')
# if should_print:
# print(txt)
#
#
# def write_interactive_log(*args, **kwargs):
# kwargs.pop('should_print', None)
# _write_log(should_print=True, *args, **kwargs)
#
#
# def write_silent_log(*args, **kwargs):
# kwargs.pop('should_print', None)
# _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:
@ -158,166 +154,14 @@ def write_file(filename: str, content: str) -> int:
return ret
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 = f"debug/requests/{now().strftime('%F_%H-%M-%S')}_{name}.{ext}"
write_file(filename, html)
else:
return {"name": f"{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: Dict[str, 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) and player.r:
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 = f"[eBot][{now().strftime('%F %T')}] Promos: {name}"
elif captcha:
resp = {"name": "%s.html" % name, "mimetype": "text/html",
"content": file_content_template.format(title="ReCaptcha", body="<br/>".join(content))}
subject = f"[eBot][{now().strftime('%F %T')}] RECAPTCHA: {name}"
else:
subject = f"[eBot][{now().strftime('%F %T')}] Bug trace: {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)
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'])
from erepublik.classes import MyJSONEncoder
files.append(('file', ("local_vars.json", json.dumps(local_vars, cls=MyJSONEncoder),
"application/json")))
if isinstance(player, Citizen):
files.append(('file', ("instance.json", player.to_json(indent=True), "application/json")))
requests.post('https://pasts.72.lv', data=data, files=files)
def 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 caught_error(e: Exception):
process_error(str(e), "Unclassified", sys.exc_info(), interactive=False)
def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None,
interactive: bool = None):
"""
Process error logging and email sending to developer
:param interactive: Should print interactively
:type interactive: bool
:param log_info: String to be written in output
:type log_info: str
:param name: String Instance name
:type name: str
:param exc_info: tuple output from sys.exc_info()
:type exc_info: tuple
:param citizen: Citizen instance
:type citizen: Citizen
:param commit_id: Caller's code version's commit id
:type commit_id: str
"""
type_, value_, traceback_ = exc_info
content = [log_info]
content += [f"eRepublik version {VERSION}"]
if commit_id:
content += [f"Commit id {commit_id}"]
content += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))]
if interactive:
write_interactive_log(log_info)
elif interactive is not None:
write_silent_log(log_info)
trace = inspect.trace()
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:
local_vars = dict()
send_email(name, content, citizen, local_vars=local_vars)
def process_warning(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None):
"""
Process error logging and email sending to developer
: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
content = [log_info]
if commit_id:
content += ["Commit id: %s" % commit_id]
content += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))]
trace = inspect.trace()
if trace:
local_vars = trace[-1][0].f_locals
else:
local_vars = dict()
send_email(name, content, citizen, local_vars=local_vars)
def slugify(value, allow_unicode=False) -> str:
"""
Function copied from Django2.2.1 django.utils.text.slugify
@ -348,7 +192,7 @@ def calculate_hit(strength: float, rang: int, tp: bool, elite: bool, ne: bool, b
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()
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
@ -360,7 +204,7 @@ def get_ground_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_
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()
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)
@ -380,32 +224,112 @@ def get_final_hit_dmg(base_dmg: Union[Decimal, float, str], rang: int,
return Decimal(dmg)
# def _clear_up_battle_memory(battle):
# del battle.invader._battle, battle.defender._battle
# for div_id, division in battle.div.items():
# del division._battle
def deprecation(message):
warnings.warn(message, DeprecationWarning, stacklevel=2)
def wait_for_lock(function):
def wrapper(instance, *args, **kwargs):
if not instance.concurrency_available.wait(600):
e = 'Concurrency not freed in 10min!'
instance.write_log(e)
if instance.debug:
instance.report_error(e)
return None
else:
instance.concurrency_available.clear()
try:
ret = function(instance, *args, **kwargs)
except Exception as e:
instance.concurrency_available.set()
raise e
instance.concurrency_available.set()
return ret
# def wait_for_lock(function):
# def wrapper(instance, *args, **kwargs):
# if not instance.concurrency_available.wait(600):
# e = 'Concurrency not freed in 10min!'
# instance.write_log(e)
# if instance.debug:
# instance.report_error(e)
# return None
# else:
# instance.concurrency_available.clear()
# try:
# ret = function(instance, *args, **kwargs)
# except Exception as e:
# instance.concurrency_available.set()
# raise e
# instance.concurrency_available.set()
# return ret
#
# return wrapper
return wrapper
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).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).encode('utf-8')).decode('utf-8')
class ErepublikJSONEncoder(json.JSONEncoder):
def default(self, o):
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()
try:
return super().default(o)
except Exception as e: # noqa
return 'Object is not JSON serializable'

View File

@ -1,19 +1,20 @@
bump2version==1.0.1
coverage==5.3.1
edx-sphinx-theme==1.6.0
coverage==5.4
edx-sphinx-theme==1.6.1
flake8==3.8.4
ipython>=7.19.0
jedi!=0.18.0
isort==5.7.0
pip==20.3.3
pip==21.0
pre-commit==2.9.3
pur==5.3.0
PyInstaller==4.1
PyInstaller==4.2
PySocks==1.7.1
pytest==6.2.1
pytest==6.2.2
pytz>=2020.5
requests>=2.25.1
responses==0.12.1
setuptools==51.1.1
Sphinx==3.4.2
setuptools==52.0.0
Sphinx==3.4.3
twine==3.3.0
wheel==0.36.2

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.23.4.5
current_version = 0.24.0.4
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?

View File

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