Compare commits

...

55 Commits

Author SHA1 Message Date
50f3d291ca Bump version: 0.15.3 → 0.16.0 2019-09-29 09:45:46 +03:00
b03140f2b8 RTD build image url fixed, setup.py typo 2019-09-29 09:42:24 +03:00
fe9a118875 Telegram loop traceback formatting
Fixed double code execution in Citizen.update_citizen_info(None) calling Citizen.update_citizen_info(html) and executing code twice.
2019-09-29 09:40:25 +03:00
17c73c79a7 Trying to find how and where Telegram spam happens 2019-09-28 16:03:31 +03:00
7533608316 Version updates 2019-09-27 13:51:09 +03:00
71a7f55338 Changes in eRepublik html 2019-09-27 11:50:51 +03:00
0ca0f49f92 bugfix 2019-09-27 11:20:00 +03:00
3f1b0018b2 Misplaced telegram queueing 2019-09-27 11:11:18 +03:00
2aaa4aad65 cleanup 2019-09-27 11:03:22 +03:00
b44a3a4b62 Init update 2019-09-27 11:03:02 +03:00
3a7dd9a6fa Init update 2019-09-27 11:02:26 +03:00
630c7cbc76 Was trying to send messages before citizen has been initialized 2019-09-27 10:26:53 +03:00
1ee47dfdcf Telegram integration 2019-09-26 15:53:21 +03:00
ab34135b73 Telegram integration 2019-09-26 15:44:21 +03:00
20bba4b9f9 Telegram integration 2019-09-26 15:42:16 +03:00
8db4ab1f0f Company sorting for wam: Raw factories, food, weapon, house, air, q7...q1, Final factories, food, weapon, house, air, q7...q1 2019-09-25 09:38:27 +03:00
acbf1590d7 If not enough food for wam - buy food and wam 2019-09-25 09:36:42 +03:00
63dd2d4f77 Optimisation and test cleanup 2019-09-05 15:36:01 +03:00
896b1b2432 Bugfix, cleanup and optimisation 2019-09-05 15:01:54 +03:00
60543e02c8 Merge branch 'master' into tests
* master:
  Update citizen.py
  Update citizen.py
2019-09-03 11:56:00 +03:00
b06e9a2933 Merge branch 'master' of github.com:eeriks/erepublik_script
* 'master' of github.com:eeriks/erepublik_script:
  Update citizen.py
  Update citizen.py
2019-09-03 11:55:44 +03:00
4eb96d7f1e Testing should travel and should fight 2019-09-03 11:55:34 +03:00
c8a1d8c8e8 Update citizen.py
If not employee find new job
2019-09-01 19:58:52 +03:00
c85f0417f2 Update citizen.py
Apply for work bugfix
2019-09-01 19:47:17 +03:00
7573f29950 Freshening up documentation 2019-08-28 17:16:46 +03:00
597d27117c Donating money now return status true or false 2019-08-27 11:16:36 +03:00
95570b7c17 Article voting and endorsing 2019-08-27 11:03:08 +03:00
bc4ba671d6 Endpoint method names renamed to better represent urls 2019-08-27 11:02:52 +03:00
088219a60a Endpoint method names renamed to better represent urls 2019-08-27 10:51:57 +03:00
53266b1c94 Merge branch 'master' of github.com:eeriks/erepublik_script 2019-08-27 10:17:34 +03:00
065a0ff3ec bugfix 2019-08-27 10:13:37 +03:00
c8e90ca910 bugfix 2019-08-27 10:13:14 +03:00
bbab84bf5b Update utils.py 2019-08-26 22:42:06 +03:00
fff17469e0 More verbose Citizen class serialization 2019-08-26 10:06:18 +03:00
0d208a8d32 Add timezone to datetime serialization 2019-08-26 10:05:52 +03:00
86004eb81b Don't serialize password nor email 2019-08-26 10:05:22 +03:00
7e05e35ebf More verbose Object representation in json format 2019-08-26 09:47:04 +03:00
411b158487 Citizen.buy_food() now buys 48h worth of food
When doing WAM and fialing because not enough food - buy food
Integrated new RW side chooser
2019-08-26 09:32:12 +03:00
d6e161d815 Bump version: 0.15.2 → 0.15.3 2019-08-24 15:44:22 +03:00
1285e95cec Inventory usage bugfixes 2019-08-24 10:48:04 +03:00
324db2c136 Fix new division numbering 2019-08-24 10:27:31 +03:00
1c6b41e41b Inventory updates and booster usage updates 2019-08-24 10:03:52 +03:00
a52552afb7 Use MyJSONEncoder from classes 2019-08-22 14:33:45 +03:00
28bfdc7b20 Use country ids from constant in utils 2019-08-22 14:33:07 +03:00
de1b734728 Don't check levelup when eating 2019-08-22 14:32:32 +03:00
fa5646ecfd Receive daily order 2019-08-22 14:32:00 +03:00
339392cfb0 Regex bugfix 2019-08-13 15:44:07 +03:00
7ebba458cb Max time timezones 2019-08-12 14:19:52 +03:00
4f436292af Organisation account fetcher - UNSTABLE 2019-08-12 12:54:58 +03:00
416c391d21 Improved levelup energy management 2019-08-11 01:45:49 +03:00
66c53ef985 Typo 2019-08-11 01:44:45 +03:00
3fa7d097fe Found awards switch to ASCII friendlier multiplier char - ✕ -> x
Localize max div end time
2019-08-05 11:55:39 +03:00
6af9675c39 Added gameTokensMarket endpoint 2019-08-05 10:20:36 +03:00
2343a6c6c8 WeeklyChallange end energy saver tweaks 2019-08-05 10:20:19 +03:00
7e56f01a38 Added gameTokensMarket endpoint 2019-08-05 10:13:48 +03:00
17 changed files with 716 additions and 448 deletions

View File

