Compare commits

...

27 Commits

Author SHA1 Message Date
f73f2b7b9f Bump version: 0.19.3 → 0.19.4 2020-02-18 20:12:02 +02:00
f6433908b4 New notifications API 2020-02-18 20:11:34 +02:00
2fd317153f Bump version: 0.19.2 → 0.19.3 2020-01-27 01:54:03 +02:00
256a180bd6 if error occures on main thread - simplify error logging 2020-01-27 01:53:52 +02:00
c7dbeb2078 Bump version: 0.19.1 → 0.19.2 2020-01-26 20:45:18 +02:00
8e5ae0320a Merge branch 'bugfix'
* bugfix:
  Hey Plato! If You're reading this - fix your variable types and there will be 90% less bugs in Your code!!!
  Some TelegramBot tweaks
  when full energy update citizen info would stop working because trying to get timedelta from now - _last_full_energy_report
  Too broad exception was cought without notifying about actual error - when Telegram isn't enabled
2020-01-26 20:44:43 +02:00
5c258d7aae Hey Plato! If You're reading this - fix your variable types and there will be 90% less bugs in Your code!!!
{'weaponId': 6, 'weaponQuantity': 0, 'damage': 120}
{'weaponId': '7', 'weaponQuantity': 1185, 'inClip': 7, 'damage': 200}
{'weaponId': 10, 'weaponQuantity': 0, 'damage': 100}
2020-01-26 20:44:21 +02:00
75b43fc455 Merge pull request #2 from eeriks/eeriks-patch-1
GHSA-7fcj-pq9j-wh2r
2020-01-17 15:41:02 +02:00
2362dc51e8 GHSA-7fcj-pq9j-wh2r
https://github.com/advisories/GHSA-7fcj-pq9j-wh2r
2020-01-17 15:40:34 +02:00
a2cf479135 Some TelegramBot tweaks 2020-01-16 13:49:24 +02:00
00b87dc832 when full energy update citizen info would stop working because trying to get timedelta from now - _last_full_energy_report 2020-01-14 13:43:10 +02:00
0dd1ae9ac5 Too broad exception was cought without notifying about actual error - when Telegram isn't enabled 2020-01-14 13:42:34 +02:00
76bd40c655 Bump version: 0.19.0 → 0.19.1 2020-01-13 21:33:58 +02:00
15e6deebda Battle initialization without valid data should be avoided to not run into strange and hard to trace bugs.
Jsonification updates - if simplejson is available some packages are importing simplejson with try-except and later throwing simplejson errors which should be cought when calling .json() on every request.
Fixed error logging
2020-01-13 21:33:50 +02:00
69d0e7df0a Bump version: 0.18.3 → 0.19.0 2020-01-13 10:40:33 +02:00
4f92894ab6 DocUpdate 2020-01-13 10:40:22 +02:00
9c64bfac0f Merge branch 'inventory_updates'
* inventory_updates:
  Python 3.8, isort, requirement update
  Representation of Citizen class
  Created method for current products on sale. Updated inventory to also include products on sale

# Conflicts:
#	erepublik/citizen.py
2020-01-13 10:31:05 +02:00
1f07f2e270 Update:
Citizen.set_default_weapon() - eRepublik should return list with all available weapon qualities, but when a battle is just launched, they return only dict with barehands
Citizen.fight() - no longer calls self.set_default_weapon()
Citizen.find_battle_and_fight() - now calls self.set_default_weapon() just before fighting
Citizen.update_war_info() - returns previous battle list if responses 'last_updated' isn't more than 30s old

New:
Citizen.get_battle_for_war(war_id) - returns Battle instance for specific war, if battle is active for given war
2020-01-13 10:28:42 +02:00
71d204843d Python 3.8, isort, requirement update 2020-01-09 12:03:11 +02:00
d9305214eb Representation of Citizen class 2020-01-07 19:55:31 +02:00
5556d5f772 Created method for current products on sale. Updated inventory to also include products on sale 2020-01-07 16:28:42 +02:00
1c47d169d2 Bump version: 0.18.2 → 0.18.3 2020-01-07 11:32:59 +02:00
ef44787bad make clean-pyc removes log/ and debug/ run 'artifacts' 2020-01-07 11:32:48 +02:00
42431134e1 UA update 2020-01-07 11:30:40 +02:00
bedaeeefd1 Battle division update 2020-01-07 11:15:40 +02:00
bbf304aa99 Bump version: 0.18.1 → 0.18.2 2020-01-05 10:53:39 +02:00
a2447959e7 fight must receive battle id as int, added warnings support 2020-01-05 10:53:26 +02:00
15 changed files with 599 additions and 229 deletions

View File

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

2
.gitignore vendored
View File

@ -85,7 +85,7 @@ celerybeat-schedule
# virtualenv # virtualenv
.venv .venv
venv/ venv*/
ENV/ ENV/
# Spyder project settings # Spyder project settings

View File

@ -5,7 +5,7 @@ python:
- 3.7 - 3.7
- 3.6 - 3.6
# Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors # Command to install dependencies, e.g. pip install -r requirements_dev.txt --use-mirrors
install: pip install -U tox-travis install: pip install -U tox-travis
# Command to run tests, e.g. python setup.py test # Command to run tests, e.g. python setup.py test

View File

@ -23,12 +23,6 @@ If you are reporting a bug, please include:
* Any details about your local setup that might be helpful in troubleshooting. * Any details about your local setup that might be helpful in troubleshooting.
* Detailed steps to reproduce the bug. * Detailed steps to reproduce the bug.
Fix Bugs
~~~~~~~~
Look through the GitHub issues for bugs. Anything tagged with "bug" and "help
wanted" is open to whoever wants to implement it.
Implement Features Implement Features
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -59,7 +53,7 @@ Get Started!
Ready to contribute? Here's how to set up `erepublik` for local development. Ready to contribute? Here's how to set up `erepublik` for local development.
1. Fork the `erepublik_script` repo on GitHub. 1. Fork the `erepublik` repo on GitHub.
2. Clone your fork locally:: 2. Clone your fork locally::
$ git clone git@github.com:your_name_here/erepublik.git $ git clone git@github.com:your_name_here/erepublik.git
@ -103,7 +97,7 @@ Before you submit a pull request, check that it meets these guidelines:
your new functionality into a function with a docstring, and add the your new functionality into a function with a docstring, and add the
feature to the list in README.rst. feature to the list in README.rst.
3. The pull request should work for Python 3.7.1. Check 3. The pull request should work for Python 3.7.1. Check
https://travis-ci.org/eeriks/erepublik_script/pull_requests https://travis-ci.org/eeriks/erepublik/pull_requests
and make sure that the tests pass for all supported Python versions. and make sure that the tests pass for all supported Python versions.
Tips Tips
@ -112,7 +106,7 @@ Tips
To run a subset of tests:: To run a subset of tests::
$ python -m unittest tests.test_erepublik_script $ python -m unittest tests.test_erepublik
Deploying Deploying
--------- ---------

View File

@ -2,6 +2,20 @@
History History
======= =======
0.19.0 (2020-01-13)
-------------------
* Created method for current products on sale.
* Updated inventory to also include products on sale
* set_default_weapon() - eRepublik should return list with all available weapon qualities, but when a battle is just launched, they return only dict with barehands
* fight() - no longer calls self.set_default_weapon()
* find_battle_and_fight() - now calls self.set_default_weapon() just before fighting
* update_war_info() - returns previous battle list if responses 'last_updated' isn't more than 30s old
* get_battle_for_war(war_id) - returns Battle instance for specific war, if battle is active for given war
* Citizen.get_raw_surplus() fixed and moved to Citizen.my_companies.get_wam_raw_usage()
* Implemented division switching
* improved multi bomb deploy with auto traveling,
* Citizen.fight() simplified battle data gathering logic -> Citizen.shoot logic improved
0.17.0 (2019-11-21) 0.17.0 (2019-11-21)
------------------- -------------------

