Compare commits

...

38 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
17 changed files with 580 additions and 339 deletions

View File

@ -15,7 +15,7 @@ Types of Contributions
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:
@ -45,7 +45,7 @@ articles, and such.
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:
@ -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.
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::

View File

@ -2,6 +2,16 @@
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)
-------------------

View File

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

View File

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

View File

@ -23,6 +23,7 @@ import sys
sys.path.insert(0, os.path.abspath('..'))
import erepublik
import edx_theme
# -- General configuration ---------------------------------------------
@ -32,7 +33,7 @@ import erepublik
# Add any Sphinx extension module names here, as strings. They can be
# 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.
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
# 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 further. For a list of options available for each theme, see the
@ -158,6 +160,3 @@ texinfo_documents = [
'One line description of project.',
'Miscellaneous'),
]

View File

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

View File

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

View File

@ -4,4 +4,7 @@ Usage
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"""
__email__ = 'eriks@72.lv'
__version__ = '0.15.3'
__version__ = '0.16.0'
from erepublik import classes, utils
from erepublik.citizen import Citizen

View File

@ -14,8 +14,6 @@ from erepublik import classes, utils
class Citizen(classes.CitizenAPI):
url: str = "https://www.erepublik.com/en"
division = 0
all_battles: Dict[int, classes.Battle] = None
@ -47,13 +45,13 @@ class Citizen(classes.CitizenAPI):
r: requests.Response
reporter: classes.Reporter
token = ""
name = "Not logged in!"
debug = False
__registered = 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__()
self.commit_id = utils.COMMIT_ID
self.config = classes.Config()
@ -65,10 +63,21 @@ class Citizen(classes.CitizenAPI):
self.my_companies = classes.MyCompanies()
self.set_debug(True)
self.reporter = classes.Reporter()
self.get_csrf_token()
self.update_citizen_info()
self.reporter.do_init(self.name, email, self.details.citizen_id)
self.telegram = classes.TelegramBot()
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))
def write_log(self, *args, **kwargs):
@ -90,7 +99,10 @@ class Citizen(classes.CitizenAPI):
@property
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('stop_threads', None)
ret.pop('_Citizen__last_war_update_data', None)
@ -257,7 +269,9 @@ class Citizen(classes.CitizenAPI):
msgs = ["{count} x {kind}, totaling {} {currency}\n"
"{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():
self.reporter.report_action("NEW_MEDAL", info)
@ -266,6 +280,7 @@ class Citizen(classes.CitizenAPI):
level = levelup.group(1)
msg = "Level up! Current level {}".format(level)
self.write_log(msg)
self.telegram.report_medal(f"Level *{level}*")
self.reporter.report_action("LEVEL_UP", value=level)
def update_all(self, force_update=False):
@ -288,8 +303,8 @@ class Citizen(classes.CitizenAPI):
"""
if html is None:
self._get_main()
html = self.r.text
ugly_js = re.search(r"promotions: (\[{?.*}?]),\s+", html).group(1)
return
ugly_js = re.search(r'"promotions":\s*(\[{?.*?}?])', html).group(1)
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}
send_mail = False
@ -333,6 +348,8 @@ class Citizen(classes.CitizenAPI):
self.energy.limit = citizen.get("energyToRecover", 0)
self.energy.recovered = citizen.get("energy", 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_country = citizen.get("countryLocationId", 0) # country where citizen is located
@ -370,7 +387,7 @@ class Citizen(classes.CitizenAPI):
return resp_data
def update_job_info(self):
ot = self._get_job_data().json().get("overTime", {})
ot = self._get_main_job_data().json().get("overTime", {})
if ot:
self.my_companies.next_ot_time = utils.localize_timestamp(int(ot.get("nextOverTime", 0)))
self.ot_points = ot.get("points", 0)
@ -392,55 +409,58 @@ class Citizen(classes.CitizenAPI):
self.eb_small = 0
j = self._get_economy_inventory_items().json()
active_items = {}
for item in j.get("inventoryItems", {}).get("activeEnhancements", {}).get("items", {}).values():
active_items.update({item['name']: item['active']['time_left']})
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
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'] == 3 and item['quality'] == 5:
self.ot_points = item['amount']
elif item['industryId'] == 4 and item['quality'] == 100:
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']})
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']})
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"),
"total": j.get("inventoryStatus").get("totalStorage")})
@ -449,7 +469,7 @@ class Citizen(classes.CitizenAPI):
return inventory
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.next_pp = []
for reward in data.get("rewards", {}).get("normal", {}):
@ -457,7 +477,7 @@ class Citizen(classes.CitizenAPI):
if status == "rewarded":
continue
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":
pps = re.search(r"Reach (\d+) Prestige Points to unlock the following reward: \+1 Energy",
reward.get("tooltip", ""))
@ -488,8 +508,6 @@ class Citizen(classes.CitizenAPI):
"""
Try to eat food
"""
self.update_citizen_info()
self.update_inventory()
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")
@ -498,7 +516,6 @@ class Citizen(classes.CitizenAPI):
else:
self.write_log("I'm out of food! But I'll try to buy some!\n{}".format(self.food))
self.buy_food()
self.update_inventory()
if self.food["total"] > self.energy.interval:
self.eat()
else:
@ -535,7 +552,6 @@ class Citizen(classes.CitizenAPI):
@property
def health_info(self):
self.update_citizen_info()
ret = "{}/{} + {}, {}hp/6m. {}xp until level up".format(
self.energy.recovered,
self.energy.limit,
@ -609,12 +625,19 @@ class Citizen(classes.CitizenAPI):
ally_battles_ground: List[int] = []
other_battles_air: 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],
reverse=sort_by_time):
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
if self.details.citizenship in battle_sides:
elif self.details.citizenship in battle_sides:
if battle.is_air:
cs_battles_ground.append(battle.id)
else:
@ -646,11 +669,6 @@ class Citizen(classes.CitizenAPI):
else:
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 +
deployed_battles_air + deployed_battles_ground +
ally_battles_air + ally_battles_ground +
@ -797,6 +815,8 @@ class Citizen(classes.CitizenAPI):
self.resign()
self.find_new_job()
else:
if r.json().get('message') == 'employee':
self.find_new_job()
self.reporter.report_action("WORK_OT", r.json())
elif self.energy.food_fights < 1 and self.ot_points >= 24:
self._eat("blue")
@ -814,6 +834,8 @@ class Citizen(classes.CitizenAPI):
js = response.json()
good_msg = ["already_worked", "captcha"]
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.work()
else:
@ -828,7 +850,7 @@ class Citizen(classes.CitizenAPI):
self.work()
def train(self):
r = self._get_training_grounds_json()
r = self._get_main_training_grounds_json()
tg_json = r.json()
self.details.gold = tg_json["page_details"]["gold"]
self.tg_contract = {"free_train": tg_json["hasFreeTrain"]}
@ -978,7 +1000,9 @@ class Citizen(classes.CitizenAPI):
break
else:
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:
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()
@ -1034,7 +1058,7 @@ class Citizen(classes.CitizenAPI):
return True
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'):
return True
else:
@ -1045,7 +1069,7 @@ class Citizen(classes.CitizenAPI):
return False
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 = []
if data.get('regions'):
@ -1061,7 +1085,7 @@ class Citizen(classes.CitizenAPI):
return False
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'):
return True
else:
@ -1094,15 +1118,15 @@ class Citizen(classes.CitizenAPI):
"toCountryId": country_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
) -> 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', [])
def get_travel_countries(self) -> List[int]:
response_json = self._post_travel_data().json()
response_json = self._post_main_travel_data().json()
return_list = []
for country_data in response_json['countries'].values():
if country_data['currentRegions']:
@ -1110,20 +1134,20 @@ class Citizen(classes.CitizenAPI):
return return_list
def parse_notifications(self, page: int = 1) -> list:
community = self._get_notifications_ajax_community(page).json()
system = self._get_notifications_ajax_system(page).json()
community = self._get_main_notifications_ajax_community(page).json()
system = self._get_main_notifications_ajax_system(page).json()
return community['alertsList'] + system['alertsList']
def delete_notifications(self):
response = self._get_notifications_ajax_community().json()
response = self._get_main_notifications_ajax_community().json()
while response['totalAlerts']:
self._post_messages_alert([_['id'] for _ in response['alertList']])
response = self._get_notifications_ajax_community().json()
self._post_main_messages_alert([_['id'] for _ in response['alertList']])
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']:
self._post_messages_alert([_['id'] for _ in response['alertList']])
response = self._get_notifications_ajax_system().json()
self._post_main_messages_alert([_['id'] for _ in response['alertList']])
response = self._get_main_notifications_ajax_system().json()
def collect_weekly_reward(self):
self.update_weekly_challenge()
@ -1131,7 +1155,7 @@ class Citizen(classes.CitizenAPI):
def collect_daily_task(self) -> None:
self.update_citizen_info()
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:
if not self.details.citizen_id == 1620414:
@ -1207,10 +1231,9 @@ class Citizen(classes.CitizenAPI):
ret = items
return ret
def buy_food(self) -> None:
self.update_money()
def buy_food(self):
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")
cheapest_q, cheapest = sorted(local_offers.items(), key=lambda v: v[1]["price"] / hp_per_quality[v[0]])[0]
@ -1265,14 +1288,16 @@ class Citizen(classes.CitizenAPI):
self._post_economy_activate_booster(5, duration, "damage")
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:
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 """
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,
quality: int = 1) -> Response:
@ -1290,24 +1315,24 @@ class Citizen(classes.CitizenAPI):
for notification in self.parse_notifications():
don_id = re.search(r"erepublik.functions.acceptRejectDonation\(\"accept\", (\d+)\)", notification)
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)
def reject_money_donations(self) -> int:
r = self._get_notifications_ajax_system()
r = self._get_main_notifications_ajax_system()
count = 0
donation_ids = re.findall(r"erepublik.functions.acceptRejectDonation\(\"reject\", (\d+)\)", r.text)
while 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
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)
return count
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:
ret = False
@ -1315,8 +1340,6 @@ class Citizen(classes.CitizenAPI):
ret = True
elif self.should_do_levelup: # Do levelup
ret = True
elif self.config.all_in and self.energy.available > self.energy.limit * 2 - self.energy.interval * 3:
ret = True
# Get to next Energy +1
elif self.next_reachable_energy and self.config.next_energy:
ret = True
@ -1361,8 +1384,7 @@ class Citizen(classes.CitizenAPI):
log_msg = "Fighting all-in. Doing %i hits" % count
# All-in for AIR battles
elif all([self.config.air, self.config.all_in,
self.energy.available >= self.energy.limit]):
elif all([self.config.air, self.config.all_in, self.energy.available >= self.energy.limit]):
count = self.energy.food_fights
log_msg = "Fighting all-in in AIR. Doing %i hits" % count
@ -1375,18 +1397,16 @@ class Citizen(classes.CitizenAPI):
elif self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2:
count = self.energy.interval
log_msg = "Fighting for 1h energy. Doing %i hits" % count
force_fight = True
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:
log_msg = ("Fight count modified (old count: {} | FF: {} | "
"WAM ff_lockdown: {} | New count: {})").format(
count, self.energy.food_fights, self.my_companies.ff_lockdown,
count - self.my_companies.ff_lockdown)
count -= self.my_companies.ff_lockdown
else:
count = 0
if self.energy.food_fights - self.my_companies.ff_lockdown < count:
log_msg = (f"Fight count modified (old count: {count} | FF: {self.energy.food_fights} | "
f"WAM ff_lockdown: {self.my_companies.ff_lockdown} |"
f" New count: {count - self.my_companies.ff_lockdown})")
count -= self.my_companies.ff_lockdown
if count <= 0:
count = 0
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:
@ -1466,7 +1486,7 @@ class Citizen(classes.CitizenAPI):
self.energy.recoverable + 2 * self.energy.interval >= self.energy.limit) # can do max amount of dmg
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:
if msg is None:
@ -1479,14 +1499,14 @@ class Citizen(classes.CitizenAPI):
return r
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:
kinds = {1: "First steps in eRepublik", 2: "Battle orders", 3: "Warfare analysis",
4: "Political debates and analysis", 5: "Financial business",
6: "Social interactions and entertainment"}
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:
raise classes.ErepublikException(
"Article kind must be one of:\n{}\n'{}' is not supported".format(
@ -1527,7 +1547,6 @@ class Citizen(classes.CitizenAPI):
def get_raw_surplus(self) -> (float, float):
frm = 0.00
wrm = 0.00
self.update_companies()
for cdata in sorted(self.my_companies.companies.values()):
if cdata["industry_token"] == "FOOD":
raw = frm
@ -1587,7 +1606,7 @@ class Citizen(classes.CitizenAPI):
return self.available_industries.get(industry_name, 0)
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())
return ret
@ -1604,7 +1623,7 @@ class Citizen(classes.CitizenAPI):
jobs = r.json().get("jobs")
data = dict(citizen=0, salary=10)
for posting in jobs:
salary = posting.get("netSalary")
salary = posting.get("salary")
limit = posting.get("salaryLimit", 0)
userid = posting.get("citizen").get("id")
@ -1614,10 +1633,10 @@ class Citizen(classes.CitizenAPI):
return self._post_economy_job_market_apply(**data)
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()
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))
return r
return resp
@ -1625,15 +1644,15 @@ class Citizen(classes.CitizenAPI):
def get_country_parties(self, country_id: int = None) -> dict:
if country_id is None:
country_id = self.details.citizenship
r = self._get_rankings_parties(country_id)
r = self._get_main_rankings_parties(country_id)
ret = {}
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})
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 = {}
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+)" '
r'title="([\w\d_ .]+)">', r.text):
ret.update({id_: name})
@ -1641,11 +1660,11 @@ class Citizen(classes.CitizenAPI):
def get_country_mus(self, country_id: int) -> Dict[int, str]:
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"]:
if data["id"]:
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"]:
if data["id"]:
ret.update({data["id"]: data["name"]})
@ -1666,13 +1685,13 @@ class Citizen(classes.CitizenAPI):
if ids is None:
ids = [1620414, ]
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):
cities = []
cities_dict = {}
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():
cities.append(region_data['cityId'])
cities_dict.update({region_data['cityId']: region_data['cityName']})
@ -1680,11 +1699,11 @@ class Citizen(classes.CitizenAPI):
cities.sort(key=int)
for city_id in cities:
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"]:
self.add_friend(resident["citizenId"])
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()
for resident in resp["widgets"]["residents"]["residents"]:
self.add_friend(resident["citizenId"])
@ -1728,6 +1747,7 @@ class Citizen(classes.CitizenAPI):
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.telegram.send_message(f"Battle for *{region_name}* queued")
def state_update_repeater(self):
try:
@ -1817,13 +1837,16 @@ class Citizen(classes.CitizenAPI):
return active_until
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:
battle = self.all_battles.get(battle_id)
if not battle:
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"),
battle.defender.id: r.json().get(str(battle.defender.id)).get("fighterData")}
@ -1833,8 +1856,9 @@ class Citizen(classes.CitizenAPI):
if self.details.cc < amount or amount < 20:
return False
data = dict(country=71, action='currency', value=amount)
self.telegram.send_message(f"Donated {amount}cc to {utils.COUNTRIES[71]}")
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')
def contribute_food_to_country(self, amount: int = 0, quality: int = 1) -> bool:
@ -1844,7 +1868,7 @@ class Citizen(classes.CitizenAPI):
return False
data = dict(country=71, action='food', value=amount, quality=quality)
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')
def contribute_gold_to_country(self, amount: int) -> bool:
@ -1854,14 +1878,14 @@ class Citizen(classes.CitizenAPI):
return False
data = dict(country=71, action='gold', value=amount)
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')
def write_on_country_wall(self, message: str) -> bool:
self._get_main()
post_to_wall_as = re.findall(r'id="post_to_country_as".*?<option value="(\d?)">.*?</option>.*</select>',
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()
def report_error(self, msg: str = ""):
@ -1902,7 +1926,7 @@ class Citizen(classes.CitizenAPI):
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_citizen_profile(self.details.citizen_id).json()
r = self._get_main_citizen_profile_json(self.details.citizen_id).json()
if not rang:
rang = r['military']['militaryData']['ground']['rankNumber']
if not strength:
@ -1923,7 +1947,7 @@ class Citizen(classes.CitizenAPI):
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_citizen_profile(self.details.citizen_id).json()
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:
@ -1933,3 +1957,14 @@ class Citizen(classes.CitizenAPI):
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 hashlib
import random
import sys
import time
import traceback
from collections import deque
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
@ -61,7 +63,7 @@ class MyCompanies:
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,
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():
tmp = {}
@ -119,7 +121,10 @@ class MyCompanies:
raw = []
factory = []
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, {})
wam_enabled = bool(company.get('wam_enabled', {}))
already_worked = not company.get('already_worked', {})
@ -156,28 +161,33 @@ class MyCompanies:
raise ErepublikException("Wrong function call")
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class SlowRequests(Session):
last_time: datetime.datetime
timeout = datetime.timedelta(milliseconds=500)
uas = [
# Chrome
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/73.0.3683.103 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/72.0.3626.13 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/71.0.3578.98 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',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36',
# 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:65.0) Gecko/20100101 Firefox/65.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0',
]
debug = False
@ -280,38 +290,21 @@ class Config:
force_wam = False
sort_battles_time = True
force_travel = False
telegram = True
telegram_chat_id = 0
telegram_token = ""
@property
def wt(self):
return self.work and self.train
@property
def __dict__(self) -> Dict[str, Union[bool, str, List[str]]]:
return dict(
email=self.email,
password=self.password,
work=self.work,
train=self.train,
wam=self.wam,
auto_sell=self.auto_sell,
auto_sell_all=self.auto_sell_all,
employees=self.employees,
fight=self.fight,
air=self.air,
ground=self.ground,
all_in=self.all_in,
next_energy=self.next_energy,
boosters=self.boosters,
travel_to_fight=self.travel_to_fight,
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,
)
ret = {}
for key in dir(self):
if not key.startswith('_') and key not in ['email', 'password']:
ret[key] = getattr(self, key)
return ret
class Energy:
@ -332,7 +325,7 @@ class Energy:
@property
def food_fights(self):
return (self.recoverable + self.recovered) // 10
return self.available // 10
@property
def reference_time(self):
@ -344,7 +337,7 @@ class Energy:
@property
def is_recoverable_full(self):
return self.recoverable >= self.limit - self.interval
return self.recoverable >= self.limit - 5 * self.interval
@property
def is_recovered_full(self):
@ -360,13 +353,11 @@ class Energy:
@property
def __dict__(self):
return dict(
limit=self.limit,
interval=self.interval,
recoverable=self.recoverable,
recovered=self.recovered,
reference_time=self.reference_time
)
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class Details(object):
@ -412,6 +403,14 @@ class Details(object):
next_level_up = (1 + (self.xp // 10)) * 10
return next_level_up - self.xp
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class Politics:
is_party_member: bool = False
@ -422,6 +421,14 @@ class Politics:
is_congressman: bool = False
is_country_president: bool = False
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class House(object):
quality = None
@ -454,25 +461,25 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def get(self, url: str, **kwargs) -> Response:
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))
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))
def _get_candidate_party(self, party_slug: str) -> Response:
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))
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))
def _get_citizen_daily_assistant(self) -> Response:
def _get_main_citizen_daily_assistant(self) -> Response:
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:
params = {}
return self.get("{}/main/city-data/{}/residents".format(self.url, city), params={"currentPage": page, **params})
@ -495,96 +502,95 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
def _get_economy_my_market_offers(self) -> Response:
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))
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)
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)
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)
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)
return self.get("{}/main/leaderboards-kills-rankings/{}/{}/{}/{}".format(self.url, *data))
def _get_main(self) -> Response:
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))
def _get_military_campaigns(self) -> Response:
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:
params = {"groupId": unit_id, "panel": "members", **kwargs}
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})
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})
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))
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))
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))
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))
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))
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))
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))
def _get_wars_show(self, war_id: int) -> Response:
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)
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)
if page:
data.update({'page': page})
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)
if parent:
data.update({"parentId": parent})
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,
damage: bool) -> Response:
data = dict(battleId=battle, zoneId=zone, action="battleStatistics", round=round_id, division=division,
leftPage=page, rightPage=page, _token=self.token)
if damage:
data.update({"type": "damage"})
else:
data.update({"type": "kills"})
def _post_main_battlefield_travel(self, side_id: int, battle_id: int) -> Response:
data = dict(_token=self.token, sideCountryId=side_id, battleId=battle_id)
return self.post("{}/main/battlefieldTravel".format(self.url), data=data)
return self.post("{}/military/battle-console".format(self.url), data=data)
def _post_buy_gold_items(self, currency: str, item: str, amount: int) -> Response:
def _post_main_buy_gold_items(self, currency: str, item: str, amount: int) -> Response:
data = dict(itemId=item, currency=currency, amount=amount, _token=self.token)
return self.post("{}/main/buyGoldItems".format(self.url), data=data)
@ -592,7 +598,7 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(_token=self.token, presentation=presentation)
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")
if add:
data.update({"action": "addFriend"})
@ -600,18 +606,22 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data.update({"action": "removeFriend"})
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})
def _post_country_donate(self, country: int, action: str, value: Union[int, float],
quality: int = None) -> Response:
def _post_main_country_donate(self, country: int, action: str, value: Union[int, float],
quality: int = None) -> Response:
json = dict(countryId=country, action=action, _token=self.token, value=value, quality=quality)
return self.post("{}/main/country-donate".format(self.url), data=json,
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))
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:
data = {"_token": self.token, "delete_message[]": msg_id}
return self.post("{}/main/messages-delete".format(self.url), data)
@ -687,18 +697,18 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data={"_token": self.token, "action_type": "resign"})
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)
if sell:
data.update({"sell": "sell"})
else:
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:
data: Dict[str, Union[int, str]] = {}
if not tg_ids:
return self._get_training_grounds_json()
return self._get_main_training_grounds_json()
else:
for idx, tg_id in enumerate(tg_ids):
data["grounds[%i][id]" % idx] = tg_id
@ -746,7 +756,7 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(_token=self.token, email=email, commit="Reset password")
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)
return self.post("{}/military/fight-activateBooster".format(self.url), data=data)
@ -754,11 +764,11 @@ 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')
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"}
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])
data = dict(citizen_name=",".join([str(x) for x in citizens]),
citizen_subject=subject, _token=self.token, citizen_message=body)
@ -768,11 +778,16 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(battleId=battle_id, action=action, _token=self.token)
if action == "battleStatistics":
data.update(round=kwargs["round_id"], zoneId=kwargs["round_id"], leftPage=page, rightPage=page,
division=kwargs["division"], type=kwargs.get("type", 'damage'),)
division=kwargs["division"], type=kwargs.get("type", 'damage'), )
elif action == "warList":
data.update(page=page)
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:
data = dict(battleId=battle_id, bombId=bomb_id, _token=self.token)
return self.post("{}/military/deploy-bomb".format(self.url), data=data)
@ -789,22 +804,26 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
data = dict(action="check", _token=self.token)
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)
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))
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}
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)
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,
article_category=kind)
return self.post("{}/main/write-article".format(self.url), data=data)
@ -812,73 +831,73 @@ Class for unifying eRepublik known endpoints and their required/optional paramet
# Wall Posts
# ## 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}
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}
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}
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}
return self.post("{}/main/country-post/retrieve/json".format(self.url), data=data)
# ## 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}
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}
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}
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}
return self.post("{}/main/military-unit-post/retrieve/json".format(self.url), data=data)
# ## 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}
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}
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}
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}
return self.post("{}/main/party-post/retrieve/json".format(self.url), data=data)
# ## 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}
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}
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}
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}
return self.post("{}/main/wall-post/retrieve/json".format(self.url), data=data)
@ -962,11 +981,12 @@ class Reporter:
class MyJSONEncoder(JSONEncoder):
def default(self, o):
from erepublik.citizen import Citizen
if isinstance(o, decimal.Decimal):
return float("{:.02f}".format(o))
elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', year=o.year, month=o.month, day=o.day, hour=o.hour, minute=o.minute,
second=o.second, microsecond=o.microsecond)
second=o.second, microsecond=o.microsecond, tzinfo=o.tzinfo.zone if o.tzinfo else None)
elif isinstance(o, datetime.date):
return dict(__type__='date', year=o.year, month=o.month, day=o.day)
elif isinstance(o, datetime.timedelta):
@ -978,6 +998,8 @@ class MyJSONEncoder(JSONEncoder):
return o.__dict__
elif isinstance(o, deque):
return list(o)
elif isinstance(o, Citizen):
return o.to_json()
return super().default(o)
@ -993,6 +1015,14 @@ class BattleSide:
self.allies = [int(ally) for ally in allies]
self.deployed = [int(ally) for ally in deployed]
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class BattleDivision:
end: datetime.datetime
@ -1010,6 +1040,14 @@ class BattleDivision:
self.dom_pts = dict({"inv": inv_pts, "def": def_pts})
self.wall = dict({"for": wall_for, "dom": wall_dom})
@property
def __dict__(self):
ret = {}
for key in dir(self):
if not key.startswith('_'):
ret[key] = getattr(self, key)
return ret
class Battle(object):
id: int = 0
@ -1070,6 +1108,14 @@ class Battle(object):
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:
energy: int = 0
@ -1100,3 +1146,57 @@ class EnergyToFight:
if 0 < new_energy < self.energy:
self.energy = new_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

@ -330,5 +330,5 @@ def slugify(value, allow_unicode=False) -> str:
value = unicodedata.normalize('NFKC', value)
else:
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)

View File

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

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.15.3
current_version = 0.16.0
commit = True
tag = True

View File

@ -11,7 +11,7 @@ with open('README.rst') as readme_file:
with open('HISTORY.rst') as history_file:
history = history_file.read()
requirements = ['pytz==2019.1', 'requests==2.22.0']
requirements = ['pytz>=2019.2', 'requests>=2.22']
setup_requirements = [ ]
@ -41,7 +41,7 @@ setup(
setup_requires=setup_requirements,
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik_script',
version='0.15.3',
url='https://github.com/eeriks/erepublik/',
version='0.16.0',
zip_safe=False,
)

View File

@ -5,30 +5,126 @@
import unittest
from click.testing import CliRunner
from erepublik import Citizen
from erepublik import cli
class TestErepublik_script(unittest.TestCase):
class TestErepublik(unittest.TestCase):
"""Tests for `erepublik` package."""
def setUp(self):
"""Set up test fixtures, if any."""
self.citizen = Citizen("email", "password", False)
self.citizen.config.interactive = False
def tearDown(self):
"""Tear down test fixtures, if any."""
def test_should_do_levelup(self):
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):
"""Test something."""
self.citizen.energy.recoverable = 1000
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]
envlist = py36, py37, flake8
envlist = py37, flake8
[travis]
python =
3.7: py37
3.6: py36
[testenv:flake8]
basepython = python