@ -15,7 +15,7 @@ Types of Contributions
Report Bugs Report Bugs
~~~~~~~~~~~ ~~~~~~~~~~~
Report bugs at https://github.com/eeriks/erepublik_script/issues. Report bugs at https://github.com/eeriks/erepublik/issues.
If you are reporting a bug, please include: If you are reporting a bug, please include:
@ -45,7 +45,7 @@ articles, and such.
Submit Feedback Submit Feedback
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
The best way to send feedback is to file an issue at https://github.com/eeriks/erepublik_script/issues. The best way to send feedback is to file an issue at https://github.com/eeriks/erepublik/issues.
If you are proposing a feature: If you are proposing a feature:
@ -62,7 +62,7 @@ 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_script` repo on GitHub.
2. Clone your fork locally:: 2. Clone your fork locally::
$ git clone git@github.com:your_name_here/erepublik_script.git $ git clone git@github.com:your_name_here/erepublik.git
3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::

View File

@ -2,6 +2,16 @@
History History
======= =======
* Telegram notification integration
* Improved serialization to JSON
* When failing to do WAM because of not enough food - buy food
* Buy food buys 48h worth instead of 24h energy
0.15.3 (2019-08-24)
-------------------
* Update after eRepublik changed campaign apis
0.15.0 (2019-07-30) 0.15.0 (2019-07-30)
------------------- -------------------

View File

@ -6,8 +6,8 @@ eRepublik script
.. image:: https://img.shields.io/pypi/v/erepublik.svg .. image:: https://img.shields.io/pypi/v/erepublik.svg
:target: https://pypi.python.org/pypi/erepublik :target: https://pypi.python.org/pypi/erepublik
.. image:: https://readthedocs.org/projects/erepublik_script/badge/?version=latest .. image:: https://readthedocs.org/projects/erepublik/badge/?version=latest
:target: https://erepublik_script.readthedocs.io/en/latest/?badge=latest :target: https://erepublik.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status :alt: Documentation Status
@ -15,7 +15,7 @@ Python package for automated eRepublik playing
* Free software: MIT license * Free software: MIT license
* Documentation: https://erepublik.readthedocs.io. * Documentation: https://erepublik.readthedocs.io/en/latest/
Features Features

View File

@ -33,4 +33,4 @@
"travel_to_fight": true, "travel_to_fight": true,
"wam": true, "wam": true,
"work": true "work": true
} }

View File

@ -23,6 +23,7 @@ import sys
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
import erepublik import erepublik
import edx_theme
# -- General configuration --------------------------------------------- # -- General configuration ---------------------------------------------
@ -32,7 +33,7 @@ import erepublik
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'edx_theme']
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ['_templates']
@ -84,7 +85,8 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
# #
html_theme = 'alabaster' html_theme = 'edx_theme'
html_theme_path = [edx_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a # Theme options are theme-specific and customize the look and feel of a
# theme further. For a list of options available for each theme, see the # theme further. For a list of options available for each theme, see the
@ -158,6 +160,3 @@ texinfo_documents = [
'One line description of project.', 'One line description of project.',
'Miscellaneous'), 'Miscellaneous'),
] ]

View File

@ -32,13 +32,13 @@ You can either clone the public repository:
.. code-block:: console .. code-block:: console
$ git clone git://github.com/eeriks/erepublik_script $ git clone git://github.com/eeriks/erepublik
Or download the `tarball`_: Or download the `tarball`_:
.. code-block:: console .. code-block:: console
$ curl -OL https://github.com/eeriks/erepublik_script/tarball/master $ curl -OL https://github.com/eeriks/erepublik/tarball/master
Once you have a copy of the source, you can install it with: Once you have a copy of the source, you can install it with:
@ -47,5 +47,5 @@ Once you have a copy of the source, you can install it with:
$ python setup.py install $ python setup.py install
.. _Github repo: https://github.com/eeriks/erepublik_script .. _Github repo: https://github.com/eeriks/erepublik
.. _tarball: https://github.com/eeriks/erepublik_script/tarball/master .. _tarball: https://github.com/eeriks/erepublik/tarball/master

View File

@ -1,36 +1,36 @@
@ECHO OFF @ECHO OFF
pushd %~dp0 pushd %~dp0
REM Command file for Sphinx documentation REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" ( if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=python -msphinx set SPHINXBUILD=python -msphinx
) )
set SOURCEDIR=. set SOURCEDIR=.
set BUILDDIR=_build set BUILDDIR=_build
set SPHINXPROJ=erepublik_script set SPHINXPROJ=erepublik
if "%1" == "" goto help if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL %SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 ( if errorlevel 9009 (
echo. echo.
echo.The Sphinx module was not found. Make sure you have Sphinx installed, echo.The Sphinx module was not found. Make sure you have Sphinx installed,
echo.then set the SPHINXBUILD environment variable to point to the full echo.then set the SPHINXBUILD environment variable to point to the full
echo.path of the 'sphinx-build' executable. Alternatively you may add the echo.path of the 'sphinx-build' executable. Alternatively you may add the
echo.Sphinx directory to PATH. echo.Sphinx directory to PATH.
echo. echo.
echo.If you don't have Sphinx installed, grab it from echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/ echo.http://sphinx-doc.org/
exit /b 1 exit /b 1
) )
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end goto end
:help :help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end :end
popd popd

View File

@ -4,4 +4,7 @@ Usage
To use eRepublik script in a project:: To use eRepublik script in a project::
import erepublik from erepublik import Citizen
player = Citizen('email@domain.com', 'password')
player.update_all()

View File

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

View File

@ -14,8 +14,6 @@ from erepublik import classes, utils
class Citizen(classes.CitizenAPI): class Citizen(classes.CitizenAPI):
url: str = "https://www.erepublik.com/en"
division = 0 division = 0
all_battles: Dict[int, classes.Battle] = None all_battles: Dict[int, classes.Battle] = None
@ -28,18 +26,14 @@ class Citizen(classes.CitizenAPI):
food = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0} food = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0}
inventory = {"used": 0, "total": 0} inventory = {"used": 0, "total": 0}
boosters = { boosters = {
"100_damageBoosters_5_7200": 0, # 2h × 60min × 60sec, +50% 100: {
"100_damageBoosters_5_28800": 0, # 8h × 60min × 60sec, +50% }, 50: {
"100_damageBoosters_5_86400": 0, # 1d × 24h × 60min × 60sec, +50% }
"100_speedBoosters_1_180": 0, # 3min × 60sec, ×2 kills
"100_speedBoosters_2_600": 0, # 10min × 60sec, ×5 kills
"100_catchupBoosters_30_60": 0, # 60sec, +30%
} }
candies_normal = 0 eb_normal = 0
candies_double = 0 eb_double = 0
candies_small = 0 eb_small = 0
tickets = 0
work_units = 0 work_units = 0
ot_points = 0 ot_points = 0
@ -51,13 +45,13 @@ class Citizen(classes.CitizenAPI):
r: requests.Response r: requests.Response
reporter: classes.Reporter reporter: classes.Reporter
token = ""
name = "Not logged in!" name = "Not logged in!"
debug = False debug = False
__registered = False __registered = False
logged_in = False logged_in = False
telegram = None
def __init__(self, email: str = "", password: str = ""): def __init__(self, email: str = "", password: str = "", auto_login: bool = True):
super().__init__() super().__init__()
self.commit_id = utils.COMMIT_ID self.commit_id = utils.COMMIT_ID
self.config = classes.Config() self.config = classes.Config()
@ -69,10 +63,21 @@ class Citizen(classes.CitizenAPI):
self.my_companies = classes.MyCompanies() self.my_companies = classes.MyCompanies()
self.set_debug(True) self.set_debug(True)
self.reporter = classes.Reporter() self.reporter = classes.Reporter()
self.get_csrf_token() self.telegram = classes.TelegramBot()
self.update_citizen_info()
self.reporter.do_init(self.name, email, self.details.citizen_id)
self.stop_threads = threading.Event() self.stop_threads = threading.Event()
if auto_login:
self.login()
def login(self):
self.get_csrf_token()
self.update_citizen_info()
self.reporter.do_init(self.name, self.config.email, self.details.citizen_id)
if self.config.telegram:
self.telegram.do_init(self.config.telegram_chat_id or 620981703,
self.config.telegram_token or "864251270:AAFzZZdjspI-kIgJVk4gF3TViGFoHnf8H4o",
"" if self.config.telegram_chat_id or self.config.telegram_token else self.name)
self.telegram.send_message("*Started* {:%F %T}".format(utils.now()))
self.__last_full_update = utils.good_timedelta(self.now, - datetime.timedelta(minutes=5)) self.__last_full_update = utils.good_timedelta(self.now, - datetime.timedelta(minutes=5))
def write_log(self, *args, **kwargs): def write_log(self, *args, **kwargs):
@ -82,6 +87,8 @@ class Citizen(classes.CitizenAPI):
utils.write_silent_log(*args, **kwargs) utils.write_silent_log(*args, **kwargs)
def sleep(self, seconds: int): def sleep(self, seconds: int):
if seconds < 0:
seconds = 0
if self.config.interactive: if self.config.interactive:
utils.interactive_sleep(seconds) utils.interactive_sleep(seconds)
else: else:
@ -92,7 +99,10 @@ class Citizen(classes.CitizenAPI):
@property @property
def __dict__(self): def __dict__(self):
ret = super().__dict__.copy() ret = {}
for key in dir(self):
if not key.startswith('_') and not callable(getattr(self, key)):
ret[key] = getattr(self, key)
ret.pop('reporter', None) ret.pop('reporter', None)
ret.pop('stop_threads', None) ret.pop('stop_threads', None)
ret.pop('_Citizen__last_war_update_data', None) ret.pop('_Citizen__last_war_update_data', None)
@ -256,10 +266,12 @@ class Citizen(classes.CitizenAPI):
continue continue
if data: if data:
msgs = ["{count} {kind}, totaling {} {currency}\n" msgs = ["{count} x {kind}, totaling {} {currency}\n"
"{about}".format(d["count"] * d["reward"], **d) for d in data.values()] "{about}".format(d["count"] * d["reward"], **d) for d in data.values()]
self.write_log("Found awards: {}".format("\n".join(msgs))) msgs = "\n".join(msgs)
self.telegram.report_medal(msgs)
self.write_log("Found awards: {}".format(msgs))
for info in data.values(): for info in data.values():
self.reporter.report_action("NEW_MEDAL", info) self.reporter.report_action("NEW_MEDAL", info)
@ -268,6 +280,7 @@ class Citizen(classes.CitizenAPI):
level = levelup.group(1) level = levelup.group(1)
msg = "Level up! Current level {}".format(level) msg = "Level up! Current level {}".format(level)
self.write_log(msg) self.write_log(msg)
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):
@ -290,8 +303,8 @@ class Citizen(classes.CitizenAPI):
""" """
if html is None: if html is None:
self._get_main() self._get_main()
html = self.r.text return
ugly_js = re.search(r"promotions: (\[{?.*}?]),\s+", html).group(1) ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1)
promos = loads(utils.normalize_html_json(ugly_js)) promos = loads(utils.normalize_html_json(ugly_js))
self.promos = {k: v for k, v in (self.promos.items() if self.promos else {}) if v > self.now} self.promos = {k: v for k, v in (self.promos.items() if self.promos else {}) if v > self.now}
send_mail = False send_mail = False
@ -335,6 +348,8 @@ class Citizen(classes.CitizenAPI):
self.energy.limit = citizen.get("energyToRecover", 0) self.energy.limit = citizen.get("energyToRecover", 0)
self.energy.recovered = citizen.get("energy", 0) self.energy.recovered = citizen.get("energy", 0)
self.energy.recoverable = citizen.get("energyFromFoodRemaining", 0) self.energy.recoverable = citizen.get("energyFromFoodRemaining", 0)
if self.energy.is_energy_full:
self.telegram.report_full_energy(self.energy.available, self.energy.limit, self.energy.interval)
self.details.current_region = citizen.get("regionLocationId", 0) self.details.current_region = citizen.get("regionLocationId", 0)
self.details.current_country = citizen.get("countryLocationId", 0) # country where citizen is located self.details.current_country = citizen.get("countryLocationId", 0) # country where citizen is located
@ -345,9 +360,8 @@ class Citizen(classes.CitizenAPI):
self.details.xp = citizen.get("currentExperiencePoints", 0) self.details.xp = citizen.get("currentExperiencePoints", 0)
self.details.daily_task_done = citizen.get("dailyTasksDone", False) self.details.daily_task_done = citizen.get("dailyTasksDone", False)
self.details.daily_task_reward = citizen.get("hasReward", False) self.details.daily_task_reward = citizen.get("hasReward", False)
# if citizen.get("dailyOrderDone", False) and not citizen.get("hasDailyOrderReward", False): if citizen.get("dailyOrderDone", False) and not citizen.get("hasDailyOrderReward", False):
# self.post_military_group_missions(self.token) self._post_military_group_missions()
# self.get_citizen_daily_assistant()
self.details.next_pp.sort() self.details.next_pp.sort()
for id_, skill in citizen.get("mySkills", {}).items(): for id_, skill in citizen.get("mySkills", {}).items():
@ -373,7 +387,7 @@ class Citizen(classes.CitizenAPI):
return resp_data return resp_data
def update_job_info(self): def update_job_info(self):
ot = self._get_job_data().json().get("overTime", {}) ot = self._get_main_job_data().json().get("overTime", {})
if ot: if ot:
self.my_companies.next_ot_time = utils.localize_timestamp(int(ot.get("nextOverTime", 0))) self.my_companies.next_ot_time = utils.localize_timestamp(int(ot.get("nextOverTime", 0)))
self.ot_points = ot.get("points", 0) self.ot_points = ot.get("points", 0)
@ -391,39 +405,71 @@ class Citizen(classes.CitizenAPI):
self.my_companies.update_holding_companies() self.my_companies.update_holding_companies()
def update_inventory(self) -> dict: def update_inventory(self) -> dict:
self.food.update({"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0})
self.eb_small = 0
j = self._get_economy_inventory_items().json() j = self._get_economy_inventory_items().json()
active_items = {}
if j.get("inventoryItems", {}).get("activeEnhancements", {}).get("items", {}):
for item in j.get("inventoryItems", {}).get("activeEnhancements", {}).get("items", {}).values():
active_items.update({item['name']: item['active']['time_left']})
final_items = {}
if j.get("inventoryItems", {}).get("finalProducts", {}).get("items", {}):
for item in j.get("inventoryItems", {}).get("finalProducts", {}).get("items", {}).values():
name = item['name']
if item.get('type') == 'damageBoosters':
if item['quality'] == 5:
self.boosters[50].update({item['duration']: item['amount']})
elif item['quality'] == 10:
self.boosters[100].update({item['duration']: item['amount']})
delta = item['duration']
if delta // 3600:
name += f" {delta // 3600}h"
if delta // 60 % 60:
name += f" {delta // 60 % 60}m"
if delta % 60:
name += f" {delta % 60}s"
elif item['industryId'] == 1:
amount = item['amount']
q = item['quality']
if 1 <= q <= 7:
self.food.update({f"q{q}": item['amount']})
else:
if q == 10:
self.eb_normal = amount
elif q == 11:
self.eb_double = amount
elif q == 13:
self.eb_small += amount
elif q == 14:
self.eb_small += amount
elif item['industryId'] == 3 and item['quality'] == 5:
self.ot_points = item['amount']
elif item['industryId'] == 4 and item['quality'] == 100:
self.ot_points = item['amount']
if item['amount']:
final_items.update({name: item['amount']})
raw_materials = {}
if j.get("inventoryItems", {}).get("rawMaterials", {}).get("items", {}):
for item in j.get("inventoryItems", {}).get("rawMaterials", {}).get("items", {}).values():
name = item['name']
if item['isPartial']:
continue
if item['amount']:
raw_materials.update({name: item['amount']})
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")})
final = j.get("inventoryItems").get("finalProducts").get("items") inventory = dict(items=dict(active=active_items, final=final_items, raw=raw_materials), status=self.inventory)
self.food.update({
"q1": final.get("1_1", {"amount": 0}).get("amount", 0),
"q2": final.get("1_2", {"amount": 0}).get("amount", 0),
"q3": final.get("1_3", {"amount": 0}).get("amount", 0),
"q4": final.get("1_4", {"amount": 0}).get("amount", 0),
"q5": final.get("1_5", {"amount": 0}).get("amount", 0),
"q6": final.get("1_6", {"amount": 0}).get("amount", 0),
"q7": final.get("1_7", {"amount": 0}).get("amount", 0),
})
self.boosters.update({
"100_damageBoosters_5_7200": final.get("100_damageBoosters_5_7200", {"amount": 0}).get("amount", 0),
"100_damageBoosters_5_28800": final.get("100_damageBoosters_5_28800", {"amount": 0}).get("amount", 0),
"100_damageBoosters_5_86400": final.get("100_damageBoosters_5_86400", {"amount": 0}).get("amount", 0),
"100_speedBoosters_1_180": final.get("100_speedBoosters_1_180", {"amount": 0}).get("amount", 0),
"100_speedBoosters_2_600": final.get("100_speedBoosters_2_600", {"amount": 0}).get("amount", 0),
"100_catchupBoosters_30_60": final.get("100_catchupBoosters_30_60", {"amount": 0}).get("amount", 0),
})
self.candies_normal = final.get("1_10", {"amount": 0}).get("amount", 0)
self.candies_double = final.get("1_11", {"amount": 0}).get("amount", 0)
self.candies_small = final.get("1_12", {"amount": 0}).get("amount", 0)
self.ot_points = final.get("4_100", {"amount": 0}).get("amount", 0)
self.tickets = final.get("3_5", {"amount": 0}).get("amount", 0)
self.food["total"] = sum([self.food[q] * utils.FOOD_ENERGY[q] for q in utils.FOOD_ENERGY]) self.food["total"] = sum([self.food[q] * utils.FOOD_ENERGY[q] for q in utils.FOOD_ENERGY])
return j return inventory
def update_weekly_challenge(self): def update_weekly_challenge(self):
data = self._get_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 = []
for reward in data.get("rewards", {}).get("normal", {}): for reward in data.get("rewards", {}).get("normal", {}):
@ -431,7 +477,7 @@ class Citizen(classes.CitizenAPI):
if status == "rewarded": if status == "rewarded":
continue continue
elif status == "completed": elif status == "completed":
self._post_weekly_challenge_reward(reward.get("id", 0)) self._post_main_weekly_challenge_reward(reward.get("id", 0))
elif reward.get("icon", "") == "energy_booster": elif reward.get("icon", "") == "energy_booster":
pps = re.search(r"Reach (\d+) Prestige Points to unlock the following reward: \+1 Energy", pps = re.search(r"Reach (\d+) Prestige Points to unlock the following reward: \+1 Energy",
reward.get("tooltip", "")) reward.get("tooltip", ""))
@ -462,29 +508,22 @@ class Citizen(classes.CitizenAPI):
""" """
Try to eat food Try to eat food
""" """
self.update_citizen_info() if self.food["total"] > self.energy.interval:
self.update_inventory() if self.energy.limit - self.energy.recovered > self.energy.interval or not self.energy.recoverable % 2:
if self.details.xp_till_level_up > (self.energy.recovered - 50) // 10: self._eat("blue")
if self.food["total"] > self.energy.interval:
if self.energy.limit - self.energy.recovered > self.energy.interval or not self.energy.recoverable % 2:
self._eat("blue")
else:
self.write_log("I don't want to eat right now!")
else: else:
self.write_log("I'm out of food! But I'll try to buy some!\n{}".format(self.food)) self.write_log("I don't want to eat right now!")
self.buy_food()
self.update_inventory()
if self.food["total"] > self.energy.interval:
self.eat()
else:
self.write_log("I failed to buy food")
else: else:
self.write_log("I'm not allowed to eat because I have levelup coming up!") self.write_log("I'm out of food! But I'll try to buy some!\n{}".format(self.food))
self.buy_food()
if self.food["total"] > self.energy.interval:
self.eat()
else:
self.write_log("I failed to buy food")
self.write_log(self.health_info) self.write_log(self.health_info)
def eat_ebs(self): def eat_ebs(self):
self.write_log("Eating energy bar") self.write_log("Eating energy bar")
self.update_citizen_info()
if self.energy.recoverable: if self.energy.recoverable:
self._eat("blue") self._eat("blue")
self._eat("orange") self._eat("orange")
@ -504,16 +543,15 @@ class Citizen(classes.CitizenAPI):
if "q{}".format(q) in self.food: if "q{}".format(q) in self.food:
self.food["q{}".format(q)] -= amount self.food["q{}".format(q)] -= amount
elif q == "10": elif q == "10":
self.candies_normal -= amount self.eb_normal -= amount
elif q == "11": elif q == "11":
self.candies_double -= amount self.eb_double -= amount
elif q == "12": elif q == "12":
self.candies_small -= amount self.eb_small -= amount
return response return response
@property @property
def health_info(self): def health_info(self):
self.update_citizen_info()
ret = "{}/{} + {}, {}hp/6m. {}xp until level up".format( ret = "{}/{} + {}, {}hp/6m. {}xp until level up".format(
self.energy.recovered, self.energy.recovered,
self.energy.limit, self.energy.limit,
@ -587,12 +625,19 @@ class Citizen(classes.CitizenAPI):
ally_battles_ground: List[int] = [] ally_battles_ground: List[int] = []
other_battles_air: List[int] = [] other_battles_air: List[int] = []
other_battles_ground: List[int] = [] other_battles_ground: List[int] = []
ret_battles = []
for bid, battle in sorted(self.all_battles.items(), key=lambda b: b[1].start if sort_by_time else b[0], for bid, battle in sorted(self.all_battles.items(), key=lambda b: b[1].start if sort_by_time else b[0],
reverse=sort_by_time): reverse=sort_by_time):
battle_sides = [battle.invader.id, battle.defender.id] battle_sides = [battle.invader.id, battle.defender.id]
# Previous battles
if self.__last_war_update_data.get("citizen_contribution"):
battle_id = self.__last_war_update_data.get("citizen_contribution")[0].get("battle_id", 0)
ret_battles.append(battle_id)
# CS Battles # CS Battles
if 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) cs_battles_ground.append(battle.id)
else: else:
@ -624,11 +669,6 @@ class Citizen(classes.CitizenAPI):
else: else:
other_battles_air.append(battle.id) other_battles_air.append(battle.id)
ret_battles = []
if self.__last_war_update_data.get("citizen_contribution"):
battle_id = self.__last_war_update_data.get("citizen_contribution")[0].get("battle_id", 0)
ret_battles.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 +
ally_battles_air + ally_battles_ground + ally_battles_air + ally_battles_ground +
@ -701,6 +741,8 @@ class Citizen(classes.CitizenAPI):
break break
def fight(self, battle_id: int, side_id: int, is_air: bool = False, count: int = None): def fight(self, battle_id: int, side_id: int, is_air: bool = False, count: int = None):
if not is_air and self.config.boosters:
self.activate_dmg_booster()
data = dict(sideId=side_id, battleId=battle_id) data = dict(sideId=side_id, battleId=battle_id)
error_count = 0 error_count = 0
ok_to_fight = True ok_to_fight = True
@ -773,6 +815,8 @@ class Citizen(classes.CitizenAPI):
self.resign() self.resign()
self.find_new_job() self.find_new_job()
else: else:
if r.json().get('message') == 'employee':
self.find_new_job()
self.reporter.report_action("WORK_OT", r.json()) self.reporter.report_action("WORK_OT", r.json())
elif self.energy.food_fights < 1 and self.ot_points >= 24: elif self.energy.food_fights < 1 and self.ot_points >= 24:
self._eat("blue") self._eat("blue")
@ -790,6 +834,8 @@ class Citizen(classes.CitizenAPI):
js = response.json() js = response.json()
good_msg = ["already_worked", "captcha"] good_msg = ["already_worked", "captcha"]
if not js.get("status") and not js.get("message") in good_msg: if not js.get("status") and not js.get("message") in good_msg:
if js.get('message') == 'employee':
self.find_new_job()
self.update_citizen_info() self.update_citizen_info()
self.work() self.work()
else: else:
@ -797,15 +843,14 @@ class Citizen(classes.CitizenAPI):
else: else:
self._eat("blue") self._eat("blue")
if self.energy.food_fights < 1: if self.energy.food_fights < 1:
large = max(self.energy.reference_time, self.now) seconds = (self.energy.reference_time - self.now).total_seconds()
small = min(self.energy.reference_time, self.now) self.write_log("I don't have energy to work. Will sleep for {}s".format(seconds))
self.write_log("I don't have energy to work. Will sleep for {}s".format((large - small).seconds)) self.sleep(seconds)
self.sleep(int((large - small).total_seconds()))
self._eat("blue") self._eat("blue")
self.work() self.work()
def train(self): def train(self):
r = self._get_training_grounds_json() r = self._get_main_training_grounds_json()
tg_json = r.json() tg_json = r.json()
self.details.gold = tg_json["page_details"]["gold"] self.details.gold = tg_json["page_details"]["gold"]
self.tg_contract = {"free_train": tg_json["hasFreeTrain"]} self.tg_contract = {"free_train": tg_json["hasFreeTrain"]}
@ -955,7 +1000,9 @@ class Citizen(classes.CitizenAPI):
break break
else: else:
return self._do_wam_and_employee_work(wam_holding_id, employee_companies) return self._do_wam_and_employee_work(wam_holding_id, employee_companies)
elif response.get("message") == "not_enough_health_food":
self.buy_food()
return self._do_wam_and_employee_work(wam_holding_id, employee_companies)
else: else:
self.write_log("I was not able to wam and or employ because:\n{}".format(response)) self.write_log("I was not able to wam and or employ because:\n{}".format(response))
wam_count = self.my_companies.get_total_wam_count() wam_count = self.my_companies.get_total_wam_count()
@ -966,7 +1013,7 @@ class Citizen(classes.CitizenAPI):
def sell_produced_product(self, kind: str, quality: int = 1, amount: int = 0): def sell_produced_product(self, kind: str, quality: int = 1, amount: int = 0):
if not amount: if not amount:
inv_resp = self.update_inventory() inv_resp = self._get_economy_inventory_items().json()
category = "rawMaterials" if kind.endswith("Raw") else "finalProducts" category = "rawMaterials" if kind.endswith("Raw") else "finalProducts"
item = "{}_{}".format(self.available_industries[kind], quality) item = "{}_{}".format(self.available_industries[kind], quality)
amount = inv_resp.get("inventoryItems").get(category).get("items").get(item).get("amount", 0) amount = inv_resp.get("inventoryItems").get(category).get("items").get(item).get("amount", 0)
@ -1011,7 +1058,7 @@ class Citizen(classes.CitizenAPI):
return True return True
def travel_to_region(self, region_id: int) -> bool: def travel_to_region(self, region_id: int) -> bool:
data = self._post_travel_data(region_id=region_id).json() data = self._post_main_travel_data(region_id=region_id).json()
if data.get('alreadyInRegion'): if data.get('alreadyInRegion'):
return True return True
else: else:
@ -1022,7 +1069,7 @@ class Citizen(classes.CitizenAPI):
return False return False
def travel_to_country(self, country_id: int) -> bool: def travel_to_country(self, country_id: int) -> bool:
data = self._post_travel_data(countryId=country_id, check="getCountryRegions").json() data = self._post_main_travel_data(countryId=country_id, check="getCountryRegions").json()
regs = [] regs = []
if data.get('regions'): if data.get('regions'):
@ -1038,7 +1085,7 @@ class Citizen(classes.CitizenAPI):
return False return False
def travel_to_holding(self, holding_id: int) -> bool: def travel_to_holding(self, holding_id: int) -> bool:
data = self._post_travel_data(holdingId=holding_id).json() data = self._post_main_travel_data(holdingId=holding_id).json()
if data.get('alreadyInRegion'): if data.get('alreadyInRegion'):
return True return True
else: else:
@ -1071,15 +1118,15 @@ class Citizen(classes.CitizenAPI):
"toCountryId": country_id, "toCountryId": country_id,
"inRegionId": region_id, "inRegionId": region_id,
} }
return self._post_travel("moveAction", **data) return self._post_main_travel("moveAction", **data)
def get_travel_regions(self, holding_id: int = 0, battle_id: int = 0, country_id: int = 0 def get_travel_regions(self, holding_id: int = 0, battle_id: int = 0, country_id: int = 0
) -> Union[List[Any], Dict[str, Dict[str, Any]]]: ) -> Union[List[Any], Dict[str, Dict[str, Any]]]:
d = self._post_travel_data(holdingId=holding_id, battleId=battle_id, countryId=country_id).json() d = self._post_main_travel_data(holdingId=holding_id, battleId=battle_id, countryId=country_id).json()
return d.get('regions', []) return d.get('regions', [])
def get_travel_countries(self) -> List[int]: def get_travel_countries(self) -> List[int]:
response_json = self._post_travel_data().json() response_json = self._post_main_travel_data().json()
return_list = [] return_list = []
for country_data in response_json['countries'].values(): for country_data in response_json['countries'].values():
if country_data['currentRegions']: if country_data['currentRegions']:
@ -1087,20 +1134,20 @@ class Citizen(classes.CitizenAPI):
return return_list return return_list
def parse_notifications(self, page: int = 1) -> list: def parse_notifications(self, page: int = 1) -> list:
community = self._get_notifications_ajax_community(page).json() community = self._get_main_notifications_ajax_community(page).json()
system = self._get_notifications_ajax_system(page).json() system = self._get_main_notifications_ajax_system(page).json()
return community['alertsList'] + system['alertsList'] return community['alertsList'] + system['alertsList']
def delete_notifications(self): def delete_notifications(self):
response = self._get_notifications_ajax_community().json() response = self._get_main_notifications_ajax_community().json()
while response['totalAlerts']: while response['totalAlerts']:
self._post_messages_alert([_['id'] for _ in response['alertList']]) self._post_main_messages_alert([_['id'] for _ in response['alertList']])
response = self._get_notifications_ajax_community().json() response = self._get_main_notifications_ajax_community().json()
response = self._get_notifications_ajax_system().json() response = self._get_main_notifications_ajax_system().json()
while response['totalAlerts']: while response['totalAlerts']:
self._post_messages_alert([_['id'] for _ in response['alertList']]) self._post_main_messages_alert([_['id'] for _ in response['alertList']])
response = self._get_notifications_ajax_system().json() response = self._get_main_notifications_ajax_system().json()
def collect_weekly_reward(self): def collect_weekly_reward(self):
self.update_weekly_challenge() self.update_weekly_challenge()
@ -1108,7 +1155,7 @@ class Citizen(classes.CitizenAPI):
def collect_daily_task(self) -> None: def collect_daily_task(self) -> None:
self.update_citizen_info() self.update_citizen_info()
if self.details.daily_task_done and not self.details.daily_task_reward: if self.details.daily_task_done and not self.details.daily_task_reward:
self._post_daily_task_reward() self._post_main_daily_task_reward()
def send_mail_to_owner(self) -> None: def send_mail_to_owner(self) -> None:
if not self.details.citizen_id == 1620414: if not self.details.citizen_id == 1620414:
@ -1147,7 +1194,7 @@ class Citizen(classes.CitizenAPI):
countries = [country_id] countries = [country_id]
else: else:
good_countries = self.get_travel_countries() good_countries = self.get_travel_countries()
countries = {cid for cid in self.countries.keys() if cid in good_countries} countries = {cid for cid in utils.COUNTRIES.keys() if cid in good_countries}
start_dt = self.now start_dt = self.now
iterable = [countries, product or items, [quality] if quality else range(1, 8)] iterable = [countries, product or items, [quality] if quality else range(1, 8)]
@ -1184,10 +1231,9 @@ class Citizen(classes.CitizenAPI):
ret = items ret = items
return ret return ret
def buy_food(self) -> None: def buy_food(self):
self.update_money()
hp_per_quality = {"q1": 2, "q2": 4, "q3": 6, "q4": 8, "q5": 10, "q6": 12, "q7": 20} hp_per_quality = {"q1": 2, "q2": 4, "q3": 6, "q4": 8, "q5": 10, "q6": 12, "q7": 20}
hp_needed = 24 * self.energy.interval * 10 - self.food["total"] hp_needed = 48 * self.energy.interval * 10 - self.food["total"]
local_offers = self.get_market_offers(country_id=self.details.current_country, product="food") local_offers = self.get_market_offers(country_id=self.details.current_country, product="food")
cheapest_q, cheapest = sorted(local_offers.items(), key=lambda v: v[1]["price"] / hp_per_quality[v[0]])[0] cheapest_q, cheapest = sorted(local_offers.items(), key=lambda v: v[1]["price"] / hp_per_quality[v[0]])[0]
@ -1229,28 +1275,29 @@ class Citizen(classes.CitizenAPI):
value="New amount {o.cc}cc, {o.gold}g".format(o=self.details)) value="New amount {o.cc}cc, {o.gold}g".format(o=self.details))
return not response.json().get("error", False) return not response.json().get("error", False)
def activate_dmg_booster(self, battle_id: int): def activate_dmg_booster(self):
if self.config.boosters: if self.config.boosters:
duration = 0 inventory = self.update_inventory()
if self.boosters.get("100_damageBoosters_5_600", 0) > 0: if not ("+100% Damage" in inventory['items']['active'] or "+50% Damage" in inventory['items']['active']):
duration = 600 duration = 0
elif self.boosters.get("100_damageBoosters_5_7200", 0) > 1: for length, amount in self.boosters[50].items():
duration = 7200 if amount > 1:
elif self.boosters.get("100_damageBoosters_5_28800", 0) > 2: duration = length
duration = 28800 break
elif self.boosters.get("100_damageBoosters_5_86400", 0) > 2: if duration:
duration = 86400 self._post_economy_activate_booster(5, duration, "damage")
self._post_fight_activate_booster(battle_id, 5, duration, "damage")
def activate_battle_effect(self, battle_id: int, kind: str) -> Response: def activate_battle_effect(self, battle_id: int, kind: str) -> Response:
return self._post_activate_battle_effect(battle_id, kind, self.details.citizen_id) return self._post_main_activate_battle_effect(battle_id, kind, self.details.citizen_id)
def activate_pp_booster(self, battle_id: int) -> Response: def activate_pp_booster(self, battle_id: int) -> Response:
return self._post_fight_activate_booster(battle_id, 1, 180, "prestige_points") return self._post_military_fight_activate_booster(battle_id, 1, 180, "prestige_points")
def donate_money(self, citizen_id: int = 1620414, amount: float = 0.0, currency: int = 62) -> Response: def donate_money(self, citizen_id: int = 1620414, amount: float = 0.0, currency: int = 62) -> bool:
""" currency: gold = 62, cc = 1 """ """ currency: gold = 62, cc = 1 """
return self._post_economy_donate_money_action(citizen_id, amount, currency) resp = self._post_economy_donate_money_action(citizen_id, amount, currency)
r = re.search('You do not have enough money in your account to make this donation', resp.text)
return not bool(r)
def donate_items(self, citizen_id: int = 1620414, amount: int = 0, industry_id: int = 1, def donate_items(self, citizen_id: int = 1620414, amount: int = 0, industry_id: int = 1,
quality: int = 1) -> Response: quality: int = 1) -> Response:
@ -1268,32 +1315,30 @@ class Citizen(classes.CitizenAPI):
for notification in self.parse_notifications(): for notification in self.parse_notifications():
don_id = re.search(r"erepublik.functions.acceptRejectDonation\(\"accept\", (\d+)\)", notification) don_id = re.search(r"erepublik.functions.acceptRejectDonation\(\"accept\", (\d+)\)", notification)
if don_id: if don_id:
self._get_money_donation_accept(int(don_id.group(1))) self._get_main_money_donation_accept(int(don_id.group(1)))
self.sleep(5) self.sleep(5)
def reject_money_donations(self) -> int: def reject_money_donations(self) -> int:
r = self._get_notifications_ajax_system() r = self._get_main_notifications_ajax_system()
count = 0 count = 0
donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text) donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text)
while donation_ids: while donation_ids:
for don_id in donation_ids: for don_id in donation_ids:
self._get_money_donation_reject(int(don_id)) self._get_main_money_donation_reject(int(don_id))
count += 1 count += 1
self.sleep(5) self.sleep(5)
r = self._get_notifications_ajax_system() r = self._get_main_notifications_ajax_system()
donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text) donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text)
return count return count
def _rw_choose_side(self, battle_id: int, side_id: int) -> Response: def _rw_choose_side(self, battle_id: int, side_id: int) -> Response:
return self._get_battlefield_choose_side(battle_id, side_id) return self._post_main_battlefield_travel(side_id, battle_id)
def should_travel_to_fight(self) -> bool: def should_travel_to_fight(self) -> bool:
ret = False ret = False
if self.config.always_travel: if self.config.always_travel:
return True
if self.should_do_levelup: # Do levelup
ret = True ret = True
elif self.config.all_in and self.energy.available > self.energy.limit * 2 - self.energy.interval * 3: elif self.should_do_levelup: # Do levelup
ret = True ret = True
# Get to next Energy +1 # Get to next Energy +1
elif self.next_reachable_energy and self.config.next_energy: elif self.next_reachable_energy and self.config.next_energy:
@ -1313,7 +1358,7 @@ class Citizen(classes.CitizenAPI):
if self.is_levelup_reachable: if self.is_levelup_reachable:
log_msg = "Level up" log_msg = "Level up"
if self.should_do_levelup: if self.should_do_levelup:
count = (self.energy.limit * 3) // 10 count = (self.energy.available + self.energy.limit) // 10
force_fight = True force_fight = True
else: else:
self.write_log("Waiting for fully recovered energy before leveling up.", False) self.write_log("Waiting for fully recovered energy before leveling up.", False)
@ -1339,8 +1384,7 @@ class Citizen(classes.CitizenAPI):
log_msg = "Fighting all-in. Doing %i hits" % count log_msg = "Fighting all-in. Doing %i hits" % count
# All-in for AIR battles # All-in for AIR battles
elif all([self.config.air, self.config.all_in, elif all([self.config.air, self.config.all_in, self.energy.available >= self.energy.limit]):
self.energy.available >= self.energy.limit]):
count = self.energy.food_fights count = self.energy.food_fights
log_msg = "Fighting all-in in AIR. Doing %i hits" % count log_msg = "Fighting all-in in AIR. Doing %i hits" % count
@ -1353,24 +1397,25 @@ class Citizen(classes.CitizenAPI):
elif self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2: elif self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2:
count = self.energy.interval count = self.energy.interval
log_msg = "Fighting for 1h energy. Doing %i hits" % count log_msg = "Fighting for 1h energy. Doing %i hits" % count
force_fight = True
if count > 0 and not force_fight: if count > 0 and not force_fight:
if self.my_companies.ff_lockdown and self.details.pp > 75: if self.energy.food_fights - self.my_companies.ff_lockdown < count:
if count - self.my_companies.ff_lockdown > 0: log_msg = (f"Fight count modified (old count: {count} | FF: {self.energy.food_fights} | "
log_msg = ("Fight count modified (old count: {} | FF: {} | " f"WAM ff_lockdown: {self.my_companies.ff_lockdown} |"
"WAM ff_lockdown: {} | New count: {})").format( f" New count: {count - self.my_companies.ff_lockdown})")
count, self.energy.food_fights, self.my_companies.ff_lockdown, count -= self.my_companies.ff_lockdown
count - self.my_companies.ff_lockdown)
count -= self.my_companies.ff_lockdown
else:
count = 0
if count <= 0: if count <= 0:
count = 0
log_msg = "Not fighting because WAM needs {} food fights".format(self.my_companies.ff_lockdown) log_msg = "Not fighting because WAM needs {} food fights".format(self.my_companies.ff_lockdown)
if self.max_time_till_full_ff > self.time_till_week_change: if self.max_time_till_full_ff > self.time_till_week_change:
max_count = int((self.time_till_week_change - self.time_till_full_ff).total_seconds()) // 60 max_count = int((self.time_till_week_change -
log_msg = "End for Weekly challenge is near ({} | {})".format(max_count, count) self.time_till_full_ff).total_seconds()) // 360 * self.energy.interval
count = count if max_count > count else max_count log_msg = "End for Weekly challenge is near (Recoverable until WC end {}hp | want to do {}hits)".format(
max_count, count)
max_usable_energy = max_count - self.energy.limit * 2
count = count if max_usable_energy > count * 10 else max_usable_energy // 10
if not silent: if not silent:
self.write_log(log_msg, False) self.write_log(log_msg, False)
@ -1437,17 +1482,11 @@ class Citizen(classes.CitizenAPI):
If Energy limit >= xp till levelup * 10 If Energy limit >= xp till levelup * 10
:return: bool :return: bool
""" """
if self.energy.limit - self.energy.interval <= self.energy.recoverable: return (self.energy.recovered >= self.details.xp_till_level_up * 10 and # can reach next level
if self.is_levelup_reachable: self.energy.recoverable + 2 * self.energy.interval >= self.energy.limit) # can do max amount of dmg
# 45xp till levelup, 50hp/6min, 500+500/500 energy = 500+450 >= 1000
# 40xp till levelup, 50hp/6min, 450+500/500 energy = 500+400 >= 950
# 25xp till levelup, 50hp/6min, 300+500/500 energy = 500+250 >= 800
# 25xp till levelup, 50hp/6min, 300+200/500 energy = 500+250 >= 500
return self.energy.limit + self.details.xp_till_level_up * 10 <= self.energy.available
return False
def get_article_comments(self, article_id: int = 2645676, page_id: int = 1) -> Response: def get_article_comments(self, article_id: int = 2645676, page_id: int = 1) -> Response:
return self._post_article_comments(article_id, page_id) return self._post_main_article_comments(article_id, page_id)
def comment_article(self, article_id: int = 2645676, msg: str = None) -> Response: def comment_article(self, article_id: int = 2645676, msg: str = None) -> Response:
if msg is None: if msg is None:
@ -1460,14 +1499,14 @@ class Citizen(classes.CitizenAPI):
return r return r
def write_article_comment(self, message: str, article_id: int, parent_id: int = None) -> Response: def write_article_comment(self, message: str, article_id: int, parent_id: int = None) -> Response:
return self._post_article_comments_create(message, article_id, parent_id) return self._post_main_article_comments_create(message, article_id, parent_id)
def publish_article(self, title: str, content: str, kind: int) -> Response: def publish_article(self, title: str, content: str, kind: int) -> Response:
kinds = {1: "First steps in eRepublik", 2: "Battle orders", 3: "Warfare analysis", kinds = {1: "First steps in eRepublik", 2: "Battle orders", 3: "Warfare analysis",
4: "Political debates and analysis", 5: "Financial business", 4: "Political debates and analysis", 5: "Financial business",
6: "Social interactions and entertainment"} 6: "Social interactions and entertainment"}
if kind in kinds: if kind in kinds:
return self._post_write_article(title, content, self.details.citizenship, kind) return self._post_main_write_article(title, content, self.details.citizenship, kind)
else: else:
raise classes.ErepublikException( raise classes.ErepublikException(
"Article kind must be one of:\n{}\n'{}' is not supported".format( "Article kind must be one of:\n{}\n'{}' is not supported".format(
@ -1508,7 +1547,6 @@ class Citizen(classes.CitizenAPI):
def get_raw_surplus(self) -> (float, float): def get_raw_surplus(self) -> (float, float):
frm = 0.00 frm = 0.00
wrm = 0.00 wrm = 0.00
self.update_companies()
for cdata in sorted(self.my_companies.companies.values()): for cdata in sorted(self.my_companies.companies.values()):
if cdata["industry_token"] == "FOOD": if cdata["industry_token"] == "FOOD":
raw = frm raw = frm
@ -1568,7 +1606,7 @@ class Citizen(classes.CitizenAPI):
return self.available_industries.get(industry_name, 0) return self.available_industries.get(industry_name, 0)
def buy_tg_contract(self) -> Response: def buy_tg_contract(self) -> Response:
ret = self._post_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())
return ret return ret
@ -1585,7 +1623,7 @@ class Citizen(classes.CitizenAPI):
jobs = r.json().get("jobs") jobs = r.json().get("jobs")
data = dict(citizen=0, salary=10) data = dict(citizen=0, salary=10)
for posting in jobs: for posting in jobs:
salary = posting.get("netSalary") salary = posting.get("salary")
limit = posting.get("salaryLimit", 0) limit = posting.get("salaryLimit", 0)
userid = posting.get("citizen").get("id") userid = posting.get("citizen").get("id")
@ -1595,10 +1633,10 @@ class Citizen(classes.CitizenAPI):
return self._post_economy_job_market_apply(**data) return self._post_economy_job_market_apply(**data)
def add_friend(self, player_id: int) -> Response: def add_friend(self, player_id: int) -> Response:
resp = self._get_citizen_hovercard(player_id) resp = self._get_main_citizen_hovercard(player_id)
rjson = resp.json() rjson = resp.json()
if not any([rjson["isBanned"], rjson["isDead"], rjson["isFriend"], rjson["isOrg"], rjson["isSelf"]]): if not any([rjson["isBanned"], rjson["isDead"], rjson["isFriend"], rjson["isOrg"], rjson["isSelf"]]):
r = self._post_citizen_add_remove_friend(int(player_id), True) r = self._post_main_citizen_add_remove_friend(int(player_id), True)
self.write_log("{:<64} (id:{:>11}) added as friend".format(rjson["name"], player_id)) self.write_log("{:<64} (id:{:>11}) added as friend".format(rjson["name"], player_id))
return r return r
return resp return resp
@ -1606,15 +1644,15 @@ class Citizen(classes.CitizenAPI):
def get_country_parties(self, country_id: int = None) -> dict: def get_country_parties(self, country_id: int = None) -> dict:
if country_id is None: if country_id is None:
country_id = self.details.citizenship country_id = self.details.citizenship
r = self._get_rankings_parties(country_id) r = self._get_main_rankings_parties(country_id)
ret = {} ret = {}
for name, id_ in re.findall(r'<a class="dotted" title="([^"]+)" href="/en/party/[\w\d-]+-(\d+)/1">', r.text): for name, id_ in re.findall(r'<a class="dotted" title="([^"]+)" href="/en/party/[\w\d-]+-(\d+)/1">', r.text):
ret.update({int(id_): name}) ret.update({int(id_): name})
return ret return ret
def _get_party_members(self, party_id: int) -> Dict[int, str]: def _get_main_party_members(self, party_id: int) -> Dict[int, str]:
ret = {} ret = {}
r = super()._get_party_members(party_id) r = super()._get_main_party_members(party_id)
for id_, name in re.findall(r'<a href="//www.erepublik.com/en/main/messages-compose/(\d+)" ' for id_, name in re.findall(r'<a href="//www.erepublik.com/en/main/messages-compose/(\d+)" '
r'title="([\w\d_ .]+)">', r.text): r'title="([\w\d_ .]+)">', r.text):
ret.update({id_: name}) ret.update({id_: name})
@ -1622,11 +1660,11 @@ class Citizen(classes.CitizenAPI):
def get_country_mus(self, country_id: int) -> Dict[int, str]: def get_country_mus(self, country_id: int) -> Dict[int, str]:
ret = {} ret = {}
r = self._get_leaderboards_damage_rankings(country_id) r = self._get_main_leaderboards_damage_rankings(country_id)
for data in r.json()["mu_filter"]: for data in r.json()["mu_filter"]:
if data["id"]: if data["id"]:
ret.update({data["id"]: data["name"]}) ret.update({data["id"]: data["name"]})
r = self._get_leaderboards_damage_aircraft_rankings(country_id) r = self._get_main_leaderboards_damage_aircraft_rankings(country_id)
for data in r.json()["mu_filter"]: for data in r.json()["mu_filter"]:
if data["id"]: if data["id"]:
ret.update({data["id"]: data["name"]}) ret.update({data["id"]: data["name"]})
@ -1647,13 +1685,13 @@ class Citizen(classes.CitizenAPI):
if ids is None: if ids is None:
ids = [1620414, ] ids = [1620414, ]
for player_id in ids: for player_id in ids:
self._post_messages_compose(subject, msg, [player_id]) self._post_main_messages_compose(subject, msg, [player_id])
def add_every_player_as_friend(self): def add_every_player_as_friend(self):
cities = [] cities = []
cities_dict = {} cities_dict = {}
self.write_log("WARNING! This will take a lot of time.") self.write_log("WARNING! This will take a lot of time.")
rj = self._post_travel_data(regionId=662, check="getCountryRegions").json() rj = self._post_main_travel_data(regionId=662, check="getCountryRegions").json()
for region_data in rj.get("regions", {}).values(): for region_data in rj.get("regions", {}).values():
cities.append(region_data['cityId']) cities.append(region_data['cityId'])
cities_dict.update({region_data['cityId']: region_data['cityName']}) cities_dict.update({region_data['cityId']: region_data['cityName']})
@ -1661,11 +1699,11 @@ class Citizen(classes.CitizenAPI):
cities.sort(key=int) cities.sort(key=int)
for city_id in cities: for city_id in cities:
self.write_log("Adding friends from {} (id: {})".format(cities_dict[city_id], city_id)) self.write_log("Adding friends from {} (id: {})".format(cities_dict[city_id], city_id))
resp = self._get_city_data_residents(city_id).json() resp = self._get_main_city_data_residents(city_id).json()
for resident in resp["widgets"]["residents"]["residents"]: for resident in resp["widgets"]["residents"]["residents"]:
self.add_friend(resident["citizenId"]) self.add_friend(resident["citizenId"])
for page in range(2, resp["widgets"]["residents"]["numResults"] // 10 + 2): for page in range(2, resp["widgets"]["residents"]["numResults"] // 10 + 2):
r = self._get_city_data_residents(city_id, page) r = self._get_main_city_data_residents(city_id, page)
resp = r.json() resp = r.json()
for resident in resp["widgets"]["residents"]["residents"]: for resident in resp["widgets"]["residents"]["residents"]:
self.add_friend(resident["citizenId"]) self.add_friend(resident["citizenId"])
@ -1709,6 +1747,7 @@ class Citizen(classes.CitizenAPI):
def launch_attack(self, war_id: int, region_id: int, region_name: str): def launch_attack(self, war_id: int, region_id: int, region_name: str):
self._post_wars_attack_region(war_id, region_id, region_name) self._post_wars_attack_region(war_id, region_id, region_name)
self.telegram.send_message(f"Battle for *{region_name}* queued")
def state_update_repeater(self): def state_update_repeater(self):
try: try:
@ -1734,52 +1773,23 @@ class Citizen(classes.CitizenAPI):
self.reporter.send_state_update(**data) self.reporter.send_state_update(**data)
def send_inventory_update(self): def send_inventory_update(self):
j = self.update_inventory() to_report = self.update_inventory()
active_items = {}
for item in j.get("inventoryItems", {}).get("activeEnhancements", {}).get("items", {}).values():
active_items.update({item['name']: item['active']['time_left']})
final_items = {}
for item in j.get("inventoryItems", {}).get("finalProducts", {}).get("items", {}).values():
name = item['name']
if item.get('type') == 'damageBoosters':
delta = item['duration']
if delta // 3600:
name += f" {delta // 3600}h"
if delta // 60 % 60:
name += f" {delta // 60 % 60}m"
if delta % 60:
name += f" {delta % 60}s"
if item['amount']:
final_items.update({name: item['amount']})
raw_materials = {}
for item in j.get("inventoryItems", {}).get("rawMaterials", {}).get("items", {}).values():
name = item['name']
if item['isPartial']:
continue
if item['amount']:
raw_materials.update({name: item['amount']})
to_report = dict(items=dict(active=active_items, final=final_items, raw=raw_materials), status=self.inventory)
self.reporter.report_action("INVENTORY", json_val=to_report) self.reporter.report_action("INVENTORY", json_val=to_report)
def check_house_durability(self) -> Dict[int, datetime.datetime]: def check_house_durability(self) -> Dict[int, datetime.datetime]:
ret = {} ret = {}
inv = self.update_inventory() inv = self.update_inventory()
if "activeEnhancements" in inv.get("inventoryItems", {}): active = inv["items"]['active']
active = inv.get("inventoryItems", {}).get("activeEnhancements", {}).get("items", {}) for q in range(1, 6):
for q in range(1, 6): if f"House Q{q}" in active:
if "4_%i_active" % q in active: till = utils.good_timedelta(self.now, datetime.timedelta(seconds=active[f"House Q{q}"]))
till = utils.good_timedelta(self.now, datetime.timedelta( ret.update({q: till})
seconds=active["4_%i_active" % q]["active"]["time_left"]))
ret.update({q: till})
return ret return ret
def buy_and_activate_house(self, q: int) -> Dict[int, datetime.datetime]: def buy_and_activate_house(self, q: int) -> Dict[int, datetime.datetime]:
inventory = self.update_inventory() inventory = self.update_inventory()
ok_to_activate = False ok_to_activate = False
if not inventory.get("inventoryItems").get("finalProducts", {}).get("items", {}).get("4_{}".format(q)): if not inventory["items"]["final"].get("House Q{}".format(q)):
offers = [] offers = []
countries = [self.details.citizenship, ] countries = [self.details.citizenship, ]
if self.details.current_country != self.details.citizenship: if self.details.current_country != self.details.citizenship:
@ -1827,13 +1837,16 @@ class Citizen(classes.CitizenAPI):
return active_until return active_until
def collect_anniversary_reward(self) -> Response: def collect_anniversary_reward(self) -> Response:
return self._post_collect_anniversary_reward() return self._post_main_collect_anniversary_reward()
def get_battle_round_data(self, battle_id: int, round_id: int, division: int = None) -> dict: def get_battle_round_data(self, battle_id: int, round_id: int, division: int = None) -> dict:
battle = self.all_battles.get(battle_id) battle = self.all_battles.get(battle_id)
if not battle: if not battle:
return {} return {}
r = self._post_battle_console(battle_id, battle.zone_id, round_id, division, 1, True)
data = dict(zoneId=round_id, round=round_id, division=division, leftPage=1, rightPage=1, type="damage")
r = self._post_military_battle_console(battle_id, "battleStatistics", 1, **data)
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")}
@ -1843,8 +1856,9 @@ class Citizen(classes.CitizenAPI):
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=71, action='currency', value=amount)
self.telegram.send_message(f"Donated {amount}cc to {utils.COUNTRIES[71]}")
self.reporter.report_action("CONTRIBUTE_CC", data) self.reporter.report_action("CONTRIBUTE_CC", data)
r = self._post_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) -> bool:
@ -1854,7 +1868,7 @@ class Citizen(classes.CitizenAPI):
return False return False
data = dict(country=71, action='food', value=amount, quality=quality) data = dict(country=71, action='food', value=amount, quality=quality)
self.reporter.report_action("CONTRIBUTE_FOOD", data) self.reporter.report_action("CONTRIBUTE_FOOD", data)
r = self._post_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) -> bool:
@ -1864,14 +1878,14 @@ class Citizen(classes.CitizenAPI):
return False return False
data = dict(country=71, action='gold', value=amount) data = dict(country=71, action='gold', value=amount)
self.reporter.report_action("CONTRIBUTE_GOLD", data) self.reporter.report_action("CONTRIBUTE_GOLD", data)
r = self._post_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 write_on_country_wall(self, message: str) -> bool: def write_on_country_wall(self, message: str) -> bool:
self._get_main() self._get_main()
post_to_wall_as = re.findall(r"""id="post_to_country_as".*?<option value="(\d?)">.*?</option>.*</select>""", post_to_wall_as = re.findall(r'id="post_to_country_as".*?<option value="(\d?)">.*?</option>.*</select>',
self.r.text, re.S | re.M) self.r.text, re.S | re.M)
r = self._post_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 = ""):
@ -1893,4 +1907,64 @@ class Citizen(classes.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=utils.MyJSONEncoder, indent=4 if indent else None, sort_keys=True) return dumps(self.__dict__, cls=classes.MyJSONEncoder, indent=4 if indent else None, sort_keys=True)
def get_game_token_offers(self):
r = self._post_economy_game_tokens_market('retrieve').json()
return {v.get('id'): dict(amount=v.get('amount'), price=v.get('price')) for v in r.get("topOffers")}
def fetch_organisation_account(self, org_id: int):
r = self._get_economy_citizen_accounts(org_id)
table = re.search(r'(<table class="holder racc" .*</table>)', r.text, re.I | re.M | re.S)
if table:
account = re.findall(r'>\s*(\d+.\d+)\s*</td>', table.group(1))
if account:
return {"gold": account[0], "cc": account[1], 'ok': True}
return {"gold": 0, "cc": 0, 'ok': False}
def get_ground_hit_dmg_value(self, rang: int = None, strength: float = None, elite: bool = None, ne: bool = False,
booster_50: bool = False, booster_100: bool = False, tp: bool = True) -> float:
if not rang or strength or elite is None:
r = self._get_main_citizen_profile_json(self.details.citizen_id).json()
if not rang:
rang = r['military']['militaryData']['ground']['rankNumber']
if not strength:
strength = r['military']['militaryData']['ground']['strength']
if elite is None:
elite = r['citizenAttributes']['level'] > 100
if ne:
tp = True
dmg = int(10 * (1 + strength / 400) * (1 + rang / 5) * 3)
booster = 1.5 if booster_50 else 2 if booster_100 else 1
elite = 1.1 if elite else 1
dmg = int(dmg * booster * elite)
legend = 1 if (not tp or rang < 70) else 1 + (rang - 69) / 10
dmg = int(dmg * legend)
return dmg * (1.1 if ne else 1)
def get_air_hit_dmg_value(self, rang: int = None, elite: bool = None, ne: bool = False,
weapon: bool = False) -> float:
if not rang or elite is None:
r = self._get_main_citizen_profile_json(self.details.citizen_id).json()
if not rang:
rang = r['military']['militaryData']['air']['rankNumber']
if elite is None:
elite = r['citizenAttributes']['level'] > 100
dmg = int(10 * (1 + rang / 5) * (1.2 if weapon else 1))
elite = 1.1 if elite else 1
dmg = int(dmg * elite)
return dmg * (1.1 if ne else 1.)
def endorse_article(self, article_id: int, amount: int) -> bool:
if amount in (5, 50, 100):
resp = self._post_main_donate_article(article_id, amount).json()
return not bool(resp.get('error'))
else:
return False
def vote_article(self, article_id: int) -> bool:
resp = self._post_main_vote_article(article_id).json()
return not bool(resp.get('error'))

View File

@ -2,12 +2,14 @@ import datetime
import decimal import decimal
import hashlib import hashlib
import random import random
import sys
import time import time
import traceback
from collections import deque from collections import deque
from json import JSONDecodeError, loads, JSONEncoder from json import JSONDecodeError, loads, JSONEncoder
from typing import Any, Dict, List, Union, Mapping, Iterable from typing import Any, Dict, List, Union, Mapping, Iterable, Tuple
from requests import Response, Session from requests import Response, Session, post
from erepublik import utils from erepublik import utils
@ -61,7 +63,7 @@ class MyCompanies:
production=0, base_production=0, wam_enabled=False, can_work_as_manager=False, production=0, base_production=0, wam_enabled=False, can_work_as_manager=False,
preset_own_work=0, already_worked=False, can_assign_employees=False, preset_works=0, preset_own_work=0, already_worked=False, can_assign_employees=False, preset_works=0,
todays_works=0, holding_company_id=None, is_assigned_to_holding=False, todays_works=0, holding_company_id=None, is_assigned_to_holding=False,
cannot_work_as_manager_reason=False) cannot_work_as_manager_reason=False, industry_id=0)
for c_id, company in companies.items(): for c_id, company in companies.items():
tmp = {} tmp = {}
@ -119,7 +121,10 @@ class MyCompanies:
raw = [] raw = []
factory = [] factory = []
if holding_id in self.holdings: if holding_id in self.holdings:
for company_id in self.holdings.get(holding_id, {}).get('companies', []): for company_id in sorted(self.holdings.get(holding_id, {}).get('companies', []),
key=lambda cid: (-self.companies[cid].get('is_raw'), # True, False
self.companies[cid].get('industry_id'), # F W H A
-self.companies[cid].get('quality'),)): # 7, 6, .. 2, 1
company = self.companies.get(company_id, {}) company = self.companies.get(company_id, {})
wam_enabled = bool(company.get('wam_enabled', {})) wam_enabled = bool(company.get('wam_enabled', {}))
already_worked = not company.get('already_worked', {}) already_worked = not company.get('already_worked', {})
@ -156,28 +161,33 @@ class MyCompanies:
raise ErepublikException("Wrong function call") raise ErepublikException("Wrong function call")
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class SlowRequests(Session): class SlowRequests(Session):
last_time: datetime.datetime last_time: datetime.datetime
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) ' 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Chrome/73.0.3683.103 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) ' 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36',
'Chrome/72.0.3626.13 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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Chrome/71.0.3578.98 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) 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/73.0.3683.103 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.11 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
# FireFox # FireFox
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.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:65.0) Gecko/20100101 Firefox/65.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:64.0) Gecko/20100101 Firefox/64.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:66.0) Gecko/20100101 Firefox/66.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0',
] ]
debug = False debug = False
@ -280,38 +290,21 @@ class Config:
force_wam = False force_wam = False
sort_battles_time = True sort_battles_time = True
force_travel = False force_travel = False
telegram = True
telegram_chat_id = 0
telegram_token = ""
@property @property
def wt(self): def wt(self):
return self.work and self.train return self.work and self.train
@property
def __dict__(self) -> Dict[str, Union[bool, str, List[str]]]: def __dict__(self) -> Dict[str, Union[bool, str, List[str]]]:
return dict( ret = {}
email=self.email, for key in dir(self):
password=self.password, if not key.startswith('_') and key not in ['email', 'password']:
work=self.work, ret[key] = getattr(self, key)
train=self.train, return ret
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,
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,
)
class Energy: class Energy:
@ -332,7 +325,7 @@ class Energy:
@property @property
def food_fights(self): def food_fights(self):
return (self.recoverable + self.recovered) // 10 return self.available // 10
@property @property
def reference_time(self): def reference_time(self):
@ -344,7 +337,7 @@ class Energy:
@property @property
def is_recoverable_full(self): def is_recoverable_full(self):
return self.recoverable >= self.limit - self.interval return self.recoverable >= self.limit - 5 * self.interval
@property @property
def is_recovered_full(self): def is_recovered_full(self):
@ -360,13 +353,11 @@ class Energy:
@property @property
def __dict__(self): def __dict__(self):
return dict( ret = {}
limit=self.limit, for key in dir(self):
interval=self.interval, if not key.startswith('_'):
recoverable=self.recoverable, ret[key] = getattr(self, key)
recovered=self.recovered, return ret
reference_time=self.reference_time
)
class Details(object): class Details(object):
@ -412,6 +403,14 @@ class Details(object):
next_level_up = (1 + (self.xp // 10)) * 10 next_level_up = (1 + (self.xp // 10)) * 10
return next_level_up - self.xp return next_level_up - self.xp
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class Politics: class Politics:
is_party_member: bool = False is_party_member: bool = False
@ -422,6 +421,14 @@ class Politics:
is_congressman: bool = False is_congressman: bool = False
is_country_president: bool = False is_country_president: bool = False
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class House(object): class House(object):
quality = None quality = None
@ -454,25 +461,25 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def get(self, url: str, **kwargs) -> Response: def get(self, url: str, **kwargs) -> Response:
return self._req.get(url, **kwargs) return self._req.get(url, **kwargs)
def _get_article_json(self, article_id: int) -> Response: def _get_main_article_json(self, article_id: int) -> Response:
return self.get("{}/main/articleJson/{}".format(self.url, article_id)) return self.get("{}/main/articleJson/{}".format(self.url, article_id))
def _get_battlefield_choose_side(self, battle: int, side: int) -> Response: def _get_military_battlefield_choose_side(self, battle: int, side: int) -> Response:
return self.get("{}/military/battlefield-choose-side/{}/{}".format(self.url, battle, side)) return self.get("{}/military/battlefield-choose-side/{}/{}".format(self.url, battle, side))
def _get_candidate_party(self, party_slug: str) -> Response: def _get_candidate_party(self, party_slug: str) -> Response:
return self.post("{}/candidate/{}".format(self.url, party_slug)) return self.post("{}/candidate/{}".format(self.url, party_slug))
def _get_citizen_hovercard(self, citizen: int) -> Response: def _get_main_citizen_hovercard(self, citizen: int) -> Response:
return self.get("{}/main/citizen-hovercard/{}".format(self.url, citizen)) return self.get("{}/main/citizen-hovercard/{}".format(self.url, citizen))
def _get_citizen_profile(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_citizen_daily_assistant(self) -> Response: def _get_main_citizen_daily_assistant(self) -> Response:
return self.get("{}/main/citizenDailyAssistant".format(self.url)) return self.get("{}/main/citizenDailyAssistant".format(self.url))
def _get_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 = {}
return self.get("{}/main/city-data/{}/residents".format(self.url, city), params={"currentPage": page, **params}) return self.get("{}/main/city-data/{}/residents".format(self.url, city), params={"currentPage": page, **params})
@ -480,6 +487,9 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_country_military(self, country: str) -> Response: def _get_country_military(self, country: str) -> Response:
return self.get("{}/country/military/{}".format(self.url, country)) return self.get("{}/country/military/{}".format(self.url, country))
def _get_economy_citizen_accounts(self, organisation_id: int) -> Response:
return self.get("{}/economy/citizen-accounts/{}".format(self.url, organisation_id))
def _get_economy_inventory_items(self) -> Response: def _get_economy_inventory_items(self) -> Response:
return self.get("{}/economy/inventory-items/".format(self.url)) return self.get("{}/economy/inventory-items/".format(self.url))
@ -492,96 +502,95 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_economy_my_market_offers(self) -> Response: def _get_economy_my_market_offers(self) -> Response:
return self.get("{}/economy/myMarketOffers".format(self.url)) return self.get("{}/economy/myMarketOffers".format(self.url))
def _get_job_data(self) -> Response: def _get_main_job_data(self) -> Response:
return self.get("{}/main/job-data".format(self.url)) return self.get("{}/main/job-data".format(self.url))
def _get_leaderboards_damage_aircraft_rankings(self, country: int, weeks: int = 0, mu: int = 0) -> Response: def _get_main_leaderboards_damage_aircraft_rankings(self, country: int, weeks: int = 0, mu: int = 0) -> Response:
data = (country, weeks, mu) data = (country, weeks, mu)
return self.get("{}/main/leaderboards-damage-aircraft-rankings/{}/{}/{}/0".format(self.url, *data)) return self.get("{}/main/leaderboards-damage-aircraft-rankings/{}/{}/{}/0".format(self.url, *data))
def _get_leaderboards_damage_rankings(self, country: int, weeks: int = 0, mu: int = 0, div: int = 0) -> Response: def _get_main_leaderboards_damage_rankings(self, country: int, weeks: int = 0, mu: int = 0,
div: int = 0) -> Response:
data = (country, weeks, mu, div) data = (country, weeks, mu, div)
return self.get("{}/main/leaderboards-damage-rankings/{}/{}/{}/{}".format(self.url, *data)) return self.get("{}/main/leaderboards-damage-rankings/{}/{}/{}/{}".format(self.url, *data))
def _get_leaderboards_kills_aircraft_rankings(self, country: int, weeks: int = 0, mu: int = 0) -> Response: def _get_main_leaderboards_kills_aircraft_rankings(self, country: int, weeks: int = 0, mu: int = 0) -> Response:
data = (country, weeks, mu) data = (country, weeks, mu)
return self.get("{}/main/leaderboards-kills-aircraft-rankings/{}/{}/{}/0".format(self.url, *data)) return self.get("{}/main/leaderboards-kills-aircraft-rankings/{}/{}/{}/0".format(self.url, *data))
def _get_leaderboards_kills_rankings(self, country: int, weeks: int = 0, mu: int = 0, div: int = 0) -> Response: def _get_main_leaderboards_kills_rankings(self, country: int, weeks: int = 0, mu: int = 0,
div: int = 0) -> Response:
data = (country, weeks, mu, div) data = (country, weeks, mu, div)
return self.get("{}/main/leaderboards-kills-rankings/{}/{}/{}/{}".format(self.url, *data)) return self.get("{}/main/leaderboards-kills-rankings/{}/{}/{}/{}".format(self.url, *data))
def _get_main(self) -> Response: def _get_main(self) -> Response:
return self.get(self.url) return self.get(self.url)
def _get_messages(self, page: int = 1) -> Response: def _get_main_messages_paginated(self, page: int = 1) -> Response:
return self.get("{}/main/messages-paginated/{}".format(self.url, page)) return self.get("{}/main/messages-paginated/{}".format(self.url, page))
def _get_military_campaigns(self) -> Response: def _get_military_campaigns(self) -> Response:
return self.get("{}/military/campaigns-new/".format(self.url)) return self.get("{}/military/campaigns-new/".format(self.url))
def _get_military_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)
def _get_money_donation_accept(self, donation_id: int) -> Response: def _get_main_money_donation_accept(self, donation_id: int) -> Response:
return self.get("{}/main/money-donation/accept/{}".format(self.url, donation_id), params={"_token": self.token}) return self.get("{}/main/money-donation/accept/{}".format(self.url, donation_id), params={"_token": self.token})
def _get_money_donation_reject(self, donation_id: int) -> Response: def _get_main_money_donation_reject(self, donation_id: int) -> Response:
return self.get("{}/main/money-donation/reject/{}".format(self.url, donation_id), params={"_token": self.token}) return self.get("{}/main/money-donation/reject/{}".format(self.url, donation_id), params={"_token": self.token})
def _get_notifications_ajax_community(self, page: int = 1) -> Response: def _get_main_notifications_ajax_community(self, page: int = 1) -> Response:
return self.get("{}/main/notificationsAjax/community/{}".format(self.url, page)) return self.get("{}/main/notificationsAjax/community/{}".format(self.url, page))
def _get_notifications_ajax_system(self, page: int = 1) -> Response: def _get_main_notifications_ajax_system(self, page: int = 1) -> Response:
return self.get("{}/main/notificationsAjax/system/{}".format(self.url, page)) return self.get("{}/main/notificationsAjax/system/{}".format(self.url, page))
def _get_notifications_ajax_report(self, page: int = 1) -> Response: def _get_main_notifications_ajax_report(self, page: int = 1) -> Response:
return self.get("{}/main/notificationsAjax/report/{}".format(self.url, page)) return self.get("{}/main/notificationsAjax/report/{}".format(self.url, page))
def _get_party_members(self, party: int) -> Response: def _get_main_party_members(self, party: int) -> Response:
return self.get("{}/main/party-members/{}".format(self.url, party)) return self.get("{}/main/party-members/{}".format(self.url, party))
def _get_rankings_parties(self, country: int) -> Response: def _get_main_rankings_parties(self, country: int) -> Response:
return self.get("{}/main/rankings-parties/1/{}".format(self.url, country)) return self.get("{}/main/rankings-parties/1/{}".format(self.url, country))
def _get_training_grounds_json(self) -> Response: def _get_main_training_grounds_json(self) -> Response:
return self.get("{}/main/training-grounds-json".format(self.url)) return self.get("{}/main/training-grounds-json".format(self.url))
def _get_weekly_challenge_data(self) -> Response: def _get_main_weekly_challenge_data(self) -> Response:
return self.get("{}/main/weekly-challenge-data".format(self.url)) return self.get("{}/main/weekly-challenge-data".format(self.url))
def _get_wars_show(self, war_id: int) -> Response: def _get_wars_show(self, war_id: int) -> Response:
return self.get("{}/wars/show/{}".format(self.url, war_id)) return self.get("{}/wars/show/{}".format(self.url, war_id))
def _post_activate_battle_effect(self, battle: int, kind: str, citizen_id: int) -> Response: def _post_main_activate_battle_effect(self, battle: int, kind: str, citizen_id: int) -> Response:
data = dict(battleId=battle, citizenId=citizen_id, type=kind, _token=self.token) data = dict(battleId=battle, citizenId=citizen_id, type=kind, _token=self.token)
return self.post("{}/main/fight-activateBattleEffect".format(self.url), data=data) return self.post("{}/main/fight-activateBattleEffect".format(self.url), data=data)
def _post_article_comments(self, article: int, page: int = 1) -> Response: def _post_main_article_comments(self, article: int, page: int = 1) -> Response:
data = dict(_token=self.token, articleId=article, page=page) data = dict(_token=self.token, articleId=article, page=page)
if page: if page:
data.update({'page': page}) data.update({'page': page})
return self.post("{}/main/articleComments".format(self.url), data=data) return self.post("{}/main/articleComments".format(self.url), data=data)
def _post_article_comments_create(self, message: str, article: int, parent: int = 0) -> Response: def _post_main_article_comments_create(self, message: str, article: int, parent: int = 0) -> Response:
data = dict(_token=self.token, message=message, articleId=article) data = dict(_token=self.token, message=message, articleId=article)
if parent: if parent:
data.update({"parentId": parent}) data.update({"parentId": parent})
return self.post("{}/main/articleComments/create".format(self.url), data=data) return self.post("{}/main/articleComments/create".format(self.url), data=data)
def _post_battle_console(self, battle: int, zone: int, round_id: int, division: int, page: int, def _post_main_battlefield_travel(self, side_id: int, battle_id: int) -> Response:
damage: bool) -> Response: data = dict(_token=self.token, sideCountryId=side_id, battleId=battle_id)
data = dict(battleId=battle, zoneId=zone, action="battleStatistics", round=round_id, division=division, return self.post("{}/main/battlefieldTravel".format(self.url), data=data)
leftPage=page, rightPage=page, _token=self.token)
if damage:
data.update({"type": "damage"})
else:
data.update({"type": "kills"})
return self.post("{}/military/battle-console".format(self.url), data=data) def _post_main_buy_gold_items(self, currency: str, item: str, amount: int) -> Response:
def _post_buy_gold_items(self, currency: str, item: str, amount: int) -> Response:
data = dict(itemId=item, currency=currency, amount=amount, _token=self.token) data = dict(itemId=item, currency=currency, amount=amount, _token=self.token)
return self.post("{}/main/buyGoldItems".format(self.url), data=data) return self.post("{}/main/buyGoldItems".format(self.url), data=data)
@ -589,7 +598,7 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(_token=self.token, presentation=presentation) data = dict(_token=self.token, presentation=presentation)
return self.post("{}/candidate-for-congress".format(self.url), data=data) return self.post("{}/candidate-for-congress".format(self.url), data=data)
def _post_citizen_add_remove_friend(self, citizen: int, add: bool) -> Response: 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") data = dict(_token=self.token, citizenId=citizen, url="//www.erepublik.com/en/main/citizen-addRemoveFriend")
if add: if add:
data.update({"action": "addFriend"}) data.update({"action": "addFriend"})
@ -597,18 +606,22 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data.update({"action": "removeFriend"}) data.update({"action": "removeFriend"})
return self.post("{}/main/citizen-addRemoveFriend".format(self.url), data=data) return self.post("{}/main/citizen-addRemoveFriend".format(self.url), data=data)
def _post_collect_anniversary_reward(self) -> Response: def _post_main_collect_anniversary_reward(self) -> Response:
return self.post("{}/main/collect-anniversary-reward".format(self.url), data={"_token": self.token}) return self.post("{}/main/collect-anniversary-reward".format(self.url), data={"_token": self.token})
def _post_country_donate(self, country: int, action: str, value: Union[int, float], def _post_main_country_donate(self, country: int, action: str, value: Union[int, float],
quality: int = None) -> Response: quality: int = None) -> Response:
json = dict(countryId=country, action=action, _token=self.token, value=value, quality=quality) json = dict(countryId=country, action=action, _token=self.token, value=value, quality=quality)
return self.post("{}/main/country-donate".format(self.url), data=json, return self.post("{}/main/country-donate".format(self.url), data=json,
headers={"Referer": "{}/country/economy/Latvia".format(self.url)}) headers={"Referer": "{}/country/economy/Latvia".format(self.url)})
def _post_daily_task_reward(self) -> Response: def _post_main_daily_task_reward(self) -> Response:
return self.post("{}/main/daily-tasks-reward".format(self.url), data=dict(_token=self.token)) return self.post("{}/main/daily-tasks-reward".format(self.url), data=dict(_token=self.token))
def _post_main_donate_article(self, article_id: int, amount: int) -> Response:
data = dict(_token=self.token, articleId=article_id, amount=amount)
return self.post("{}/main/donate-article".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)
@ -617,6 +630,10 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(_token=self.token, buttonColor=color) data = dict(_token=self.token, buttonColor=color)
return self.post("{}/main/eat".format(self.url), params=data) return self.post("{}/main/eat".format(self.url), params=data)
def _post_economy_activate_booster(self, quality: int, duration: int, kind: str) -> Response:
data = dict(type=kind, quality=quality, duration=duration, fromInventory=True)
return self.post("{}/economy/activateBooster".format(self.url), data=data)
def _post_economy_activate_house(self, quality: int) -> Response: def _post_economy_activate_house(self, quality: int) -> Response:
data = {"action": "activate", "quality": quality, "type": "house", "_token": self.token} data = {"action": "activate", "quality": quality, "type": "house", "_token": self.token}
return self.post("{}/economy/activateHouse".format(self.url), data=data) return self.post("{}/economy/activateHouse".format(self.url), data=data)
@ -650,6 +667,11 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(_token=self.token, personalOffers=int(personal), page=page, currencyId=currency) data = dict(_token=self.token, personalOffers=int(personal), page=page, currencyId=currency)
return self.post("{}/economy/exchange/retrieve/".format(self.url), data=data) return self.post("{}/economy/exchange/retrieve/".format(self.url), data=data)
def _post_economy_game_tokens_market(self, action: str) -> Response:
assert action in ['retrieve', ]
data = dict(_token=self.token, action=action)
return self.post("{}/economy/gameTokensMarketAjax".format(self.url), data=data)
def _post_economy_job_market_apply(self, citizen: int, salary: int) -> Response: def _post_economy_job_market_apply(self, citizen: int, salary: int) -> Response:
data = dict(_token=self.token, citizenId=citizen, salary=salary) data = dict(_token=self.token, citizenId=citizen, salary=salary)
return self.post("{}/economy/job-market-apply".format(self.url), data=data) return self.post("{}/economy/job-market-apply".format(self.url), data=data)
@ -675,18 +697,18 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data={"_token": self.token, "action_type": "resign"}) data={"_token": self.token, "action_type": "resign"})
def _post_economy_sell_company(self, factory: int, pin: int = None, sell: bool = True) -> Response: def _post_economy_sell_company(self, factory: int, pin: int = None, sell: bool = True) -> Response:
url = "{}/economy/sell-company/{}".format(self.url, factory)
data = dict(_token=self.token, pin="" if pin is None else pin) data = dict(_token=self.token, pin="" if pin is None else pin)
if sell: if sell:
data.update({"sell": "sell"}) data.update({"sell": "sell"})
else: else:
data.update({"dissolve": factory}) data.update({"dissolve": factory})
return self.post(url, data=data, headers={"Referer": url}) return self.post("{}/economy/sell-company/{}".format(self.url, factory),
data=data, headers={"Referer": self.url})
def _post_economy_train(self, tg_ids: List[int]) -> Response: def _post_economy_train(self, tg_ids: List[int]) -> Response:
data: Dict[str, Union[int, str]] = {} data: Dict[str, Union[int, str]] = {}
if not tg_ids: if not tg_ids:
return self._get_training_grounds_json() return self._get_main_training_grounds_json()
else: else:
for idx, tg_id in enumerate(tg_ids): for idx, tg_id in enumerate(tg_ids):
data["grounds[%i][id]" % idx] = tg_id data["grounds[%i][id]" % idx] = tg_id
@ -734,7 +756,7 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(_token=self.token, email=email, commit="Reset password") data = dict(_token=self.token, email=email, commit="Reset password")
return self.post("{}/forgot-password".format(self.url), data=data) return self.post("{}/forgot-password".format(self.url), data=data)
def _post_fight_activate_booster(self, battle: int, quality: int, duration: int, kind: str) -> Response: def _post_military_fight_activate_booster(self, battle: int, quality: int, duration: int, kind: str) -> Response:
data = dict(type=kind, quality=quality, duration=duration, battleId=battle, _token=self.token) data = dict(type=kind, quality=quality, duration=duration, battleId=battle, _token=self.token)
return self.post("{}/military/fight-activateBooster".format(self.url), data=data) return self.post("{}/military/fight-activateBooster".format(self.url), data=data)
@ -742,25 +764,30 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember='on') data = dict(csrf_token=self.token, citizen_email=email, citizen_password=password, remember='on')
return self.post("{}/login".format(self.url), data=data) return self.post("{}/login".format(self.url), data=data)
def _post_messages_alert(self, notification_ids: List[int]) -> Response: 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("{}/main/messages-alerts/1".format(self.url), data=data) return self.post("{}/main/messages-alerts/1".format(self.url), data=data)
def _post_messages_compose(self, subject: str, body: str, citizens: List[int]) -> Response: def _post_main_messages_compose(self, subject: str, body: str, citizens: List[int]) -> Response:
url_pk = 0 if len(citizens) > 1 else str(citizens[0]) url_pk = 0 if len(citizens) > 1 else str(citizens[0])
data = dict(citizen_name=",".join([str(x) for x in citizens]), data = dict(citizen_name=",".join([str(x) for x in citizens]),
citizen_subject=subject, _token=self.token, citizen_message=body) citizen_subject=subject, _token=self.token, citizen_message=body)
return self.post("{}/main/messages-compose/{}}".format(self.url, url_pk), data=data) return self.post("{}/main/messages-compose/{}".format(self.url, url_pk), data=data)
def _post_military_battle_console(self, battle_id: int, action: str, page: int = 1, **kwargs) -> Response: 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) data = dict(battleId=battle_id, action=action, _token=self.token)
if action == "battleStatistics": if action == "battleStatistics":
data.update(round=kwargs["round_id"], zoneId=kwargs["round_id"], leftPage=page, rightPage=page, data.update(round=kwargs["round_id"], zoneId=kwargs["round_id"], leftPage=page, rightPage=page,
division=kwargs["division"], type=kwargs.get("type", 'damage'),) division=kwargs["division"], type=kwargs.get("type", 'damage'), )
elif action == "warList": elif action == "warList":
data.update(page=page) data.update(page=page)
return self.post("{}/military/battle-console".format(self.url), data=data) return self.post("{}/military/battle-console".format(self.url), data=data)
def _post_military_change_weapon(self, battle_id: int, battle_zone_id: int, customization_level: int) -> Response:
data = dict(_token=self.token, battleZoneId=battle_zone_id, battleId=battle_id,
customizationLevel=customization_level)
return self.post("{}/military/change-weapon".format(self.url), data=data)
def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response: def _post_military_deploy_bomb(self, battle_id: int, bomb_id: int) -> Response:
data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token) data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token)
return self.post("{}/military/deploy-bomb".format(self.url), data=data) return self.post("{}/military/deploy-bomb".format(self.url), data=data)
@ -777,22 +804,26 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(action="check", _token=self.token) data = dict(action="check", _token=self.token)
return self.post("{}/military/group-missions".format(self.url), data=data) return self.post("{}/military/group-missions".format(self.url), data=data)
def _post_travel(self, check: str, **kwargs) -> Response: def _post_main_travel(self, check: str, **kwargs) -> Response:
data = dict(_token=self.token, check=check, **kwargs) data = dict(_token=self.token, check=check, **kwargs)
return self.post("{}/main/travel".format(self.url), data=data) return self.post("{}/main/travel".format(self.url), data=data)
def _post_travel_data(self, **kwargs) -> Response: def _post_main_vote_article(self, article_id: int) -> Response:
data = dict(_token=self.token, articleId=article_id)
return self.post("{}/main/vote-article".format(self.url), data=data)
def _post_main_travel_data(self, **kwargs) -> Response:
return self.post("{}/main/travelData".format(self.url), data=dict(_token=self.token, **kwargs)) return self.post("{}/main/travelData".format(self.url), data=dict(_token=self.token, **kwargs))
def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response: def _post_wars_attack_region(self, war_id: int, region_id: int, region_name: str) -> Response:
data = {'_token': self.token, 'warId': war_id, 'regionName': region_name, 'regionNameConfirm': region_name} data = {'_token': self.token, 'warId': war_id, 'regionName': region_name, 'regionNameConfirm': region_name}
return self.post('{}/wars/attack-region/{}/{}'.format(self.url, war_id, region_id), data=data) return self.post('{}/wars/attack-region/{}/{}'.format(self.url, war_id, region_id), data=data)
def _post_weekly_challenge_reward(self, reward_id: int) -> Response: def _post_main_weekly_challenge_reward(self, reward_id: int) -> Response:
data = dict(_token=self.token, rewardId=reward_id) data = dict(_token=self.token, rewardId=reward_id)
return self.post("{}/main/weekly-challenge-collect-reward".format(self.url), data=data) return self.post("{}/main/weekly-challenge-collect-reward".format(self.url), data=data)
def _post_write_article(self, title: str, content: str, location: int, kind: int) -> Response: def _post_main_write_article(self, title: str, content: str, location: int, kind: int) -> Response:
data = dict(_token=self.token, article_name=title, article_body=content, article_location=location, data = dict(_token=self.token, article_name=title, article_body=content, article_location=location,
article_category=kind) article_category=kind)
return self.post("{}/main/write-article".format(self.url), data=data) return self.post("{}/main/write-article".format(self.url), data=data)
@ -800,73 +831,73 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
# Wall Posts # Wall Posts
# ## Country # ## Country
def _post_country_comment_retrieve(self, post_id: int) -> Response: 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("{}/main/country-comment/retrieve/json".format(self.url), data=data) return self.post("{}/main/country-comment/retrieve/json".format(self.url), data=data)
def _post_country_comment_create(self, post_id: int, comment_message: str) -> Response: 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("{}/main/country-comment/create/json".format(self.url), data=data) return self.post("{}/main/country-comment/create/json".format(self.url), data=data)
def _post_country_post_create(self, body: str, post_as: int) -> Response: 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("{}/main/country-post/create/json".format(self.url), data=data) return self.post("{}/main/country-post/create/json".format(self.url), data=data)
def _post_country_post_retrieve(self) -> Response: 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("{}/main/country-post/retrieve/json".format(self.url), data=data) return self.post("{}/main/country-post/retrieve/json".format(self.url), data=data)
# ## Military Unit # ## Military Unit
def _post_military_unit_comment_retrieve(self, post_id: int) -> Response: 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("{}/main/military-unit-comment/retrieve/json".format(self.url), data=data) return self.post("{}/main/military-unit-comment/retrieve/json".format(self.url), data=data)
def _post_military_unit_comment_create(self, post_id: int, comment_message: str) -> Response: 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("{}/main/military-unit-comment/create/json".format(self.url), data=data) return self.post("{}/main/military-unit-comment/create/json".format(self.url), data=data)
def _post_military_unit_post_create(self, body: str, post_as: int) -> Response: 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("{}/main/military-unit-post/create/json".format(self.url), data=data) return self.post("{}/main/military-unit-post/create/json".format(self.url), data=data)
def _post_military_unit_post_retrieve(self) -> Response: 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("{}/main/military-unit-post/retrieve/json".format(self.url), data=data) return self.post("{}/main/military-unit-post/retrieve/json".format(self.url), data=data)
# ## Party # ## Party
def _post_party_comment_retrieve(self, post_id: int) -> Response: 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("{}/main/party-comment/retrieve/json".format(self.url), data=data) return self.post("{}/main/party-comment/retrieve/json".format(self.url), data=data)
def _post_party_comment_create(self, post_id: int, comment_message: str) -> Response: 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("{}/main/party-comment/create/json".format(self.url), data=data) return self.post("{}/main/party-comment/create/json".format(self.url), data=data)
def _post_party_post_create(self, body: str) -> Response: 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("{}/main/party-post/create/json".format(self.url), data=data) return self.post("{}/main/party-post/create/json".format(self.url), data=data)
def _post_party_post_retrieve(self) -> Response: 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("{}/main/party-post/retrieve/json".format(self.url), data=data) return self.post("{}/main/party-post/retrieve/json".format(self.url), data=data)
# ## Friend's Wall # ## Friend's Wall
def _post_wall_comment_retrieve(self, post_id: int) -> Response: 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("{}/main/wall-comment/retrieve/json".format(self.url), data=data) return self.post("{}/main/wall-comment/retrieve/json".format(self.url), data=data)
def _post_wall_comment_create(self, post_id: int, comment_message: str) -> Response: 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("{}/main/wall-comment/create/json".format(self.url), data=data) return self.post("{}/main/wall-comment/create/json".format(self.url), data=data)
def _post_wall_post_create(self, body: str) -> Response: 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("{}/main/wall-post/create/json".format(self.url), data=data) return self.post("{}/main/wall-post/create/json".format(self.url), data=data)
def _post_wall_post_retrieve(self) -> Response: def _post_main_wall_post_retrieve(self) -> Response:
data = {"_token": self.token, "page": 1, "switchedFrom": False} data = {"_token": self.token, "page": 1, "switchedFrom": False}
return self.post("{}/main/wall-post/retrieve/json".format(self.url), data=data) return self.post("{}/main/wall-post/retrieve/json".format(self.url), data=data)
@ -950,11 +981,12 @@ class Reporter:
class MyJSONEncoder(JSONEncoder): class MyJSONEncoder(JSONEncoder):
def default(self, o): def default(self, o):
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', year=o.year, month=o.month, day=o.day, hour=o.hour, minute=o.minute,
second=o.second, microsecond=o.microsecond) second=o.second, microsecond=o.microsecond, 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', year=o.year, month=o.month, day=o.day)
elif isinstance(o, datetime.timedelta): elif isinstance(o, datetime.timedelta):
@ -966,6 +998,8 @@ class MyJSONEncoder(JSONEncoder):
return o.__dict__ return o.__dict__
elif isinstance(o, deque): elif isinstance(o, deque):
return list(o) return list(o)
elif isinstance(o, Citizen):
return o.to_json()
return super().default(o) return super().default(o)
@ -981,6 +1015,14 @@ class BattleSide:
self.allies = [int(ally) for ally in allies] self.allies = [int(ally) for ally in allies]
self.deployed = [int(ally) for ally in deployed] self.deployed = [int(ally) for ally in deployed]
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class BattleDivision: class BattleDivision:
end: datetime.datetime end: datetime.datetime
@ -998,6 +1040,14 @@ class BattleDivision:
self.dom_pts = dict({"inv": inv_pts, "def": def_pts}) self.dom_pts = dict({"inv": inv_pts, "def": def_pts})
self.wall = dict({"for": wall_for, "dom": wall_dom}) self.wall = dict({"for": wall_for, "dom": wall_dom})
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class Battle(object): class Battle(object):
id: int = 0 id: int = 0
@ -1033,11 +1083,11 @@ class Battle(object):
self.div = {} self.div = {}
for div, data in battle.get('div', {}).items(): for div, data in battle.get('div', {}).items():
div = int(div) div = int(data.get('div'))
if data.get('end'): if data.get('end'):
end = datetime.datetime.fromtimestamp(data.get('end'), tz=utils.erep_tz) end = datetime.datetime.fromtimestamp(data.get('end'), tz=utils.erep_tz)
else: else:
end = datetime.datetime.max end = utils.localize_dt(datetime.datetime.max - datetime.timedelta(days=1))
battle_div = BattleDivision( battle_div = BattleDivision(
end=end, epic=data.get('epic_type') in [1, 5], end=end, epic=data.get('epic_type') in [1, 5],
@ -1058,6 +1108,14 @@ class Battle(object):
self.id, utils.COUNTRIES[self.invader.id], utils.COUNTRIES[self.defender.id], self.zone_id, time_part self.id, utils.COUNTRIES[self.invader.id], utils.COUNTRIES[self.defender.id], self.zone_id, time_part
) )
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class EnergyToFight: class EnergyToFight:
energy: int = 0 energy: int = 0
@ -1088,3 +1146,57 @@ class EnergyToFight:
if 0 < new_energy < self.energy: if 0 < new_energy < self.energy:
self.energy = new_energy self.energy = new_energy
return self.energy return self.energy
class TelegramBot:
__initialized = False
__queue: List[str] = []
chat_id = 0
api_url = ""
player_name = ""
__last_time: datetime.datetime = None
def do_init(self, chat_id: int, token: str, player_name: str = ""):
self.chat_id = chat_id
self.api_url = "https://api.telegram.org/bot{}/sendMessage".format(token)
self.player_name = player_name
self.__initialized = True
self.__last_time = utils.good_timedelta(utils.now(), datetime.timedelta(minutes=-5))
if self.__queue:
self.send_message("\n\n\n\n".join(self.__queue))
def send_message(self, message: str) -> bool:
if not self.__initialized:
self.__queue.append(message)
return True
if self.player_name:
message = f"Player *{self.player_name}*\n" + message
if utils.good_timedelta(utils.now(), datetime.timedelta(seconds=-1)) <= self.__last_time:
tb = traceback.extract_stack()
message += "\n\n```\n{}\n```".format("\n".join([' File "{}", line {}, in {}\n'.format(l.filename, l.lineno, l.name) for l in tb]))
response = post(self.api_url, json=dict(chat_id=self.chat_id, text=message, parse_mode="Markdown"))
self.__last_time = utils.now()
return response.json().get('ok')
def report_free_bhs(self, battles: List[Tuple[int, int, int, int, datetime.timedelta]]):
battle_links = []
for battle_id, side_id, against_id, damage, time_left in battles:
total_seconds = int(time_left.total_seconds())
time_start = ""
hours, remainder = divmod(total_seconds, 3600)
if hours:
time_start = f"{hours}h "
minutes, seconds = divmod(remainder, 60)
time_start += f"{minutes:02}m {seconds:02}s"
damage = "{:,}".format(damage).replace(',', ' ')
battle_links.append(f"*{damage}*dmg bh for [{utils.COUNTRIES[side_id]} vs {utils.COUNTRIES[against_id]}]"
f"(https://www.erepublik.com/en/military/battlefield/{battle_id}) "
f"_time since start {time_start}_")
self.send_message("Free BHs:\n" + "\n".join(battle_links))
def report_full_energy(self, available: int, limit: int, interval: int):
message = f"Full energy ({available}hp/{limit}hp +{interval}hp/6min)"
self.send_message(message)
def report_medal(self, msg):
self.send_message(f"New award: *{msg}*")

View File

@ -7,15 +7,11 @@ import sys
import time import time
import traceback import traceback
import unicodedata import unicodedata
from collections import deque
from decimal import Decimal
from json import JSONEncoder
from pathlib import Path from pathlib import Path
from typing import Union, Any, List, NoReturn, Mapping from typing import Union, Any, List, NoReturn, Mapping
import pytz import pytz
import requests import requests
from requests import Response
__all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz", __all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz",
"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",
@ -23,7 +19,6 @@ __all__ = ["FOOD_ENERGY", "COMMIT_ID", "COUNTRIES", "erep_tz",
"write_silent_log", "write_interactive_log", "get_file", "write_file", "write_silent_log", "write_interactive_log", "get_file", "write_file",
"send_email", "normalize_html_json", "process_error", ] "send_email", "normalize_html_json", "process_error", ]
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"
@ -86,7 +81,8 @@ COUNTRIES = {1: 'Romania', 9: 'Brazil', 10: 'Italy', 11: 'France', 12: 'Germany'
COUNTRY_LINK = {1: 'Romania', 9: 'Brazil', 11: 'France', 12: 'Germany', 13: 'Hungary', 82: 'Cyprus', 168: 'Georgia', COUNTRY_LINK = {1: 'Romania', 9: 'Brazil', 11: 'France', 12: 'Germany', 13: 'Hungary', 82: 'Cyprus', 168: 'Georgia',
15: 'Spain', 23: 'Canada', 26: 'Mexico', 27: 'Argentina', 28: 'Venezuela', 80: 'Montenegro', 24: 'USA', 15: 'Spain', 23: 'Canada', 26: 'Mexico', 27: 'Argentina', 28: 'Venezuela', 80: 'Montenegro', 24: 'USA',
29: 'United-Kingdom', 50: 'Australia', 47: 'South-Korea',171: 'Cuba', 79: 'Republic-of-Macedonia-FYROM', 29: 'United-Kingdom', 50: 'Australia', 47: 'South-Korea', 171: 'Cuba',
79: 'Republic-of-Macedonia-FYROM',
30: 'Switzerland', 31: 'Netherlands', 32: 'Belgium', 33: 'Austria', 34: 'Czech-Republic', 35: 'Poland', 30: 'Switzerland', 31: 'Netherlands', 32: 'Belgium', 33: 'Austria', 34: 'Czech-Republic', 35: 'Poland',
36: 'Slovakia', 37: 'Norway', 38: 'Sweden', 39: 'Finland', 40: 'Ukraine', 41: 'Russia', 42: 'Bulgaria', 36: 'Slovakia', 37: 'Norway', 38: 'Sweden', 39: 'Finland', 40: 'Ukraine', 41: 'Russia', 42: 'Bulgaria',
43: 'Turkey', 44: 'Greece', 45: 'Japan', 48: 'India', 49: 'Indonesia', 78: 'Colombia', 68: 'Singapore', 43: 'Turkey', 44: 'Greece', 45: 'Japan', 48: 'India', 49: 'Indonesia', 78: 'Colombia', 68: 'Singapore',
@ -98,27 +94,6 @@ COUNTRY_LINK = {1: 'Romania', 9: 'Brazil', 11: 'France', 12: 'Germany', 13: 'Hun
169: 'Armenia', 83: 'Belarus', 84: 'New-Zealand', 164: 'Saudi-Arabia', 170: 'Nigeria', } 169: 'Armenia', 83: 'Belarus', 84: 'New-Zealand', 164: 'Saudi-Arabia', 170: 'Nigeria', }
class MyJSONEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
return float("{:.02f}".format(o))
elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', year=o.year, month=o.month, day=o.day, hour=o.hour, minute=o.minute,
second=o.second, microsecond=o.microsecond)
elif isinstance(o, datetime.date):
return dict(__type__='date', year=o.year, month=o.month, day=o.day)
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, '__dict__'):
return o.__dict__
elif isinstance(o, deque):
return list(o)
return super().default(o)
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)
@ -296,6 +271,7 @@ def send_email(name: str, content: List[Any], player=None, local_vars: Mapping[A
if local_vars: if local_vars:
if "state_thread" in local_vars: if "state_thread" in local_vars:
local_vars.pop('state_thread', None) local_vars.pop('state_thread', None)
from erepublik.classes import MyJSONEncoder
files.append(('file', ("local_vars.json", json.dumps(local_vars, indent=2, files.append(('file', ("local_vars.json", json.dumps(local_vars, indent=2,
cls=MyJSONEncoder, sort_keys=True), "application/json"))) cls=MyJSONEncoder, sort_keys=True), "application/json")))
if isinstance(player, Citizen): if isinstance(player, Citizen):
@ -354,5 +330,5 @@ def slugify(value, allow_unicode=False) -> str:
value = unicodedata.normalize('NFKC', value) value = unicodedata.normalize('NFKC', value)
else: else:
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii') value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
value = re.sub(r'[^\w\s-]', '', value).strip().lower() value = re.sub(r'[^\w\s-]', '_', value).strip().lower()
return re.sub(r'[-\s]+', '-', value) return re.sub(r'[-\s]+', '-', value)

View File

@ -5,11 +5,10 @@ watchdog==0.9.0
flake8==3.7.8 flake8==3.7.8
tox==3.13.2 tox==3.13.2
coverage==4.5.3 coverage==4.5.3
Sphinx==2.1.2 Sphinx==2.2.0
twine==1.13.0 twine==2.0.0
ipython==7.6.1 ipython
PyInstaller==3.5 PyInstaller
pytz==2019.1 pytz==2019.1
requests==2.22.0 requests==2.22.0
pycryptodome==3.8.2 edx-sphinx-theme
python-slugify==2.0.1

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.15.2 current_version = 0.16.0
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.1', 'requests==2.22.0'] requirements = ['pytz>=2019.2', 'requests>=2.22']
setup_requirements = [ ] setup_requirements = [ ]
@ -41,7 +41,7 @@ setup(
setup_requires=setup_requirements, setup_requires=setup_requirements,
test_suite='tests', test_suite='tests',
tests_require=test_requirements, tests_require=test_requirements,
url='https://github.com/eeriks/erepublik_script', url='https://github.com/eeriks/erepublik/',
version='0.15.2', version='0.16.0',
zip_safe=False, zip_safe=False,
) )

View File

@ -5,30 +5,126 @@
import unittest import unittest
from click.testing import CliRunner
from erepublik import Citizen from erepublik import Citizen
from erepublik import cli
class TestErepublik_script(unittest.TestCase): class TestErepublik(unittest.TestCase):
"""Tests for `erepublik` package.""" """Tests for `erepublik` package."""
def setUp(self): def setUp(self):
"""Set up test fixtures, if any.""" """Set up test fixtures, if any."""
self.citizen = Citizen("email", "password", False)
self.citizen.config.interactive = False
def tearDown(self): def test_should_do_levelup(self):
"""Tear down test fixtures, if any.""" self.citizen.energy.recovered = 1900
self.citizen.energy.recoverable = 2940
self.citizen.energy.interval = 30
self.citizen.energy.limit = 3000
self.citizen.details.xp = 14850
self.assertTrue(self.citizen.should_do_levelup)
def test_000_something(self): self.citizen.energy.recoverable = 1000
"""Test something.""" self.assertFalse(self.citizen.should_do_levelup)
def test_should_travel_to_fight(self):
self.citizen.config.always_travel = True
self.assertTrue(self.citizen.should_travel_to_fight())
self.citizen.config.always_travel = False
self.assertFalse(self.citizen.should_travel_to_fight())
self.citizen.energy.recovered = 1900
self.citizen.energy.recoverable = 2940
self.citizen.energy.interval = 30
self.citizen.energy.limit = 3000
self.citizen.details.xp = 14850
self.assertTrue(self.citizen.should_travel_to_fight())
self.citizen.details.xp = 15000
self.assertFalse(self.citizen.should_travel_to_fight())
self.citizen.energy.recovered = 3000
self.citizen.energy.recoverable = 2910
self.assertTrue(self.citizen.should_travel_to_fight())
self.citizen.energy.recoverable = 2900
self.assertFalse(self.citizen.should_travel_to_fight())
# self.citizen.next_reachable_energy and self.citizen.config.next_energy
self.citizen.config.next_energy = True
self.citizen.energy.limit = 5000
self.citizen.details.next_pp = [5000, 5250, 5750, 6250, 6750]
self.citizen.details.pp = 4900
self.citizen.energy.recovered = 4000
self.citizen.energy.recoverable = 4510
self.assertEqual(self.citizen.next_reachable_energy, 850)
self.citizen.energy.recoverable = 4490
self.assertTrue(self.citizen.should_travel_to_fight())
self.assertEqual(self.citizen.next_reachable_energy, 350)
self.citizen.energy.recovered = 100
self.citizen.energy.recoverable = 150
self.assertFalse(self.citizen.should_travel_to_fight())
self.assertEqual(self.citizen.next_reachable_energy, 0)
def test_should_fight(self):
self.citizen.config.fight = False
self.assertEqual(self.citizen.should_fight(), 0)
self.citizen.config.fight = True
# Level up
self.citizen.energy.limit = 3000
self.citizen.details.xp = 24705
self.assertEqual(self.citizen.should_fight(), 0)
self.citizen.energy.recovered = 3000
self.citizen.energy.recoverable = 2950
self.citizen.energy.interval = 30
self.assertEqual(self.citizen.should_fight(), 895)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 895)
self.citizen.my_companies.ff_lockdown = 0
# Level up reachable
self.citizen.details.xp = 24400
self.assertEqual(self.citizen.should_fight(), 305)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 305)
self.citizen.my_companies.ff_lockdown = 0
self.citizen.details.xp = 21000
self.assertEqual(self.citizen.should_fight(), 75)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 75)
self.citizen.my_companies.ff_lockdown = 0
self.citizen.details.pp = 80
# All-in (type = all-in and full ff)
self.citizen.config.all_in = True
self.assertEqual(self.citizen.should_fight(), 595)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 435)
self.citizen.my_companies.ff_lockdown = 0
self.citizen.config.air = True
self.citizen.energy.recoverable = 1000
self.assertEqual(self.citizen.should_fight(), 400)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 240)
self.citizen.my_companies.ff_lockdown = 0
self.citizen.config.all_in = False
self.citizen.config.next_energy = True
self.citizen.energy.limit = 5000
self.citizen.details.next_pp = [100, 150, 250, 400, 500]
self.assertEqual(self.citizen.should_fight(), 320)
self.citizen.my_companies.ff_lockdown = 160
self.assertEqual(self.citizen.should_fight(), 160)
self.citizen.my_companies.ff_lockdown = 0
self.citizen.energy.limit = 3000
self.citizen.details.next_pp = [19250, 20000]
self.citizen.config.next_energy = False
# 1h worth of energy
self.citizen.energy.recoverable = 2910
self.assertEqual(self.citizen.should_fight(), 30)
def test_command_line_interface(self):
"""Test the CLI."""
runner = CliRunner()
result = runner.invoke(cli.main)
assert result.exit_code == 0
assert 'erepublik.cli.main' in result.output
help_result = runner.invoke(cli.main, ['--help'])
assert help_result.exit_code == 0
assert '--help Show this message and exit.' in help_result.output

View File

@ -1,10 +1,9 @@
[tox] [tox]
envlist = py36, py37, flake8 envlist = py37, flake8
[travis] [travis]
python = python =
3.7: py37 3.7: py37
3.6: py36
[testenv:flake8] [testenv:flake8]
basepython = python basepython = python