View File

@ -43,6 +43,8 @@ clean-pyc: ## remove Python file artifacts
find . -name '*.pyo' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} + find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} + find . -name '__pycache__' -exec rm -fr {} +
rm -rf debug/
rm -rf log/
clean-test: ## remove test and coverage artifacts clean-test: ## remove test and coverage artifacts
rm -fr .tox/ rm -fr .tox/

View File

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

View File

@ -1,19 +1,22 @@
import re import re
import sys import sys
from collections import defaultdict
from datetime import datetime, timedelta from datetime import datetime, timedelta
from itertools import product from itertools import product
from json import loads, dumps
from threading import Event from threading import Event
from time import sleep from time import sleep
from typing import Dict, List, Tuple, Any, Union, Set from typing import Any, Dict, List, Optional, Set, Tuple, Union
from requests import Response, RequestException from requests import RequestException, Response
from erepublik.classes import (CitizenAPI, Battle, Reporter, Config, Energy, Details, Politics, MyCompanies, from erepublik.classes import (Battle, BattleDivision, CitizenAPI, Config, Details, Energy, ErepublikException,
TelegramBot, ErepublikException, BattleDivision, MyJSONEncoder) MyCompanies, MyJSONEncoder, Politics, Reporter, TelegramBot)
from erepublik.utils import * from erepublik.utils import *
try:
import simplejson as json
except ImportError:
import json
class Citizen(CitizenAPI): class Citizen(CitizenAPI):
division = 0 division = 0
@ -41,15 +44,15 @@ class Citizen(CitizenAPI):
eday = 0 eday = 0
energy: Energy energy: Energy = None
details: Details details: Details = None
politics: Politics politics: Politics = None
my_companies: MyCompanies my_companies: MyCompanies = None
reporter: Reporter reporter: Reporter = None
stop_threads: Event stop_threads: Event = None
telegram: TelegramBot telegram: TelegramBot = None
r: Response r: Response = None
name = "Not logged in!" name = "Not logged in!"
debug = False debug = False
__registered = False __registered = False
@ -110,6 +113,10 @@ class Citizen(CitizenAPI):
def __str__(self) -> str: def __str__(self) -> str:
return f"Citizen {self.name}" return f"Citizen {self.name}"
def __repr__(self):
return self.__str__()
@property
def __dict__(self): def __dict__(self):
ret = super().__dict__.copy() ret = super().__dict__.copy()
ret.pop('stop_threads', None) ret.pop('stop_threads', None)
@ -137,7 +144,6 @@ class Citizen(CitizenAPI):
return return
html = resp.text html = resp.text
self.check_for_new_medals(html)
re_token = re.search(r'var csrfToken = \'(\w{32})\'', html) re_token = re.search(r'var csrfToken = \'(\w{32})\'', html)
re_login_token = re.search(r'<input type="hidden" id="_token" name="_token" value="(\w{32})">', html) re_login_token = re.search(r'<input type="hidden" id="_token" name="_token" value="(\w{32})">', html)
if re_token: if re_token:
@ -149,7 +155,7 @@ class Citizen(CitizenAPI):
raise ErepublikException("Something went wrong! Can't find token in page! Exiting!") raise ErepublikException("Something went wrong! Can't find token in page! Exiting!")
try: try:
self.update_citizen_info(resp.text) self.update_citizen_info(resp.text)
except: except (AttributeError, json.JSONDecodeError, ValueError, KeyError):
pass pass
def _login(self): def _login(self):
@ -176,7 +182,7 @@ class Citizen(CitizenAPI):
if j['error'] and j["message"] == 'Too many requests': if j['error'] and j["message"] == 'Too many requests':
self.write_log("Made too many requests! Sleeping for 30 seconds.") self.write_log("Made too many requests! Sleeping for 30 seconds.")
self.sleep(30) self.sleep(30)
except: except (json.JSONDecodeError, KeyError, TypeError):
pass pass
if response.status_code >= 400: if response.status_code >= 400:
self.r = response self.r = response
@ -205,15 +211,13 @@ class Citizen(CitizenAPI):
return self.get(url, **kwargs) return self.get(url, **kwargs)
try: try:
self.update_citizen_info(html=response.text) self.update_citizen_info(response.text)
except: except (AttributeError, json.JSONDecodeError, ValueError, KeyError):
pass pass
if self._errors_in_response(response): if self._errors_in_response(response):
self.get_csrf_token() self.get_csrf_token()
self.get(url, **kwargs) self.get(url, **kwargs)
else:
self.check_for_new_medals(response.text)
self.r = response self.r = response
return response return response
@ -251,40 +255,38 @@ class Citizen(CitizenAPI):
elif json: elif json:
json.update({"_token": self.token}) json.update({"_token": self.token})
response = self.post(url, data=data, json=json, **kwargs) response = self.post(url, data=data, json=json, **kwargs)
else:
self.check_for_new_medals(response.text)
self.r = response self.r = response
return response return response
def check_for_new_medals(self, html: str): def check_for_new_medals(self):
new_medals = re.findall(r'(<div class="home_reward reward achievement">.*?<div class="bottom"></div>\s*</div>)', notifications = self._get_main_citizen_daily_assistant().json()
html, re.M | re.S | re.I)
data: Dict[Tuple[str, Union[float, str]], Dict[str, Union[int, str, float]]] = {} data: Dict[Tuple[str, Union[float, str]], Dict[str, Union[int, str, float]]] = {}
for medal in new_medals: for medal in notifications.get('notifications', []):
try: if medal.get('details', {}).get('type') == "citizenAchievement":
info = re.search(r"<h3>New Achievement</h3>.*?<p.*?>(.*?)</p>.*?" params = medal.get('details', {}).get('achievement')
r"achievement_recieved.*?<strong>(.*?)</strong>.*?" about = medal.get('details').get('description')
r"<div title=\"(.*?)\">", medal, re.M | re.S) title = medal.get('title')
about = info.group(1).strip() # award_id = re.search(r'"wall_enable_alerts_(\d+)', medal)
title = info.group(2).strip() # if award_id:
award_id = re.search(r'"wall_enable_alerts_(\d+)', medal) # self._post_main_wall_post_automatic(**{'message': title, 'awardId': award_id.group(1)})
if award_id:
self._post_main_wall_post_automatic(**{'message': title, 'awardId': award_id.group(1)}) if params.get('ccValue'):
reward, currency = info.group(3).strip().split(" ") reward = params.get('ccValue')
while not isinstance(reward, float): currency = "Currency"
try: elif params.get('goldValue'):
reward = float(reward) reward = params.get('goldValue')
except ValueError: currency = "Gold"
reward = reward[:-1] else:
reward = params.get('energyValue')
currency = "Energy"
if (title, reward) not in data: if (title, reward) not in data:
data[(title, reward)] = {'about': about, 'kind': title, 'reward': reward, "count": 1, data[(title, reward)] = {'about': about, 'kind': title, 'reward': reward, "count": 1,
"currency": currency} "currency": currency, "params": params}
else: else:
data[(title, reward)]['count'] += 1 data[(title, reward)]['count'] += 1
except AttributeError: self._post_main_global_alerts_close(medal.get('id'))
continue
if data: if data:
msgs = ["{count} x {kind}, totaling {} {currency}\n" msgs = ["{count} x {kind}, totaling {} {currency}\n"
@ -296,13 +298,13 @@ class Citizen(CitizenAPI):
for info in data.values(): for info in data.values():
self.reporter.report_action("NEW_MEDAL", info) self.reporter.report_action("NEW_MEDAL", info)
levelup = re.search(r"<p>Congratulations, you have reached experience <strong>level (\d+)</strong></p>", html) # levelup = re.search(r"<p>Congratulations, you have reached experience <strong>level (\d+)</strong></p>", html)
if levelup: # if levelup:
level = levelup.group(1) # level = levelup.group(1)
msg = f"Level up! Current level {level}" # msg = f"Level up! Current level {level}"
self.write_log(msg) # self.write_log(msg)
self.telegram.report_medal(f"Level *{level}*") # self.telegram.report_medal(f"Level *{level}*")
self.reporter.report_action("LEVEL_UP", value=level) # self.reporter.report_action("LEVEL_UP", value=level)
def update_all(self, force_update=False): def update_all(self, force_update=False):
# Do full update max every 5 min # Do full update max every 5 min
@ -317,16 +319,18 @@ class Citizen(CitizenAPI):
self.update_money() self.update_money()
self.update_weekly_challenge() self.update_weekly_challenge()
self.send_state_update() self.send_state_update()
self.check_for_new_medals()
def update_citizen_info(self, html: str = None): def update_citizen_info(self, html: str = None):
""" """
Gets main page and updates most information about player Gets main page and updates most information about player
""" """
if html is None: if html is None:
self.check_for_new_medals()
self._get_main() self._get_main()
return return
ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1) 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: if self.promos is None:
self.promos = {} self.promos = {}
else: else:
@ -361,7 +365,7 @@ class Citizen(CitizenAPI):
) )
ugly_js = re.search(r"var erepublik = ({.*}),\s+", html).group(1) 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", {}) citizen = citizen_js.get("citizen", {})
self.eday = citizen_js.get("settings").get("eDay") self.eday = citizen_js.get("settings").get("eDay")
@ -417,14 +421,14 @@ class Citizen(CitizenAPI):
def update_companies(self): def update_companies(self):
html = self._get_economy_my_companies().text 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)) self.my_companies.work_units = int(page_details.get("total_works", 0))
have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html) have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html)
have_companies = re.search(r"var companies\s+= ({.*}});", html) have_companies = re.search(r"var companies\s+= ({.*}});", html)
if have_holdings and have_companies: if have_holdings and have_companies:
self.my_companies.prepare_companies(loads(have_companies.group(1))) self.my_companies.prepare_companies(json.loads(have_companies.group(1)))
self.my_companies.prepare_holdings(loads(have_holdings.group(1))) self.my_companies.prepare_holdings(json.loads(have_holdings.group(1)))
self.my_companies.update_holding_companies() self.my_companies.update_holding_companies()
def update_inventory(self) -> Dict[str, Any]: def update_inventory(self) -> Dict[str, Any]:
@ -503,7 +507,21 @@ class Citizen(CitizenAPI):
if kind not in final_items: if kind not in final_items:
final_items[kind] = {} final_items[kind] = {}
icon = item['icon'] if item['icon'] else "//www.erepublik.net/images/modules/manager/tab_storage.png" if item['icon']:
icon = item['icon']
else:
if item['type'] == 'damageBoosters':
icon = "/images/modules/pvp/damage_boosters/damage_booster.png"
elif item['type'] == 'aircraftDamageBoosters':
icon = "/images/modules/pvp/damage_boosters/air_damage_booster.png"
elif item['type'] == 'prestigePointsBoosters':
icon = "/images/modules/pvp/prestige_points_boosters/prestige_booster.png"
elif item['type'] == 'speedBoosters':
icon = "/images/modules/pvp/speed_boosters/speed_booster.png"
elif item['type'] == 'catchupBoosters':
icon = "/images/modules/pvp/ghost_boosters/icon_booster_30_60.png"
else:
icon = "//www.erepublik.net/images/modules/manager/tab_storage.png"
data = dict(kind=kind, quality=item.get('quality', 0), amount=item.get('amount', 0), data = dict(kind=kind, quality=item.get('quality', 0), amount=item.get('amount', 0),
durability=item.get('duration', 0), icon=icon, name=name) durability=item.get('duration', 0), icon=icon, name=name)
if item.get('type') in ('damageBoosters', "aircraftDamageBoosters"): if item.get('type') in ('damageBoosters', "aircraftDamageBoosters"):
@ -531,16 +549,29 @@ class Citizen(CitizenAPI):
icon=icon) icon=icon)
) )
offers = {}
for offer in self._get_economy_my_market_offers().json():
kind = self.get_industry_name(offer['industryId'])
data = dict(quality=offer.get('quality', 0), amount=offer.get('amount', 0), icon=offer.get('icon'),
kind=kind, name=kind)
data = {data['quality']: data}
if kind not in offers:
offers[kind] = {}
offers[kind].update(data)
self.inventory.update({"used": j.get("inventoryStatus").get("usedStorage"), self.inventory.update({"used": j.get("inventoryStatus").get("usedStorage"),
"total": j.get("inventoryStatus").get("totalStorage")}) "total": j.get("inventoryStatus").get("totalStorage")})
inventory = dict(items=dict(active=active_items, final=final_items, raw=raw_materials), status=self.inventory) inventory = dict(items=dict(active=active_items, final=final_items,
raw=raw_materials, offers=offers), status=self.inventory)
self.food["total"] = sum([self.food[q] * FOOD_ENERGY[q] for q in FOOD_ENERGY]) self.food["total"] = sum([self.food[q] * FOOD_ENERGY[q] for q in FOOD_ENERGY])
return inventory return inventory
def update_weekly_challenge(self): def update_weekly_challenge(self):
data = self._get_main_weekly_challenge_data().json() data = self._get_main_weekly_challenge_data().json()
self.details.pp = data.get("player", {}).get("prestigePoints", 0) 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", {}): for reward in data.get("rewards", {}).get("normal", {}):
status = reward.get("status", "") status = reward.get("status", "")
if status == "rewarded": if status == "rewarded":
@ -557,10 +588,13 @@ class Citizen(CitizenAPI):
if not self.details.current_country: if not self.details.current_country:
self.update_citizen_info() self.update_citizen_info()
if self.__last_war_update_data and self.__last_war_update_data.get('last_updated', 0) + 30 > self.now.timestamp():
resp_json = self.__last_war_update_data
else:
resp_json = self._get_military_campaigns_json_list().json() resp_json = self._get_military_campaigns_json_list().json()
if resp_json.get("countries"): if resp_json.get("countries"):
if self.all_battles is None: if self.all_battles is None:
self.all_battles = defaultdict(Battle) self.all_battles = {}
else: else:
self.all_battles.clear() self.all_battles.clear()
@ -636,7 +670,7 @@ class Citizen(CitizenAPI):
def now(self) -> datetime: def now(self) -> datetime:
""" """
Returns aware datetime object localized to US/Pacific (eRepublik time) Returns aware datetime object localized to US/Pacific (eRepublik time)
:return: datetime.datetime :return: datetime
""" """
return now() return now()
@ -712,35 +746,35 @@ class Citizen(CitizenAPI):
# CS Battles # CS Battles
elif self.details.citizenship in battle_sides: elif self.details.citizenship in battle_sides:
if battle.is_air: if battle.is_air:
cs_battles_ground.append(battle.id)
else:
cs_battles_air.append(battle.id) cs_battles_air.append(battle.id)
else:
cs_battles_ground.append(battle.id)
# Current location battles: # Current location battles:
elif self.details.current_country in battle_sides: elif self.details.current_country in battle_sides:
if battle.is_air: if battle.is_air:
deployed_battles_ground.append(battle.id)
else:
deployed_battles_air.append(battle.id) deployed_battles_air.append(battle.id)
else:
deployed_battles_ground.append(battle.id)
# Deployed battles and allied battles: # Deployed battles and allied battles:
elif self.details.current_country in battle.invader.allies + battle.defender.allies + battle_sides: elif self.details.current_country in battle.invader.allies + battle.defender.allies + battle_sides:
if self.details.current_country in battle.invader.deployed + battle.defender.deployed: if self.details.current_country in battle.invader.deployed + battle.defender.deployed:
if battle.is_air: if battle.is_air:
deployed_battles_ground.append(battle.id)
else:
deployed_battles_air.append(battle.id) deployed_battles_air.append(battle.id)
else:
deployed_battles_ground.append(battle.id)
# Allied battles: # Allied battles:
else: else:
if battle.is_air: if battle.is_air:
ally_battles_ground.append(battle.id)
else:
ally_battles_air.append(battle.id) ally_battles_air.append(battle.id)
else: else:
if battle.is_air: ally_battles_ground.append(battle.id)
other_battles_ground.append(battle.id)
else: else:
if battle.is_air:
other_battles_air.append(battle.id) other_battles_air.append(battle.id)
else:
other_battles_ground.append(battle.id)
ret_battles += (cs_battles_air + cs_battles_ground + ret_battles += (cs_battles_air + cs_battles_ground +
deployed_battles_air + deployed_battles_ground + deployed_battles_air + deployed_battles_ground +
@ -809,26 +843,30 @@ class Citizen(CitizenAPI):
if not self.travel_to_battle(battle_id, country_ids_to_travel): if not self.travel_to_battle(battle_id, country_ids_to_travel):
break break
self.set_default_weapon(battle_id)
self.fight(battle_id, side_id) self.fight(battle_id, side_id)
self.travel_to_residence() self.travel_to_residence()
self.collect_weekly_reward() self.collect_weekly_reward()
break break
def fight(self, battle_id: int, side_id: int = None, count: int = None): def fight(self, battle_id: int, side_id: int = None, count: int = None) -> int:
"""Fight in a battle. """Fight in a battle.
Will auto activate booster and travel if allowed to do it and Will auto activate booster and travel if allowed to do it.
in the beginning will switch to default weapon (air - bare hands, ground - q7, if count > 30, else bare hands.
:param battle_id: int BattleId - battle to fight in :param battle_id: int BattleId - battle to fight in
:param side_id: int or None. Battle side to fight in, If side_id not == invader id or not in invader deployed allies list, then defender's side is chosen :param side_id: int or None. Battle side to fight in, If side_id not == invader id or not in invader deployed allies list, then defender's side is chosen
:param count: How many hits to do, if not specified self.should_fight() is called. :param count: How many hits to do, if not specified self.should_fight() is called.
:return: None if no errors while fighting, otherwise error count. :return: None if no errors while fighting, otherwise error count.
""" """
battle = self.all_battles[battle_id] if not isinstance(battle_id, int):
self.report_error(f"WARNINNG! Parameter battle_id should be 'int', but it is '{type(battle_id).__name__}'")
battle_id = int(battle_id)
if battle_id not in self.all_battles:
self.update_war_info()
battle = self.all_battles.get(battle_id)
zone_id = battle.div[11 if battle.is_air else self.division].battle_zone_id zone_id = battle.div[11 if battle.is_air else self.division].battle_zone_id
if not battle.is_air and self.config.boosters: if not battle.is_air and self.config.boosters:
self.activate_dmg_booster() self.activate_dmg_booster()
self.set_default_weapon(battle_id)
error_count = 0 error_count = 0
ok_to_fight = True ok_to_fight = True
if count is None: if count is None:
@ -852,11 +890,10 @@ class Citizen(CitizenAPI):
if total_damage: if total_damage:
self.reporter.report_action("FIGHT", dict(battle=battle_id, side=side_id, dmg=total_damage, self.reporter.report_action("FIGHT", dict(battle=battle_id, side=side_id, dmg=total_damage,
air=battle.is_air, hits=total_hits)) air=battle.is_air, hits=total_hits))
if error_count:
return error_count return error_count
def _shoot(self, battle_id: int, inv_side: bool, zone_id: int): def _shoot(self, battle_id: int, inv_side: bool, zone_id: int):
battle = self.all_battles[battle_id] battle = self.all_battles.get(battle_id)
side_id = battle.invader.id if inv_side else battle.defender.id side_id = battle.invader.id if inv_side else battle.defender.id
if battle.is_air: if battle.is_air:
response = self._post_military_fight_air(battle_id, side_id, zone_id) response = self._post_military_fight_air(battle_id, side_id, zone_id)
@ -908,7 +945,7 @@ class Citizen(CitizenAPI):
if not isinstance(count, int) or count < 1: if not isinstance(count, int) or count < 1:
count = 1 count = 1
has_traveled = False has_traveled = False
battle = self.all_battles[battle_id] battle = self.all_battles.get(battle_id)
if inv_side: if inv_side:
good_countries = [battle.invader.id] + battle.invader.deployed good_countries = [battle.invader.id] + battle.invader.deployed
if self.details.current_country not in good_countries: if self.details.current_country not in good_countries:
@ -940,7 +977,7 @@ class Citizen(CitizenAPI):
:param division_to: int target division to switch to :param division_to: int target division to switch to
:return: :return:
""" """
battle = self.all_battles[battle_id] battle = self.all_battles.get(battle_id)
self._post_main_battlefield_change_division(battle_id, battle.div[division_to].battle_zone_id) self._post_main_battlefield_change_division(battle_id, battle.div[division_to].battle_zone_id)
def work_ot(self): def work_ot(self):
@ -1142,7 +1179,7 @@ class Citizen(CitizenAPI):
return self._do_wam_and_employee_work(wam_holding_id, employee_companies) return self._do_wam_and_employee_work(wam_holding_id, employee_companies)
else: else:
msg = "I was not able to wam and or employ because:\n{}".format(response) msg = "I was not able to wam and or employ because:\n{}".format(response)
self.reporter.report_action("WORK_WAM_EMPLOYEES", value=msg) self.reporter.report_action("WORK_WAM_EMPLOYEES", response, msg)
self.write_log(msg) self.write_log(msg)
wam_count = self.my_companies.get_total_wam_count() wam_count = self.my_companies.get_total_wam_count()
if wam_count: if wam_count:
@ -1662,6 +1699,14 @@ class Citizen(CitizenAPI):
"\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), kind "\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), kind
)) ))
def get_my_market_offers(self) -> List[Dict[str, Union[int, float, str]]]:
ret = []
for offer in self._get_economy_my_market_offers().json():
line = offer.copy()
line.pop('icon', None)
ret.append(line)
return ret
def post_market_offer(self, industry: int, quality: int, amount: int, price: float) -> Response: def post_market_offer(self, industry: int, quality: int, amount: int, price: float) -> Response:
if industry not in self.available_industries.values(): if industry not in self.available_industries.values():
self.write_log(f"Trying to sell unsupported industry {industry}") self.write_log(f"Trying to sell unsupported industry {industry}")
@ -1739,7 +1784,8 @@ class Citizen(CitizenAPI):
@property @property
def factories(self) -> Dict[int, str]: def factories(self) -> Dict[int, str]:
"""Returns factory industries as dict(id: name) """Returns factory industries as dict(id: name)
:return: dict :return: Factory id:name dict
":rtype: Dict[int, str]
""" """
return {1: "Food", 2: "Weapons", 4: "House", 23: "Aircraft", return {1: "Food", 2: "Weapons", 4: "House", 23: "Aircraft",
7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5", 7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
@ -1748,13 +1794,25 @@ class Citizen(CitizenAPI):
24: "ARM q1", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5", } 24: "ARM q1", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5", }
def get_industry_id(self, industry_name: str) -> int: def get_industry_id(self, industry_name: str) -> int:
""" """Returns industry id
Returns industry id
:type industry_name: str :type industry_name: str
:return: int :return: int
""" """
return self.available_industries.get(industry_name, 0) return self.available_industries.get(industry_name, 0)
def get_industry_name(self, industry_id: int) -> str:
"""Returns industry name from industry ID
:type industry_id: int
:return: industry name
:rtype: str
"""
for iname, iid in self.available_industries.items():
if iid == industry_id:
return iname
return ""
def buy_tg_contract(self) -> Response: def buy_tg_contract(self) -> Response:
ret = self._post_main_buy_gold_items('gold', "TrainingContract2", 1) ret = self._post_main_buy_gold_items('gold', "TrainingContract2", 1)
self.reporter.report_action("BUY_TG_CONTRACT", ret.json()) self.reporter.report_action("BUY_TG_CONTRACT", ret.json())
@ -1882,7 +1940,7 @@ class Citizen(CitizenAPI):
r'class="join" title="Join"><span>Join</span></a>', html): r'class="join" title="Join"><span>Join</span></a>', html):
battle_id = re.search(r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" ' battle_id = re.search(r'<a href="//www.erepublik.com/en/military/battlefield/(\d+)" '
r'class="join" title="Join"><span>Join</span></a>', html).group(1) r'class="join" title="Join"><span>Join</span></a>', html).group(1)
ret.update(can_attack=False, battle_id=battle_id) ret.update(can_attack=False, battle_id=int(battle_id))
elif re.search(r'This war is no longer active.', html): elif re.search(r'This war is no longer active.', html):
ret.update(can_attack=False, ended=True) ret.update(can_attack=False, ended=True)
else: else:
@ -2000,33 +2058,32 @@ class Citizen(CitizenAPI):
return {battle.invader.id: r.json().get(str(battle.invader.id)).get("fighterData"), 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")} 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() self.update_money()
amount = int(amount) amount = int(amount)
if self.details.cc < amount or amount < 20: if self.details.cc < amount or amount < 20:
return False return False
data = dict(country=71, action='currency', value=amount) data = dict(country=country_id, action='currency', value=amount)
self.telegram.send_message(f"Donated {amount}cc to {COUNTRIES[71]}")
self.reporter.report_action("CONTRIBUTE_CC", data, str(amount)) self.reporter.report_action("CONTRIBUTE_CC", data, str(amount))
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error') 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() self.update_inventory()
amount = amount // 1 amount = amount // 1
if self.food["q" + str(quality)] < amount or amount < 10: if self.food["q" + str(quality)] < amount or amount < 10:
return False 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) self.reporter.report_action("CONTRIBUTE_FOOD", data, FOOD_ENERGY[quality] * amount)
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error') 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() self.update_money()
if self.details.cc < amount: if self.details.cc < amount:
return False 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)) self.reporter.report_action("CONTRIBUTE_GOLD", data, str(amount))
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
return r.json().get('status') or not r.json().get('error') return r.json().get('status') or not r.json().get('error')
@ -2038,8 +2095,11 @@ class Citizen(CitizenAPI):
r = self._post_main_country_post_create(message, max(post_to_wall_as, key=int) if post_to_wall_as else 0) r = self._post_main_country_post_create(message, max(post_to_wall_as, key=int) if post_to_wall_as else 0)
return r.json() return r.json()
def report_error(self, msg: str = ""): def report_error(self, msg: str = "", is_warning: bool = False):
process_error(msg, self.name, sys.exc_info(), self, self.commit_id, False) if is_warning:
process_warning(msg, self.name, sys.exc_info(), self, self.commit_id)
else:
process_error(msg, self.name, sys.exc_info(), self, self.commit_id, None)
def get_battle_top_10(self, battle_id: int) -> Dict[int, List[Tuple[int, int]]]: def get_battle_top_10(self, battle_id: int) -> Dict[int, List[Tuple[int, int]]]:
return {} return {}
@ -2057,7 +2117,7 @@ class Citizen(CitizenAPI):
# return ret # return ret
def to_json(self, indent: bool = False) -> str: 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): def get_game_token_offers(self):
r = self._post_economy_game_tokens_market('retrieve').json() r = self._post_economy_game_tokens_market('retrieve').json()
@ -2127,18 +2187,28 @@ class Citizen(CitizenAPI):
return self._get_military_show_weapons(battle_id).json() return self._get_military_show_weapons(battle_id).json()
def set_default_weapon(self, battle_id: int) -> int: def set_default_weapon(self, battle_id: int) -> int:
battle = self.all_battles[battle_id] battle = self.all_battles.get(battle_id)
available_weapons = self._get_military_show_weapons(battle_id).json()
while not isinstance(available_weapons, list):
available_weapons = self._get_military_show_weapons(battle_id).json() available_weapons = self._get_military_show_weapons(battle_id).json()
weapon_quality = -1 weapon_quality = -1
weapon_damage = 0
if not battle.is_air: if not battle.is_air:
for weapon in available_weapons: for weapon in available_weapons:
if weapon['weaponId'] == 7 and weapon['weaponQuantity'] > 30: try:
weapon_quality = 7 if weapon['weaponQuantity'] > 30 and weapon['damage'] > weapon_damage:
break weapon_quality = int(weapon['weaponId'])
except ValueError:
pass
return self.change_weapon(battle_id, weapon_quality) return self.change_weapon(battle_id, weapon_quality)
def change_weapon(self, battle_id: int, weapon_quality: int) -> int: def change_weapon(self, battle_id: int, weapon_quality: int) -> int:
battle = self.all_battles[battle_id] battle = self.all_battles.get(battle_id)
battle_zone = battle.div[11 if battle.is_air else self.division].battle_zone_id battle_zone = battle.div[11 if battle.is_air else self.division].battle_zone_id
r = self._post_military_change_weapon(battle_id, battle_zone, weapon_quality) r = self._post_military_change_weapon(battle_id, battle_zone, weapon_quality)
return r.json().get('weaponInfluence') return r.json().get('weaponInfluence')
def get_battle_for_war(self, war_id: int) -> Optional[Battle]:
self.update_war_info()
war_info = self.get_war_status(war_id)
return self.all_battles.get(war_info.get("battle_id"), None)

View File

@ -4,14 +4,18 @@ import hashlib
import random import random
import threading import threading
import time import time
from collections import deque from collections import defaultdict, deque
from json import JSONDecodeError, loads, JSONEncoder from typing import Any, Dict, Iterable, List, Mapping, Tuple, Union
from typing import Any, Dict, List, Union, Mapping, Iterable, Tuple
from requests import Response, Session, post from requests import Response, Session, post
from erepublik import utils from erepublik import utils
try:
import simplejson as json
except ImportError:
import json
class ErepublikException(Exception): class ErepublikException(Exception):
def __init__(self, message): def __init__(self, message):
@ -200,19 +204,19 @@ class SlowRequests(Session):
timeout = datetime.timedelta(milliseconds=500) timeout = datetime.timedelta(milliseconds=500)
uas = [ uas = [
# Chrome # Chrome
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36',
# FireFox # FireFox
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0',
] ]
debug = False debug = False
@ -281,9 +285,9 @@ class SlowRequests(Session):
} }
try: try:
loads(resp.text) json.loads(resp.text)
file_data.update({"ext": "json"}) file_data.update({"ext": "json"})
except JSONDecodeError: except json.JSONDecodeError:
file_data.update({"ext": "html"}) file_data.update({"ext": "html"})
filename = 'debug/requests/{time}_{name}{extra}.{ext}'.format(**file_data) filename = 'debug/requests/{time}_{name}{extra}.{ext}'.format(**file_data)
@ -356,6 +360,18 @@ class Config:
self.telegram_chat_id = 0 self.telegram_chat_id = 0
self.telegram_token = "" 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: class Energy:
limit = 500 # energyToRecover limit = 500 # energyToRecover
@ -508,9 +524,12 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_main_citizen_profile_json(self, player_id: int) -> Response: def _get_main_citizen_profile_json(self, player_id: int) -> Response:
return self.get("{}/main/citizen-profile-json/{}".format(self.url, player_id)) return self.get("{}/main/citizen-profile-json/{}".format(self.url, player_id))
def _get_main_citizen_daily_assistant(self) -> Response: def _get_main_citizen_notifications(self) -> Response:
return self.get("{}/main/citizenDailyAssistant".format(self.url)) return self.get("{}/main/citizenDailyAssistant".format(self.url))
def _get_main_citizen_daily_assistant(self) -> Response:
return self.get("{}/main/citizenNotifications".format(self.url))
def _get_main_city_data_residents(self, city: int, page: int = 1, params: Mapping[str, Any] = None) -> Response: def _get_main_city_data_residents(self, city: int, page: int = 1, params: Mapping[str, Any] = None) -> Response:
if params is None: if params is None:
params = {} params = {}
@ -567,10 +586,6 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_military_campaigns_json_list(self) -> Response: def _get_military_campaigns_json_list(self) -> Response:
return self.get("{}/military/campaignsJson/list".format(self.url)) return self.get("{}/military/campaignsJson/list".format(self.url))
def _get_military_show_weapons(self, battle_id: int) -> Response:
params = {"_token": self.token, "battleId": battle_id}
return self.get("{}/military/show-weapons".format(self.url), params=params)
def _get_military_unit_data(self, unit_id: int, **kwargs) -> Response: 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("{}/military/military-unit-data/".format(self.url), params=params) return self.get("{}/military/military-unit-data/".format(self.url), params=params)
@ -661,6 +676,10 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(_token=self.token, articleId=article_id, amount=amount) data = dict(_token=self.token, articleId=article_id, amount=amount)
return self.post("{}/main/donate-article".format(self.url), data=data) return self.post("{}/main/donate-article".format(self.url), data=data)
def _post_main_global_alerts_close(self, alert_id: int) -> Response:
data = dict(_token=self.token, alert_id=alert_id)
return self.post("{}/main/global-alerts/close".format(self.url), data=data)
def _post_delete_message(self, msg_id: list) -> Response: 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("{}/main/messages-delete".format(self.url), data) return self.post("{}/main/messages-delete".format(self.url), data)
@ -1049,16 +1068,16 @@ class Reporter:
self.__to_update.append(json_data) self.__to_update.append(json_data)
class MyJSONEncoder(JSONEncoder): class MyJSONEncoder(json.JSONEncoder):
def default(self, o): def default(self, o):
from erepublik.citizen import Citizen from erepublik.citizen import Citizen
if isinstance(o, decimal.Decimal): if isinstance(o, decimal.Decimal):
return float("{:.02f}".format(o)) return float("{:.02f}".format(o))
elif isinstance(o, datetime.datetime): elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', year=o.year, month=o.month, day=o.day, hour=o.hour, minute=o.minute, return dict(__type__='datetime', date=o.strftime("%Y-%m-%d"), time=o.strftime("%H:%M:%S"),
second=o.second, microsecond=o.microsecond, tzinfo=o.tzinfo.zone if o.tzinfo else None) tzinfo=o.tzinfo.zone if o.tzinfo else None)
elif isinstance(o, datetime.date): 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): elif isinstance(o, datetime.timedelta):
return dict(__type__='timedelta', days=o.days, seconds=o.seconds, return dict(__type__='timedelta', days=o.days, seconds=o.seconds,
microseconds=o.microseconds, total_seconds=o.total_seconds()) microseconds=o.microseconds, total_seconds=o.total_seconds())
@ -1099,17 +1118,25 @@ class BattleDivision:
def div_end(self) -> bool: def div_end(self) -> bool:
return utils.now() >= self.end return utils.now() >= self.end
def __init__( def __init__(self, **kwargs):
self, div_id: int, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int, """Battle division helper class
:param kwargs: must contain keys:
div_id: int, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int,
wall_for: int, wall_dom: float, def_medal: Tuple[int, int], inv_medal: Tuple[int, int] wall_for: int, wall_dom: float, def_medal: Tuple[int, int], inv_medal: Tuple[int, int]
): """
self.battle_zone_id = div_id
self.end = end self.battle_zone_id = kwargs.get("div_id", 0)
self.epic = epic self.end = kwargs.get("end", 0)
self.dom_pts = dict({"inv": inv_pts, "def": def_pts}) self.epic = kwargs.get("epic", 0)
self.wall = dict({"for": wall_for, "dom": wall_dom}) self.dom_pts = dict({"inv": kwargs.get("inv_pts", 0), "def": kwargs.get("def_pts", 0)})
self.def_medal = {"id": def_medal[0], "dmg": def_medal[1]} self.wall = dict({"for": kwargs.get("wall_for", 0), "dom": kwargs.get("wall_dom", 0)})
self.inv_medal = {"id": inv_medal[0], "dmg": inv_medal[1]} self.def_medal = {"id": kwargs.get("def_medal", 0)[0], "dmg": kwargs.get("def_medal", 0)[1]}
self.inv_medal = {"id": kwargs.get("inv_medal", 0)[0], "dmg": kwargs.get("inv_medal", 0)[1]}
@property
def id(self):
return self.battle_zone_id
class Battle: class Battle:
@ -1127,26 +1154,15 @@ class Battle:
def is_air(self) -> bool: def is_air(self) -> bool:
return not bool(self.zone_id % 4) return not bool(self.zone_id % 4)
def __init__(self, battle: Dict[str, Any] = None): def __init__(self, battle: Dict[str, Any]):
"""Object representing eRepublik battle. """Object representing eRepublik battle.
:param battle: Dict object for single battle from '/military/campaignsJson/list' response's 'battles' object :param battle: Dict object for single battle from '/military/campaignsJson/list' response's 'battles' object
""" """
if battle is None:
battle = {} self.id = int(battle.get('id'))
self.id = 0 self.war_id = int(battle.get('war_id'))
self.war_id = 0 self.zone_id = int(battle.get('zone_id'))
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_rw = bool(battle.get('is_rw'))
self.is_as = bool(battle.get('is_as')) self.is_as = bool(battle.get('is_as'))
self.is_dict_lib = bool(battle.get('is_dict')) or bool(battle.get('is_lib')) self.is_dict_lib = bool(battle.get('is_dict')) or bool(battle.get('is_lib'))
@ -1164,7 +1180,7 @@ class Battle:
[row.get('id') for row in battle.get('def', {}).get('ally_list') if row['deployed']] [row.get('id') for row in battle.get('def', {}).get('ally_list') if row['deployed']]
) )
self.div = {} self.div = defaultdict(BattleDivision)
for div, data in battle.get('div', {}).items(): for div, data in battle.get('div', {}).items():
div = int(data.get('div')) div = int(data.get('div'))
if data.get('end'): if data.get('end'):
@ -1191,13 +1207,14 @@ class Battle:
now = utils.now() now = utils.now()
is_started = self.start < utils.now() is_started = self.start < utils.now()
if is_started: if is_started:
time_part = "{}".format(now - self.start) time_part = " {}".format(now - self.start)
else: else:
time_part = "- {}".format(self.start - now) time_part = "-{}".format(self.start - now)
return f"Battle {self.id} | " \ 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"Round {self.zone_id:2} | " \
f"Time since start {time_part}" f"Round time {time_part}"
class EnergyToFight: class EnergyToFight:
@ -1232,11 +1249,11 @@ class EnergyToFight:
class TelegramBot: class TelegramBot:
__initialized = False __initialized: bool = False
__queue: List[str] __queue: List[str]
chat_id = 0 chat_id: int = 0
api_url = "" api_url: str = ""
player_name = "" player_name: str = ""
__thread_stopper: threading.Event __thread_stopper: threading.Event
_last_time: datetime.datetime _last_time: datetime.datetime
_last_full_energy_report: datetime.datetime _last_full_energy_report: datetime.datetime
@ -1247,11 +1264,13 @@ class TelegramBot:
self._threads = [] self._threads = []
self.__queue = [] self.__queue = []
self.__thread_stopper = threading.Event() if stop_event is None else stop_event 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): def __dict__(self):
return dict(chat_id=self.chat_id, api_url=self.api_url, player=self.player_name, last_time=self._last_time, return {'chat_id': self.chat_id, 'api_url': self.api_url, 'player': self.player_name,
next_time=self._next_time, queue=self.__queue, initialized=self.__initialized, 'last_time': self._last_time, 'next_time': self._next_time, 'queue': self.__queue,
has_threads=bool(len(self._threads))) 'initialized': self.__initialized, 'has_threads': bool(len(self._threads))}
def do_init(self, chat_id: int, token: str, player_name: str = ""): def do_init(self, chat_id: int, token: str, player_name: str = ""):
self.chat_id = chat_id self.chat_id = chat_id

View File

@ -1,23 +1,33 @@
import datetime import datetime
import inspect import inspect
import json
import os import os
import re import re
import sys import sys
import time import time
import traceback import traceback
import unicodedata import unicodedata
from decimal import Decimal
from pathlib import Path from pathlib import Path
from typing import Union, Any, List, NoReturn, Mapping from typing import Any, List, Mapping, NoReturn, Optional, Union
import pytz import pytz
import requests import requests
try:
import simplejson as json
except ImportError:
import json
__all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", 'COUNTRY_LINK', __all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", 'COUNTRY_LINK',
"now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday", "now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday",
"get_sleep_seconds", "interactive_sleep", "silent_sleep", "get_sleep_seconds", "interactive_sleep", "silent_sleep",
"write_silent_log", "write_interactive_log", "get_file", "write_file", "write_silent_log", "write_interactive_log", "get_file", "write_file",
"send_email", "normalize_html_json", "process_error", 'report_promo', 'calculate_hit'] "send_email", "normalize_html_json", "process_error", "process_warning", 'report_promo', 'calculate_hit']
if not sys.version_info >= (3, 7):
raise AssertionError('This script requires Python version 3.7 and higher\n'
'But Your version is v{}.{}.{}'.format(*sys.version_info))
FOOD_ENERGY = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20) FOOD_ENERGY = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20)
COMMIT_ID = "7b92e19" COMMIT_ID = "7b92e19"
@ -93,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', 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', } 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: def now() -> datetime.datetime:
return datetime.datetime.now(erep_tz).replace(microsecond=0) return datetime.datetime.now(erep_tz).replace(microsecond=0)
@ -113,6 +132,15 @@ def localize_dt(dt: Union[datetime.date, datetime.datetime]) -> datetime.datetim
def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.datetime: def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.datetime:
"""Normalize timezone aware datetime object after timedelta to correct jumps over DST switches
:param dt: Timezone aware datetime object
:type dt: datetime.datetime
:param td: timedelta object
:type td: datetime.timedelta
:return: datetime object with correct timezone when jumped over DST
:rtype: datetime.datetime
"""
return erep_tz.normalize(dt + td) return erep_tz.normalize(dt + td)
@ -288,10 +316,48 @@ def normalize_html_json(js: str) -> str:
def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None, def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None,
interactive: bool = False): interactive: Optional[bool] = None):
""" """
Process error logging and email sending to developer Process error logging and email sending to developer
:param interactive: Should print interactively :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: Code's version by commit id
:type commit_id: str
"""
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_))]
if interactive:
write_interactive_log(log_info)
elif interactive is not None:
write_silent_log(log_info)
trace = inspect.trace()
if trace:
trace = trace[-1][0].f_locals
if trace.get('__name__') == '__main__':
trace = {'commit_id': trace.get('COMMIT_ID'),
'interactive': trace.get('INTERACTIVE'),
'version': trace.get('__version__'),
'config': trace.get('CONFIG')}
else:
trace = dict()
send_email(name, content, citizen, local_vars=trace)
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 log_info: String to be written in output
:param name: String Instance name :param name: String Instance name
:param exc_info: tuple output from sys.exc_info() :param exc_info: tuple output from sys.exc_info()
@ -299,19 +365,17 @@ def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commi
:param commit_id: Code's version by commit id :param commit_id: Code's version by commit id
""" """
type_, value_, traceback_ = exc_info type_, value_, traceback_ = exc_info
bugtrace = [] if not commit_id else ["Commit id: %s" % commit_id, ] content = [log_info]
bugtrace += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))] if commit_id:
content += ["Commit id: %s" % commit_id]
content += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))]
if interactive:
write_interactive_log(log_info)
else:
write_silent_log(log_info)
trace = inspect.trace() trace = inspect.trace()
if trace: if trace:
trace = trace[-1][0].f_locals trace = trace[-1][0].f_locals
else: else:
trace = dict() trace = dict()
send_email(name, bugtrace, citizen, local_vars=trace) send_email(name, content, citizen, local_vars=trace)
def report_promo(kind: str, time_untill: datetime.datetime) -> NoReturn: def report_promo(kind: str, time_untill: datetime.datetime) -> NoReturn:
@ -335,27 +399,28 @@ def slugify(value, allow_unicode=False) -> str:
def calculate_hit(strength: float, rang: int, tp: bool, elite: bool, ne: bool, booster: int = 0, def calculate_hit(strength: float, rang: int, tp: bool, elite: bool, ne: bool, booster: int = 0,
weapon: int = 200) -> float: weapon: int = 200, is_deploy: bool = False) -> Decimal:
base_dmg = 10 * (1 + strength / 400) * (1 + rang / 5) * (1 + weapon / 100) dec = 3 if is_deploy else 0
dmg = int(base_dmg * 10 + 5) // 10 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 if elite:
booster_dmg = dmg * booster_multiplier dmg = dmg * 11 / 10
dmg = int(booster_dmg * 10 + 5) // 10
elite = 1.1 if elite else 1 if tp and rang >= 70:
elite_dmg = dmg * elite dmg = dmg * (1 + Decimal((rang - 69) / 10))
dmg = int(elite_dmg)
legend = 1 if (not tp or rang < 70) else 1 + (rang - 69) / 10 dmg = dmg * (100 + booster) / 100
legend_dmg = dmg * legend
dmg = int(legend_dmg)
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, def get_ground_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False,
booster: int = 0, weapon_power: int = 200) -> float: 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'] rang = r['military']['militaryData']['ground']['rankNumber']
strength = r['military']['militaryData']['ground']['strength'] strength = r['military']['militaryData']['ground']['strength']
@ -366,8 +431,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) 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, def get_air_hit_dmg_value(citizen_id: int, natural_enemy: bool = False, true_patriot: bool = False, booster: int = 0,
weapon_power: int = 0) -> float: 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'] rang = r['military']['militaryData']['aircraft']['rankNumber']
elite = r['citizenAttributes']['level'] > 100 elite = r['citizenAttributes']['level'] > 100

View File

@ -0,0 +1,96 @@
import threading
from datetime import timedelta
from erepublik import Citizen, utils
CONFIG = {
'email': 'player@email.com',
'password': 'Pa$5w0rd!',
'interactive': True,
'fight': True,
'debug': True,
'start_battles': {
121672: {"auto_attack": False, "regions": [661]},
125530: {"auto_attack": False, "regions": [259]},
125226: {"auto_attack": True, "regions": [549]},
124559: {"auto_attack": True, "regions": [176]}
}
}
def _battle_launcher(player: Citizen):
"""Launch battles. Check every 5th minute (0,5,10...45,50,55) if any battle could be started on specified regions
and after launching wait for 90 minutes before starting next attack so that all battles aren't launched at the same
time. If player is allowed to fight, do 100 hits on the first round in players division.
:param player: Logged in Citizen instance
":type player: Citizen
"""
global CONFIG
finished_war_ids = {*[]}
war_data = CONFIG.get('start_battles', {})
war_ids = {int(war_id) for war_id in war_data.keys()}
next_attack_time = player.now
next_attack_time = next_attack_time.replace(minute=next_attack_time.minute // 5 * 5, second=0)
while not player.stop_threads.is_set():
try:
attacked = False
player.update_war_info()
running_wars = {b.war_id for b in player.all_battles.values()}
for war_id in war_ids - finished_war_ids - running_wars:
war = war_data[str(war_id)]
war_regions = set(war.get('regions'))
auto_attack = war.get('auto_attack')
status = player.get_war_status(war_id)
if status.get('ended', False):
CONFIG['start_battles'].pop(str(war_id), None)
finished_war_ids.add(war_id)
continue
elif not status.get('can_attack'):
continue
if auto_attack or (player.now.hour > 20 or player.now.hour < 2):
for reg in war_regions:
if attacked:
break
if reg in status.get('regions', {}).keys():
player.launch_attack(war_id, reg, status.get('regions', {}).get(reg))
attacked = True
hits = 100
if player.energy.food_fights >= hits and player.config.fight:
for _ in range(120):
player.update_war_info()
battle_id = player.get_war_status(war_id).get("battle_id")
if battle_id is not None and battle_id in player.all_battles:
player.fight(battle_id, player.details.citizenship, hits)
break
player.sleep(1)
if attacked:
break
if attacked:
break
war_ids -= finished_war_ids
if attacked:
next_attack_time = utils.good_timedelta(next_attack_time, timedelta(hours=1, minutes=30))
else:
next_attack_time = utils.good_timedelta(next_attack_time, timedelta(minutes=5))
player.stop_threads.wait(utils.get_sleep_seconds(next_attack_time))
except:
player.report_error("Task error: start_battles")
def main():
player = Citizen(email=CONFIG['email'], password=CONFIG['password'], auto_login=False)
player.config.interactive = CONFIG['interactive']
player.config.fight = CONFIG['fight']
player.set_debug(CONFIG.get('debug', False))
player.login()
if CONFIG.get('start_battles'):
name = "{}-start_battles-{}".format(player.name, threading.active_count() - 1)
state_thread = threading.Thread(target=_battle_launcher, args=(player,), name=name)
state_thread.start()
if __name__ == "__main__":
main()

102
examples/eat_work_train.py Normal file
View File

@ -0,0 +1,102 @@
from datetime import timedelta
from erepublik import Citizen, utils
CONFIG = {
'email': 'player@email.com',
'password': 'Pa$5w0rd!',
'interactive': True,
'debug': True
}
def main():
player = Citizen(email=CONFIG['email'], password=CONFIG['password'], auto_login=False)
player.config.interactive = CONFIG['interactive']
player.config.fight = CONFIG['fight']
player.set_debug(CONFIG.get('debug', False))
player.login()
now = player.now.replace(second=0, microsecond=0)
dt_max = now.replace(year=9999)
tasks = {
'eat': now,
}
if player.config.work:
tasks.update({'work': now})
if player.config.train:
tasks.update({'train': now})
if player.config.ot:
tasks.update({'ot': now})
if player.config.wam:
tasks.update({'wam': now.replace(hour=14, minute=0)})
while True:
player.update_all()
if tasks.get('work', dt_max) <= now:
player.write_log("Doing task: work")
player.update_citizen_info()
player.work()
if player.config.ot:
tasks['ot'] = now
player.collect_daily_task()
next_time = utils.good_timedelta(now.replace(hour=0, minute=0, second=0), timedelta(days=1))
tasks.update({'work': next_time})
if tasks.get('train', dt_max) <= now:
player.write_log("Doing task: train")
player.update_citizen_info()
player.train()
player.collect_daily_task()
next_time = utils.good_timedelta(now.replace(hour=0, minute=0, second=0), timedelta(days=1))
tasks.update({'train': next_time})
if tasks.get('wam', dt_max) <= now:
player.write_log("Doing task: Work as manager")
success = player.work_wam()
player.eat()
if success:
next_time = utils.good_timedelta(now.replace(hour=14, minute=0, second=0, microsecond=0),
timedelta(days=1))
else:
next_time = utils.good_timedelta(now.replace(second=0, microsecond=0), timedelta(minutes=30))
tasks.update({'wam': next_time})
if tasks.get('eat', dt_max) <= now:
player.write_log("Doing task: eat")
player.eat()
if player.energy.food_fights > player.energy.limit // 10:
next_minutes = 12
else:
next_minutes = (player.energy.limit - 5 * player.energy.interval) // player.energy.interval * 6
next_time = player.energy.reference_time + timedelta(minutes=next_minutes)
tasks.update({'eat': next_time})
if tasks.get('ot', dt_max) <= now:
player.write_log("Doing task: ot")
if now > player.my_companies.next_ot_time:
player.work_ot()
next_time = now + timedelta(minutes=60)
else:
next_time = player.my_companies.next_ot_time
tasks.update({'ot': next_time})
closest_next_time = dt_max
next_tasks = []
for task, next_time in sorted(tasks.items(), key=lambda s: s[1]):
next_tasks.append("{}: {}".format(next_time.strftime('%F %T'), task))
if next_time < closest_next_time:
closest_next_time = next_time
sleep_seconds = int(utils.get_sleep_seconds(closest_next_time))
if sleep_seconds <= 0:
player.write_log(f"Loop detected! Offending task: '{next_tasks[0]}'")
player.write_log("My next Tasks and there time:\n" + "\n".join(sorted(next_tasks)))
player.write_log("Sleeping until (eRep): {} (sleeping for {}s)".format(
closest_next_time.strftime("%F %T"), sleep_seconds))
seconds_to_sleep = sleep_seconds if sleep_seconds > 0 else 0
player.sleep(seconds_to_sleep)
if __name__ == "__main__":
main()

View File

@ -1,14 +1,16 @@
pip==19.1.1
bumpversion==0.5.3 bumpversion==0.5.3
wheel==0.33.4 coverage==5.0.2
watchdog==0.9.0 edx-sphinx-theme==1.5.0
flake8==3.7.8 flake8==3.7.9
tox==3.13.2 ipython==7.11.1
coverage==4.5.3 isort==4.3.21
Sphinx==2.2.0 pip==19.3.1
twine==2.0.0 PyInstaller==3.6
ipython pytz==2019.3
PyInstaller
pytz==2019.1
requests==2.22.0 requests==2.22.0
edx-sphinx-theme setuptools==44.0.0
Sphinx==2.3.1
tox==3.14.3
twine==3.1.1
watchdog==0.9.0
wheel==0.33.6

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.18.1 current_version = 0.19.4
commit = True commit = True
tag = True tag = True

View File

@ -11,7 +11,7 @@ with open('README.rst') as readme_file:
with open('HISTORY.rst') as history_file: with open('HISTORY.rst') as history_file:
history = history_file.read() history = history_file.read()
requirements = ['pytz>=2019.2', 'requests>=2.22'] requirements = ['pytz==2019.3', 'requests==2.22.0']
setup_requirements = [] setup_requirements = []
@ -27,6 +27,7 @@ setup(
'Natural Language :: English', 'Natural Language :: English',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
], ],
description="Python package for automated eRepublik playing", description="Python package for automated eRepublik playing",
entry_points={}, entry_points={},
@ -42,6 +43,6 @@ setup(
test_suite='tests', test_suite='tests',
tests_require=test_requirements, tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/', url='https://github.com/eeriks/erepublik/',
version='0.18.1', version='0.19.4',
zip_safe=False, zip_safe=False,
) )